Completed
Push — master ( 2ab56b...9d3aa0 )
by Julito
38:18
created

Exercise::selectRandomAnswers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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
 * @todo use doctrine object
12
 * @author Olivier Brouckaert
13
 * @author Julio Montoya Cleaning exercises
14
 * Modified by Hubert Borderiou #294
15
 */
16
class Exercise
17
{
18
    public $id;
19
    public $name;
20
    public $title;
21
    public $exercise;
22
    public $description;
23
    public $sound;
24
    public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
25
    public $random;
26
    public $random_answers;
27
    public $active;
28
    public $timeLimit;
29
    public $attempts;
30
    public $feedback_type;
31
    public $end_time;
32
    public $start_time;
33
    public $questionList;  // array with the list of this exercise's questions
34
    /* including question list of the media */
35
    public $questionListUncompressed;
36
    public $results_disabled;
37
    public $expired_time;
38
    public $course;
39
    public $course_id;
40
    public $propagate_neg;
41
    public $saveCorrectAnswers;
42
    public $review_answers;
43
    public $randomByCat;
44
    public $text_when_finished;
45
    public $display_category_name;
46
    public $pass_percentage;
47
    public $edit_exercise_in_lp = false;
48
    public $is_gradebook_locked = false;
49
    public $exercise_was_added_in_lp = false;
50
    public $lpList = array();
51
    public $force_edit_exercise_in_lp = false;
52
    public $categories;
53
    public $categories_grouping = true;
54
    public $endButton = 0;
55
    public $categoryWithQuestionList;
56
    public $mediaList;
57
    public $loadQuestionAJAX = false;
58
    // Notification send to the teacher.
59
    public $emailNotificationTemplate = null;
60
    // Notification send to the student.
61
    public $emailNotificationTemplateToUser = null;
62
    public $countQuestions = 0;
63
    public $fastEdition = false;
64
    public $modelType = 1;
65
    public $questionSelectionType = EX_Q_SELECTION_ORDERED;
66
    public $hideQuestionTitle = 0;
67
    public $scoreTypeModel = 0;
68
    public $categoryMinusOne = true; // Shows the category -1: See BT#6540
69
    public $globalCategoryId = null;
70
    public $onSuccessMessage = null;
71
    public $onFailedMessage = null;
72
    public $emailAlert;
73
    public $notifyUserByEmail = '';
74
    public $sessionId = 0;
75
76
    /**
77
     * Constructor of the class
78
     *
79
     * @author Olivier Brouckaert
80
     */
81
    public function __construct($course_id = null)
82
    {
83
        $this->id = 0;
84
        $this->exercise = '';
85
        $this->description = '';
86
        $this->sound = '';
87
        $this->type = ALL_ON_ONE_PAGE;
88
        $this->random = 0;
89
        $this->random_answers = 0;
90
        $this->active = 1;
91
        $this->questionList = array();
92
        $this->timeLimit = 0;
93
        $this->end_time = '';
94
        $this->start_time = '';
95
        $this->results_disabled = 1;
96
        $this->expired_time = 0;
97
        $this->propagate_neg = 0;
98
        $this->saveCorrectAnswers = 0;
99
        $this->review_answers = false;
100
        $this->randomByCat = 0;
101
        $this->text_when_finished = '';
102
        $this->display_category_name = 0;
103
        $this->pass_percentage = '';
104
105
        $this->modelType = 1;
106
        $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
107
        $this->endButton = 0;
108
        $this->scoreTypeModel = 0;
109
        $this->globalCategoryId = null;
110
111
        if (!empty($course_id)) {
112
            $course_info = api_get_course_info_by_id($course_id);
113
        } else {
114
            $course_info = api_get_course_info();
115
        }
116
        $this->course_id = $course_info['real_id'];
117
        $this->course = $course_info;
118
    }
119
120
    /**
121
     * Reads exercise information from the data base
122
     *
123
     * @author Olivier Brouckaert
124
     * @param integer $id - exercise Id
125
     *
126
     * @return boolean - true if exercise exists, otherwise false
127
     */
128
    public function read($id, $parseQuestionList = true)
129
    {
130
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
131
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
132
133
        $id  = intval($id);
134
        if (empty($this->course_id)) {
135
136
            return false;
137
        }
138
        $sql = "SELECT * FROM $TBL_EXERCISES 
139
                WHERE c_id = ".$this->course_id." AND id = ".$id;
140
        $result = Database::query($sql);
141
142
        // if the exercise has been found
143
        if ($object = Database::fetch_object($result)) {
144
            $this->id = $id;
145
            $this->exercise = $object->title;
146
            $this->name = $object->title;
147
            $this->title = $object->title;
148
            $this->description = $object->description;
149
            $this->sound = $object->sound;
150
            $this->type = $object->type;
151
            if (empty($this->type)) {
152
                $this->type = ONE_PER_PAGE;
153
            }
154
            $this->random = $object->random;
155
            $this->random_answers = $object->random_answers;
156
            $this->active = $object->active;
157
            $this->results_disabled = $object->results_disabled;
158
            $this->attempts = $object->max_attempt;
159
            $this->feedback_type = $object->feedback_type;
160
            $this->propagate_neg = $object->propagate_neg;
161
            $this->saveCorrectAnswers = $object->save_correct_answers;
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 (!empty($object->end_time)) {
193
                $this->end_time = $object->end_time;
194
            }
195
            if (!empty($object->start_time)) {
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
            if ($parseQuestionList) {
204
                $this->setQuestionList();
205
            }
206
207
            //overload questions list with recorded questions list
208
            //load questions only for exercises of type 'one question per page'
209
            //this is needed only is there is no questions
210
            /*
211
			// @todo not sure were in the code this is used somebody mess with the exercise tool
212
			// @todo don't know who add that config and why $_configuration['live_exercise_tracking']
213
			global $_configuration, $questionList;
214
			if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST' && defined('QUESTION_LIST_ALREADY_LOGGED') &&
215
			isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']) {
216
				$this->questionList = $questionList;
217
			}*/
218
            return true;
219
        }
220
221
        return false;
222
    }
223
224
    /**
225
     * @return string
226
     */
227
    public function getCutTitle()
228
    {
229
        return cut($this->exercise, EXERCISE_MAX_NAME_SIZE);
230
    }
231
232
    /**
233
     * returns the exercise ID
234
     *
235
     * @author Olivier Brouckaert
236
     * @return int - exercise ID
237
     */
238
    public function selectId()
239
    {
240
        return $this->id;
241
    }
242
243
    /**
244
     * returns the exercise title
245
     *
246
     * @author Olivier Brouckaert
247
     * @return string - exercise title
248
     */
249
    public function selectTitle()
250
    {
251
        return $this->exercise;
252
    }
253
254
    /**
255
     * returns the number of attempts setted
256
     *
257
     * @return int - exercise attempts
258
     */
259
    public function selectAttempts()
260
    {
261
        return $this->attempts;
262
    }
263
264
    /** returns the number of FeedbackType  *
265
     *  0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback
266
     * @return int - exercise attempts
267
     */
268
    public function selectFeedbackType()
269
    {
270
        return $this->feedback_type;
271
    }
272
273
    /**
274
     * returns the time limit
275
     */
276
    public function selectTimeLimit()
277
    {
278
        return $this->timeLimit;
279
    }
280
281
    /**
282
     * returns the exercise description
283
     *
284
     * @author Olivier Brouckaert
285
     * @return string - exercise description
286
     */
287
    public function selectDescription()
288
    {
289
        return $this->description;
290
    }
291
292
    /**
293
     * returns the exercise sound file
294
     *
295
     * @author Olivier Brouckaert
296
     * @return string - exercise description
297
     */
298
    public function selectSound()
299
    {
300
        return $this->sound;
301
    }
302
303
    /**
304
     * returns the exercise type
305
     *
306
     * @author Olivier Brouckaert
307
     * @return integer - exercise type
308
     */
309
    public function selectType()
310
    {
311
        return $this->type;
312
    }
313
314
    /**
315
     * @return int
316
     */
317
    public function getModelType()
318
    {
319
        return $this->modelType;
320
    }
321
322
    /**
323
     * @return int
324
     */
325
    public function selectEndButton()
326
    {
327
        return $this->endButton;
328
    }
329
330
    /**
331
     * @return string
332
     */
333
    public function getOnSuccessMessage()
334
    {
335
        return $this->onSuccessMessage;
336
    }
337
338
    /**
339
     * @return string
340
     */
341
    public function getOnFailedMessage()
342
    {
343
        return $this->onFailedMessage;
344
    }
345
346
    /**
347
     * @author hubert borderiou 30-11-11
348
     * @return integer : do we display the question category name for students
349
     */
350
    public function selectDisplayCategoryName()
351
    {
352
        return $this->display_category_name;
353
    }
354
355
    /**
356
     * @return int
357
     */
358
    public function selectPassPercentage()
359
    {
360
        return $this->pass_percentage;
361
    }
362
363
    /**
364
     *
365
     * Modify object to update the switch display_category_name
366
     * @author hubert borderiou 30-11-11
367
     * @param int $in_txt is an integer 0 or 1
368
     */
369
    public function updateDisplayCategoryName($in_txt)
370
    {
371
        $this->display_category_name = $in_txt;
372
    }
373
374
    /**
375
     * @author hubert borderiou 28-11-11
376
     * @return string html text : the text to display ay the end of the test.
377
     */
378
    public function selectTextWhenFinished()
379
    {
380
        return $this->text_when_finished;
381
    }
382
383
    /**
384
     * @author hubert borderiou 28-11-11
385
     * @return string  html text : update the text to display ay the end of the test.
386
     */
387
    public function updateTextWhenFinished($in_txt)
388
    {
389
        $this->text_when_finished = $in_txt;
390
    }
391
392
    /**
393
     * return 1 or 2 if randomByCat
394
     * @author hubert borderiou
395
     * @return integer - quiz random by category
396
     */
397
    public function selectRandomByCat()
398
    {
399
        return $this->randomByCat;
400
    }
401
402
    /**
403
     * return 0 if no random by cat
404
     * return 1 if random by cat, categories shuffled
405
     * return 2 if random by cat, categories sorted by alphabetic order
406
     * @author hubert borderiou
407
     * @return integer - quiz random by category
408
     */
409
    public function isRandomByCat()
410
    {
411
        /*$res = 0;
412
        if ($this->randomByCat == 1) {
413
            $res = 1;
414
        } else if ($this->randomByCat == 2) {
415
            $res = 2;
416
        }
417
        */
418
419
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
420
        if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
421
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
422
        } else if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
423
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
424
        }
425
426
        return $res;
427
    }
428
429
    /**
430
     * return nothing
431
     * update randomByCat value for object
432
     * @param int $random
433
     *
434
     * @author hubert borderiou
435
     */
436
    public function updateRandomByCat($random)
437
    {
438
        if (in_array($random, array(
439
                EXERCISE_CATEGORY_RANDOM_SHUFFLED,
440
                EXERCISE_CATEGORY_RANDOM_ORDERED,
441
                EXERCISE_CATEGORY_RANDOM_DISABLED
442
            )
443
        )) {
444
            $this->randomByCat = $random;
445
        } else {
446
            $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
447
        }
448
    }
449
450
    /**
451
     * Tells if questions are selected randomly, and if so returns the draws
452
     *
453
     * @author Carlos Vargas
454
     * @return integer - results disabled exercise
455
     */
456
    public function selectResultsDisabled()
457
    {
458
        return $this->results_disabled;
459
    }
460
461
    /**
462
     * tells if questions are selected randomly, and if so returns the draws
463
     *
464
     * @author Olivier Brouckaert
465
     * @return integer - 0 if not random, otherwise the draws
466
     */
467
    public function isRandom()
468
    {
469
        if($this->random > 0 || $this->random == -1) {
470
            return true;
471
        } else {
472
            return false;
473
        }
474
    }
475
476
    /**
477
     * returns random answers status.
478
     *
479
     * @author Juan Carlos Rana
480
     */
481
    public function selectRandomAnswers()
482
    {
483
        return $this->random_answers;
484
    }
485
486
    /**
487
     * Same as isRandom() but has a name applied to values different than 0 or 1
488
     */
489
    public function getShuffle()
490
    {
491
        return $this->random;
492
    }
493
494
    /**
495
     * returns the exercise status (1 = enabled ; 0 = disabled)
496
     *
497
     * @author Olivier Brouckaert
498
     * @return boolean - true if enabled, otherwise false
499
     */
500
    public function selectStatus()
501
    {
502
        return $this->active;
503
    }
504
505
    /**
506
     * If false the question list will be managed as always if true the question will be filtered
507
     * depending of the exercise settings (table c_quiz_rel_category)
508
     * @param bool active or inactive grouping
509
     **/
510
    public function setCategoriesGrouping($status)
511
    {
512
        $this->categories_grouping = (bool) $status;
513
    }
514
515
    /**
516
     * @return int
517
     */
518
    public function getHideQuestionTitle()
519
    {
520
        return $this->hideQuestionTitle;
521
    }
522
523
    /**
524
     * @param $value
525
     */
526
    public function setHideQuestionTitle($value)
527
    {
528
        $this->hideQuestionTitle = intval($value);
529
    }
530
531
    /**
532
     * @return int
533
     */
534
    public function getScoreTypeModel()
535
    {
536
        return $this->scoreTypeModel;
537
    }
538
539
    /**
540
     * @param int $value
541
     */
542
    public function setScoreTypeModel($value)
543
    {
544
        $this->scoreTypeModel = intval($value);
545
    }
546
547
    /**
548
     * @return int
549
     */
550
    public function getGlobalCategoryId()
551
    {
552
        return $this->globalCategoryId;
553
    }
554
555
    /**
556
     * @param int $value
557
     */
558
    public function setGlobalCategoryId($value)
559
    {
560
        if (is_array($value) && isset($value[0])) {
561
            $value = $value[0];
562
        }
563
        $this->globalCategoryId = intval($value);
564
    }
565
566
    /**
567
     *
568
     * @param int $start
569
     * @param int $limit
570
     * @param int $sidx
571
     * @param string $sord
572
     * @param array $where_condition
573
     * @param array $extraFields
574
     */
575
    public function getQuestionListPagination($start, $limit, $sidx, $sord, $where_condition = array(), $extraFields = array())
576
    {
577
        if (!empty($this->id)) {
578
            $category_list = TestCategory::getListOfCategoriesNameForTest($this->id, false);
579
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
580
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
581
582
            $sql = "SELECT q.iid
583
                    FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS  q
584
                        ON (e.question_id = q.iid AND e.c_id = ".$this->course_id." )
585
					WHERE e.exercice_id	= '".Database::escape_string($this->id)."'
586
					";
587
588
            $orderCondition = "ORDER BY question_order";
589
590
            if (!empty($sidx) && !empty($sord)) {
591
                if ($sidx == 'question') {
592
593
                    if (in_array(strtolower($sord), array('desc', 'asc'))) {
594
                        $orderCondition = " ORDER BY q.$sidx $sord";
595
                    }
596
                }
597
            }
598
599
            $sql .= $orderCondition;
600
601
            $limitCondition = null;
602
603 View Code Duplication
            if (isset($start) && isset($limit)) {
604
                $start = intval($start);
605
                $limit = intval($limit);
606
                $limitCondition = " LIMIT $start, $limit";
607
            }
608
            $sql .= $limitCondition;
609
            $result = Database::query($sql);
610
            $questions = array();
611
            if (Database::num_rows($result)) {
612
                if (!empty($extraFields)) {
613
                    $extraFieldValue = new ExtraFieldValue('question');
614
                }
615
                while ($question = Database::fetch_array($result, 'ASSOC')) {
616
                    /** @var Question $objQuestionTmp */
617
                    $objQuestionTmp = Question::read($question['iid']);
618
                    $category_labels = TestCategory::return_category_labels($objQuestionTmp->category_list, $category_list);
619
620
                    if (empty($category_labels)) {
621
                        $category_labels = "-";
622
                    }
623
624
                    // Question type
625
                    list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html();
626
627
                    $question_media = null;
628
                    if (!empty($objQuestionTmp->parent_id)) {
629
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
630
                        $question_media  = Question::getMediaLabel($objQuestionMedia->question);
631
                    }
632
633
                    $questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM).$question_media);
634
635
                    $question = array(
636
                        'id' => $question['iid'],
637
                        'question' => $objQuestionTmp->selectTitle(),
638
                        'type' => $questionType,
639
                        'category' => Display::tag('div', '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'),
640
                        'score' => $objQuestionTmp->selectWeighting(),
641
                        'level' => $objQuestionTmp->level
642
                    );
643
                    if (!empty($extraFields)) {
644
                        foreach ($extraFields as $extraField) {
645
                            $value = $extraFieldValue->get_values_by_handler_and_field_id($question['id'], $extraField['id']);
646
                            $stringValue = null;
647
                            if ($value) {
648
                                $stringValue = $value['field_value'];
649
                            }
650
                            $question[$extraField['field_variable']] = $stringValue;
651
                        }
652
                    }
653
                    $questions[] = $question;
654
                }
655
            }
656
            return $questions;
657
        }
658
    }
659
660
    /**
661
     * Get question count per exercise from DB (any special treatment)
662
     * @return int
663
     */
664 View Code Duplication
    public function getQuestionCount()
665
    {
666
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
667
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
668
        $sql = "SELECT count(q.id) as count
669
                FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
670
                    ON (e.question_id = q.id)
671
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= ".Database::escape_string($this->id);
672
        $result = Database::query($sql);
673
674
        $count = 0;
675
        if (Database::num_rows($result)) {
676
            $row = Database::fetch_array($result);
677
            $count = $row['count'];
678
        }
679
680
        return $count;
681
    }
682
683
    /**
684
     * @return array
685
     */
686 View Code Duplication
    public function getQuestionOrderedListByName()
687
    {
688
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
689
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
690
691
        // Getting question list from the order (question list drag n drop interface ).
692
        $sql = "SELECT e.question_id
693
                FROM $TBL_EXERCICE_QUESTION e 
694
                INNER JOIN $TBL_QUESTIONS q
695
                ON (e.question_id= q.id AND e.c_id = q.c_id)
696
                WHERE 
697
                    e.c_id = {$this->course_id} AND 
698
                    e.exercice_id	= '".Database::escape_string($this->id)."'
699
                ORDER BY q.question";
700
        $result = Database::query($sql);
701
        $list = array();
702
        if (Database::num_rows($result)) {
703
            $list = Database::store_result($result, 'ASSOC');
704
        }
705
        return $list;
706
    }
707
708
    /**
709
     * Gets the question list ordered by the question_order setting (drag and drop)
710
     * @return array
711
     */
712
    private function getQuestionOrderedList()
713
    {
714
        $questionList = array();
715
716
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
717
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
718
719
        // Getting question_order to verify that the question
720
        // list is correct and all question_order's were set
721
        $sql = "SELECT DISTINCT e.question_order
722
                FROM $TBL_EXERCICE_QUESTION e
723
                INNER JOIN $TBL_QUESTIONS q
724
                    ON (e.question_id = q.id)
725
                WHERE
726
                  e.c_id = {$this->course_id} AND
727
                  e.exercice_id	= ".Database::escape_string($this->id);
728
729
        $result = Database::query($sql);
730
731
        $count_question_orders = Database::num_rows($result);
732
733
        // Getting question list from the order (question list drag n drop interface ).
734
        $sql = "SELECT DISTINCT e.question_id, e.question_order
735
                FROM $TBL_EXERCICE_QUESTION e
736
                INNER JOIN $TBL_QUESTIONS q
737
                    ON (e.question_id= q.id)
738
                WHERE
739
                    e.c_id = {$this->course_id} AND
740
                    e.exercice_id	= '".Database::escape_string($this->id)."'
741
                ORDER BY question_order";
742
743
        $result = Database::query($sql);
744
745
        // Fills the array with the question ID for this exercise
746
        // the key of the array is the question position
747
        $temp_question_list = array();
748
749
        $counter = 1;
750
        while ($new_object = Database::fetch_object($result)) {
751
            // Correct order.
752
            $questionList[$new_object->question_order] = $new_object->question_id;
753
            // Just in case we save the order in other array
754
            $temp_question_list[$counter] = $new_object->question_id;
755
            $counter++;
756
        }
757
758
        if (!empty($temp_question_list)) {
759
            /* If both array don't match it means that question_order was not correctly set
760
               for all questions using the default mysql order */
761
            if (count($temp_question_list) != $count_question_orders) {
762
                $questionList = $temp_question_list;
763
            }
764
        }
765
766
        return $questionList;
767
    }
768
769
    /**
770
     * Select N values from the questions per category array
771
     *
772
     * @param array $categoriesAddedInExercise
773
     * @param array $question_list
774
     * @param array $questions_by_category per category
775
     * @param bool $flatResult
776
     * @param bool $randomizeQuestions
777
     *
778
     * @return array
779
     */
780
    private function pickQuestionsPerCategory(
781
        $categoriesAddedInExercise,
782
        $question_list,
783
        & $questions_by_category,
784
        $flatResult = true,
785
        $randomizeQuestions = false
786
    ) {
787
        $addAll = true;
788
        $categoryCountArray = array();
789
790
        // Getting how many questions will be selected per category.
791
        if (!empty($categoriesAddedInExercise)) {
792
            $addAll = false;
793
            // Parsing question according the category rel exercise settings
794
            foreach ($categoriesAddedInExercise as $category_info) {
795
                $category_id = $category_info['category_id'];
796
                if (isset($questions_by_category[$category_id])) {
797
                    // How many question will be picked from this category.
798
                    $count = $category_info['count_questions'];
799
                    // -1 means all questions
800
                    if ($count == -1) {
801
                        $categoryCountArray[$category_id] = 999;
802
                    } else {
803
                        $categoryCountArray[$category_id] = $count;
804
                    }
805
                }
806
            }
807
        }
808
809
        if (!empty($questions_by_category)) {
810
            $temp_question_list = array();
811
812
            foreach ($questions_by_category as $category_id => & $categoryQuestionList) {
813
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
814
                    if (isset($categoryCountArray[$category_id])) {
815
                        $numberOfQuestions = $categoryCountArray[$category_id];
816
                    } else {
817
                        $numberOfQuestions = 0;
818
                    }
819
                }
820
821
                if ($addAll) {
822
                    $numberOfQuestions = 999;
823
                }
824
825
                if (!empty($numberOfQuestions)) {
826
                    $elements = TestCategory::getNElementsFromArray(
827
                        $categoryQuestionList,
828
                        $numberOfQuestions,
829
                        $randomizeQuestions
830
                    );
831
832
                    if (!empty($elements)) {
833
                        $temp_question_list[$category_id] = $elements;
834
                        $categoryQuestionList = $elements;
835
                    }
836
                }
837
            }
838
839
            if (!empty($temp_question_list)) {
840
                if ($flatResult) {
841
                    $temp_question_list = array_flatten($temp_question_list);
842
                }
843
                $question_list = $temp_question_list;
844
            }
845
        }
846
847
        return $question_list;
848
    }
849
850
    /**
851
     * Selecting question list depending in the exercise-category
852
     * relationship (category table in exercise settings)
853
     *
854
     * @param array $question_list
855
     * @param int $questionSelectionType
856
     * @return array
857
     */
858
    public function getQuestionListWithCategoryListFilteredByCategorySettings($question_list, $questionSelectionType)
859
    {
860
        $result = array(
861
            'question_list' => array(),
862
            'category_with_questions_list' => array()
863
        );
864
865
        // Order/random categories
866
        $cat = new TestCategory();
867
868
        // Setting category order.
869
870
        switch ($questionSelectionType) {
871
            case EX_Q_SELECTION_ORDERED: // 1
872
            case EX_Q_SELECTION_RANDOM:  // 2
873
                // This options are not allowed here.
874
                break;
875 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
876
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
877
                    $this,
878
                    $this->course['real_id'],
879
                    'title ASC',
880
                    false,
881
                    true
882
                );
883
884
                $questions_by_category = TestCategory::getQuestionsByCat(
885
                    $this->id,
886
                    $question_list,
887
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...itle ASC', false, true) on line 876 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...
888
                );
889
890
891
                $question_list = $this->pickQuestionsPerCategory(
892
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...itle ASC', false, true) on line 876 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...
893
                    $question_list,
894
                    $questions_by_category,
895
                    true,
896
                    false
897
                );
898
                break;
899
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
900 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
901
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
902
                    $this,
903
                    $this->course['real_id'],
904
                    null,
905
                    true,
906
                    true
907
                );
908
                $questions_by_category = TestCategory::getQuestionsByCat(
909
                    $this->id,
910
                    $question_list,
911
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 901 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...
912
                );
913
                $question_list = $this->pickQuestionsPerCategory(
914
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 901 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...
915
                    $question_list,
916
                    $questions_by_category,
917
                    true,
918
                    false
919
                );
920
            break;
921 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
922
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
923
                    $this,
924
                    $this->course['real_id'],
925
                    'title DESC',
926
                    false,
927
                    true
928
                );
929
                $questions_by_category = TestCategory::getQuestionsByCat(
930
                    $this->id,
931
                    $question_list,
932
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 922 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...
933
                );
934
                $question_list = $this->pickQuestionsPerCategory(
935
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 922 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...
936
                    $question_list,
937
                    $questions_by_category,
938
                    true,
939
                    true
940
                );
941
                break;
942
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
943 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
944
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
945
                    $this,
946
                    $this->course['real_id'],
947
                    null,
948
                    true,
949
                    true
950
                );
951
                $questions_by_category = TestCategory::getQuestionsByCat(
952
                    $this->id,
953
                    $question_list,
954
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 944 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...
955
                );
956
                $question_list = $this->pickQuestionsPerCategory(
957
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 944 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...
958
                    $question_list,
959
                    $questions_by_category,
960
                    true,
961
                    true
962
                );
963
                break;
964
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
965
                break;
966
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
967
                break;
968 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
969
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
970
                    $this,
971
                    $this->course['real_id'],
972
                    'root ASC, lft ASC',
973
                    false,
974
                    true
975
                );
976
                $questions_by_category = TestCategory::getQuestionsByCat(
977
                    $this->id,
978
                    $question_list,
979
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 969 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...
980
                );
981
                $question_list = $this->pickQuestionsPerCategory(
982
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 969 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...
983
                    $question_list,
984
                    $questions_by_category,
985
                    true,
986
                    false
987
                );
988
                break;
989 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
990
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
991
                    $this,
992
                    $this->course['real_id'],
993
                    'root, lft ASC',
994
                    false,
995
                    true
996
                );
997
                $questions_by_category = TestCategory::getQuestionsByCat(
998
                    $this->id,
999
                    $question_list,
1000
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 990 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...
1001
                );
1002
                $question_list = $this->pickQuestionsPerCategory(
1003
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 990 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...
1004
                    $question_list,
1005
                    $questions_by_category,
1006
                    true,
1007
                    true
1008
                );
1009
                break;
1010
        }
1011
1012
        $result['question_list'] = isset($question_list) ? $question_list : array();
1013
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : array();
1014
1015
        // Adding category info in the category list with question list:
1016
1017
        if (!empty($questions_by_category)) {
1018
1019
            /*$em = Database::getManager();
1020
            $repo = $em->getRepository('ChamiloCoreBundle:CQuizCategory');*/
1021
1022
            $newCategoryList = array();
1023
1024
            foreach ($questions_by_category as $categoryId => $questionList) {
1025
                $cat = new TestCategory();
1026
                $cat = $cat->getCategory($categoryId);
1027
1028
                $cat = (array)$cat;
1029
                $cat['iid'] = $cat['id'];
1030
                //*$cat['name'] = $cat['name'];
1031
1032
                $categoryParentInfo = null;
1033
                // Parent is not set no loop here
1034
                if (!empty($cat['parent_id'])) {
1035
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
1036
                        $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...
1037
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
1038
                    } else {
1039
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
1040
                    }
1041
                    $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...
1042
                    $index = 0;
1043
                    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...
1044
                        //$index = 1;
1045
                    }
1046
                    /** @var \Chamilo\Entity\CQuizCategory $categoryParent*/
1047
1048
                    foreach ($path as $categoryParent) {
1049
                        $visibility = $categoryParent->getVisibility();
1050
1051
                        if ($visibility == 0) {
1052
                            $categoryParentId = $categoryId;
1053
                            $categoryTitle = $cat['title'];
1054
                            if (count($path) > 1) {
1055
                                continue;
1056
                            }
1057
                        } else {
1058
                            $categoryParentId = $categoryParent->getIid();
1059
                            $categoryTitle = $categoryParent->getTitle();
1060
                        }
1061
1062
                        $categoryParentInfo['id'] = $categoryParentId;
1063
                        $categoryParentInfo['iid'] = $categoryParentId;
1064
                        $categoryParentInfo['parent_path'] = null;
1065
                        $categoryParentInfo['title'] = $categoryTitle;
1066
                        $categoryParentInfo['name'] = $categoryTitle;
1067
                        $categoryParentInfo['parent_id'] = null;
1068
                        break;
1069
                    }
1070
                }
1071
                $cat['parent_info'] = $categoryParentInfo;
1072
                $newCategoryList[$categoryId] = array(
1073
                    'category' => $cat,
1074
                    'question_list' => $questionList
1075
                );
1076
            }
1077
1078
            $result['category_with_questions_list'] = $newCategoryList;
1079
        }
1080
        return $result;
1081
    }
1082
1083
    /**
1084
     * returns the array with the question ID list
1085
     *
1086
     * @author Olivier Brouckaert
1087
     * @return array - question ID list
1088
     */
1089
    public function selectQuestionList($from_db = false)
1090
    {
1091
        if ($from_db && !empty($this->id)) {
1092
            $nbQuestions = $this->getQuestionCount();
1093
            $questionSelectionType = $this->getQuestionSelectionType();
1094
1095
            switch ($questionSelectionType) {
1096
                case EX_Q_SELECTION_ORDERED:
1097
                    $questionList = $this->getQuestionOrderedList();
1098
                    break;
1099
                case EX_Q_SELECTION_RANDOM:
1100
                    // Not a random exercise, or if there are not at least 2 questions
1101
                    if ($this->random == 0 || $nbQuestions < 2) {
1102
                        $questionList = $this->getQuestionOrderedList();
1103
                    } else {
1104
                        $questionList = $this->selectRandomList();
1105
                    }
1106
                    break;
1107
                default:
1108
                    $questionList = $this->getQuestionOrderedList();
1109
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1110
                        $questionList,
1111
                        $questionSelectionType
1112
                    );
1113
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1114
                    $questionList = $result['question_list'];
1115
                    break;
1116
            }
1117
1118
            return $questionList;
1119
        }
1120
1121
        return $this->questionList;
1122
    }
1123
1124
    /**
1125
     * returns the number of questions in this exercise
1126
     *
1127
     * @author Olivier Brouckaert
1128
     * @return integer - number of questions
1129
     */
1130
    public function selectNbrQuestions()
1131
    {
1132
        return sizeof($this->questionList);
1133
    }
1134
1135
    /**
1136
     * @return int
1137
     */
1138
    public function selectPropagateNeg()
1139
    {
1140
        return $this->propagate_neg;
1141
    }
1142
1143
    /**
1144
     * @return int
1145
     */
1146
    public function selectSaveCorrectAnswers()
1147
    {
1148
        return $this->saveCorrectAnswers;
1149
    }
1150
1151
    /**
1152
     * Selects questions randomly in the question list
1153
     *
1154
     * @author Olivier Brouckaert
1155
     * @author Hubert Borderiou 15 nov 2011
1156
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1157
     *					 without randomizing, otherwise, returns the list with questions selected randomly
1158
     */
1159
    public function selectRandomList()
1160
    {
1161
        /*$nbQuestions	= $this->selectNbrQuestions();
1162
        $temp_list		= $this->questionList;
1163
1164
        //Not a random exercise, or if there are not at least 2 questions
1165
        if($this->random == 0 || $nbQuestions < 2) {
1166
            return $this->questionList;
1167
        }
1168
        if ($nbQuestions != 0) {
1169
            shuffle($temp_list);
1170
            $my_random_list = array_combine(range(1,$nbQuestions),$temp_list);
1171
            $my_question_list = array();
1172
            // $this->random == -1 if random with all questions
1173
            if ($this->random > 0) {
1174
                $i = 0;
1175
                foreach ($my_random_list as $item) {
1176
                    if ($i < $this->random) {
1177
                        $my_question_list[$i] = $item;
1178
                    } else {
1179
                        break;
1180
                    }
1181
                    $i++;
1182
                }
1183
            } else {
1184
                $my_question_list = $my_random_list;
1185
            }
1186
            return $my_question_list;
1187
        }*/
1188
1189
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1190
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1191
1192
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1193
1194
        $randomLimit = "LIMIT $random";
1195
        // Random all questions so no limit
1196
        if ($random == -1) {
1197
            $randomLimit = null;
1198
        }
1199
1200
        $sql = "SELECT e.question_id
1201
                FROM $TBL_EXERCISE_QUESTION e 
1202
                INNER JOIN $TBL_QUESTIONS q
1203
                ON (e.question_id= q.iid AND e.c_id = q.c_id)
1204
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
1205
                ORDER BY RAND()
1206
                $randomLimit ";
1207
        $result = Database::query($sql);
1208
        $questionList = array();
1209
        while ($row = Database::fetch_object($result)) {
1210
            $questionList[] = $row->question_id;
1211
        }
1212
1213
        return $questionList;
1214
    }
1215
1216
    /**
1217
     * returns 'true' if the question ID is in the question list
1218
     *
1219
     * @author Olivier Brouckaert
1220
     * @param integer $questionId - question ID
1221
     * @return boolean - true if in the list, otherwise false
1222
     */
1223
    public function isInList($questionId)
1224
    {
1225
        if (is_array($this->questionList)) {
1226
            return in_array($questionId, $this->questionList);
1227
        } else {
1228
            return false;
1229
        }
1230
    }
1231
1232
    /**
1233
     * changes the exercise title
1234
     *
1235
     * @author Olivier Brouckaert
1236
     * @param string $title - exercise title
1237
     */
1238
    public function updateTitle($title)
1239
    {
1240
        $this->exercise=$title;
1241
    }
1242
1243
    /**
1244
     * changes the exercise max attempts
1245
     *
1246
     * @param int $attempts - exercise max attempts
1247
     */
1248
    public function updateAttempts($attempts)
1249
    {
1250
        $this->attempts=$attempts;
1251
    }
1252
1253
    /**
1254
     * changes the exercise feedback type
1255
     *
1256
     * @param int $feedback_type
1257
     */
1258
    public function updateFeedbackType($feedback_type)
1259
    {
1260
        $this->feedback_type=$feedback_type;
1261
    }
1262
1263
    /**
1264
     * changes the exercise description
1265
     *
1266
     * @author Olivier Brouckaert
1267
     * @param string $description - exercise description
1268
     */
1269
    public function updateDescription($description)
1270
    {
1271
        $this->description=$description;
1272
    }
1273
1274
    /**
1275
     * changes the exercise expired_time
1276
     *
1277
     * @author Isaac flores
1278
     * @param int $expired_time The expired time of the quiz
1279
     */
1280
    public function updateExpiredTime($expired_time)
1281
    {
1282
        $this->expired_time = $expired_time;
1283
    }
1284
1285
    /**
1286
     * @param $value
1287
     */
1288
    public function updatePropagateNegative($value)
1289
    {
1290
        $this->propagate_neg = $value;
1291
    }
1292
1293
    /**
1294
     * @param $value int
1295
     */
1296
    public function updateSaveCorrectAnswers($value)
1297
    {
1298
        $this->saveCorrectAnswers = $value;
1299
    }
1300
1301
    /**
1302
     * @param $value
1303
     */
1304
    public function updateReviewAnswers($value)
1305
    {
1306
        $this->review_answers = isset($value) && $value ? true : false;
1307
    }
1308
1309
    /**
1310
     * @param $value
1311
     */
1312
    public function updatePassPercentage($value)
1313
    {
1314
        $this->pass_percentage = $value;
1315
    }
1316
1317
    /**
1318
     * @param string $text
1319
     */
1320
    public function updateEmailNotificationTemplate($text)
1321
    {
1322
        $this->emailNotificationTemplate = $text;
1323
    }
1324
1325
    /**
1326
     * @param string $text
1327
     */
1328
    public function updateEmailNotificationTemplateToUser($text)
1329
    {
1330
        $this->emailNotificationTemplateToUser = $text;
1331
    }
1332
1333
    /**
1334
     * @param string $value
1335
     */
1336
    public function setNotifyUserByEmail($value)
1337
    {
1338
        $this->notifyUserByEmail = $value;
1339
    }
1340
1341
    /**
1342
     * @param int $value
1343
     */
1344
    public function updateEndButton($value)
1345
    {
1346
        $this->endButton = intval($value);
1347
    }
1348
1349
    /**
1350
     * @param string $value
1351
     */
1352
    public function setOnSuccessMessage($value)
1353
    {
1354
        $this->onSuccessMessage = $value;
1355
    }
1356
1357
    /**
1358
     * @param string $value
1359
     */
1360
    public function setOnFailedMessage($value)
1361
    {
1362
        $this->onFailedMessage = $value;
1363
    }
1364
1365
    /**
1366
     * @param $value
1367
     */
1368
    public function setModelType($value)
1369
    {
1370
        $this->modelType = intval($value);
1371
    }
1372
1373
    /**
1374
     * @param int $value
1375
     */
1376
    public function setQuestionSelectionType($value)
1377
    {
1378
        $this->questionSelectionType = intval($value);
1379
    }
1380
1381
    /**
1382
     * @return int
1383
     */
1384
    public function getQuestionSelectionType()
1385
    {
1386
        return $this->questionSelectionType;
1387
    }
1388
1389
    /**
1390
     * @param array $categories
1391
     */
1392
    public function updateCategories($categories)
1393
    {
1394
        if (!empty($categories)) {
1395
            $categories = array_map('intval', $categories);
1396
            $this->categories = $categories;
1397
        }
1398
    }
1399
1400
    /**
1401
     * changes the exercise sound file
1402
     *
1403
     * @author Olivier Brouckaert
1404
     * @param string $sound - exercise sound file
1405
     * @param string $delete - ask to delete the file
1406
     */
1407
    public function updateSound($sound,$delete)
1408
    {
1409
        global $audioPath, $documentPath;
1410
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1411
1412
        if ($sound['size'] && (strstr($sound['type'],'audio') || strstr($sound['type'],'video'))) {
1413
            $this->sound=$sound['name'];
1414
1415
            if (@move_uploaded_file($sound['tmp_name'],$audioPath.'/'.$this->sound)) {
1416
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1417
                        WHERE c_id = ".$this->course_id." AND path='".str_replace($documentPath,'',$audioPath).'/'.$this->sound."'";
1418
                $result = Database::query($sql);
1419
1420
                if (!Database::num_rows($result)) {
1421
                    $id = add_document(
1422
                        $this->course,
1423
                        str_replace($documentPath,'',$audioPath).'/'.$this->sound,
1424
                        'file',
1425
                        $sound['size'],
1426
                        $sound['name']
1427
                    );
1428
                    api_item_property_update(
1429
                        $this->course,
1430
                        TOOL_DOCUMENT,
1431
                        $id,
1432
                        'DocumentAdded',
1433
                        api_get_user_id()
1434
                    );
1435
                    item_property_update_on_folder(
1436
                        $this->course,
1437
                        str_replace($documentPath, '', $audioPath),
1438
                        api_get_user_id()
1439
                    );
1440
                }
1441
            }
1442
        } elseif($delete && is_file($audioPath.'/'.$this->sound)) {
1443
            $this->sound='';
1444
        }
1445
    }
1446
1447
    /**
1448
     * changes the exercise type
1449
     *
1450
     * @author Olivier Brouckaert
1451
     * @param integer $type - exercise type
1452
     */
1453
    public function updateType($type)
1454
    {
1455
        $this->type = $type;
1456
    }
1457
1458
    /**
1459
     * sets to 0 if questions are not selected randomly
1460
     * if questions are selected randomly, sets the draws
1461
     *
1462
     * @author Olivier Brouckaert
1463
     * @param integer $random - 0 if not random, otherwise the draws
1464
     */
1465
    public function setRandom($random)
1466
    {
1467
        /*if ($random == 'all') {
1468
            $random = $this->selectNbrQuestions();
1469
        }*/
1470
        $this->random = $random;
1471
    }
1472
1473
    /**
1474
     * sets to 0 if answers are not selected randomly
1475
     * if answers are selected randomly
1476
     * @author Juan Carlos Rana
1477
     * @param integer $random_answers - random answers
1478
     */
1479
    public function updateRandomAnswers($random_answers)
1480
    {
1481
        $this->random_answers = $random_answers;
1482
    }
1483
1484
    /**
1485
     * enables the exercise
1486
     *
1487
     * @author Olivier Brouckaert
1488
     */
1489
    public function enable()
1490
    {
1491
        $this->active=1;
1492
    }
1493
1494
    /**
1495
     * disables the exercise
1496
     *
1497
     * @author Olivier Brouckaert
1498
     */
1499
    public function disable()
1500
    {
1501
        $this->active=0;
1502
    }
1503
1504
    /**
1505
     * Set disable results
1506
     */
1507
    public function disable_results()
1508
    {
1509
        $this->results_disabled = true;
1510
    }
1511
1512
    /**
1513
     * Enable results
1514
     */
1515
    public function enable_results()
1516
    {
1517
        $this->results_disabled = false;
1518
    }
1519
1520
    /**
1521
     * @param int $results_disabled
1522
     */
1523
    public function updateResultsDisabled($results_disabled)
1524
    {
1525
        $this->results_disabled = intval($results_disabled);
1526
    }
1527
1528
    /**
1529
     * updates the exercise in the data base
1530
     *
1531
     * @author Olivier Brouckaert
1532
     */
1533
    public function save($type_e = '')
1534
    {
1535
        $_course = $this->course;
1536
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1537
1538
        $id = $this->id;
1539
        $exercise = $this->exercise;
1540
        $description = $this->description;
1541
        $sound = $this->sound;
1542
        $type = $this->type;
1543
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1544
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1545
        $random = $this->random;
1546
        $random_answers = $this->random_answers;
1547
        $active = $this->active;
1548
        $propagate_neg = (int) $this->propagate_neg;
1549
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0;
1550
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1551
        $randomByCat = intval($this->randomByCat);
1552
        $text_when_finished = $this->text_when_finished;
1553
        $display_category_name = intval($this->display_category_name);
1554
        $pass_percentage = intval($this->pass_percentage);
1555
        $session_id = $this->sessionId;
1556
1557
        //If direct we do not show results
1558
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1559
            $results_disabled = 0;
1560
        } else {
1561
            $results_disabled = intval($this->results_disabled);
1562
        }
1563
1564
        $expired_time = intval($this->expired_time);
1565
1566
        // Exercise already exists
1567
        if ($id) {
1568
            // we prepare date in the database using the api_get_utc_datetime() function
1569
            if (!empty($this->start_time)) {
1570
                $start_time = $this->start_time;
1571
            } else {
1572
                $start_time = null;
1573
            }
1574
1575
            if (!empty($this->end_time)) {
1576
                $end_time = $this->end_time;
1577
            } else {
1578
                $end_time = null;
1579
            }
1580
1581
            $params = [
1582
                'title' => $exercise,
1583
                'description' => $description,
1584
            ];
1585
1586
            $paramsExtra = [];
1587
            if ($type_e != 'simple') {
1588
                $paramsExtra = [
1589
                    'sound' => $sound,
1590
                    'type' => $type,
1591
                    'random' => $random,
1592
                    'random_answers' => $random_answers,
1593
                    'active' => $active,
1594
                    'feedback_type' => $feedback_type,
1595
                    'start_time' => $start_time,
1596
                    'end_time' => $end_time,
1597
                    'max_attempt' => $attempts,
1598
                    'expired_time' => $expired_time,
1599
                    'propagate_neg' => $propagate_neg,
1600
                    'save_correct_answers' => $saveCorrectAnswers,
1601
                    'review_answers' => $review_answers,
1602
                    'random_by_category' => $randomByCat,
1603
                    'text_when_finished' => $text_when_finished,
1604
                    'display_category_name' => $display_category_name,
1605
                    'pass_percentage' => $pass_percentage,
1606
                    'results_disabled' => $results_disabled,
1607
                    'question_selection_type' => $this->getQuestionSelectionType(),
1608
                    'hide_question_title' => $this->getHideQuestionTitle()
1609
                ];
1610
            }
1611
1612
            $params = array_merge($params, $paramsExtra);
1613
1614
            Database::update(
1615
                $TBL_EXERCISES,
1616
                $params,
1617
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1618
            );
1619
1620
            // update into the item_property table
1621
            api_item_property_update(
1622
                $_course,
1623
                TOOL_QUIZ,
1624
                $id,
1625
                'QuizUpdated',
1626
                api_get_user_id()
1627
            );
1628
1629
            if (api_get_setting('search_enabled')=='true') {
1630
                $this->search_engine_edit();
1631
            }
1632
        } else {
1633
            // Creates a new exercise
1634
1635
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1636
            // for date because, bellow, we call function api_set_default_visibility()
1637
            // In this function, api_set_default_visibility,
1638
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1639
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1640
            if (!empty($this->start_time)) {
1641
                $start_time = $this->start_time;
1642
            } else {
1643
                $start_time = null;
1644
            }
1645
1646
            if (!empty($this->end_time)) {
1647
                $end_time = $this->end_time;
1648
            } else {
1649
                $end_time = null;
1650
            }
1651
1652
            $params = [
1653
                'c_id' => $this->course_id,
1654
                'start_time' => $start_time,
1655
                'end_time' => $end_time,
1656
                'title' => $exercise,
1657
                'description' => $description,
1658
                'sound' => $sound,
1659
                'type' => $type,
1660
                'random' => $random,
1661
                'random_answers' => $random_answers,
1662
                'active' => $active,
1663
                'results_disabled' => $results_disabled,
1664
                'max_attempt' => $attempts,
1665
                'feedback_type' => $feedback_type,
1666
                'expired_time' => $expired_time,
1667
                'session_id' => $session_id,
1668
                'review_answers' => $review_answers,
1669
                'random_by_category' => $randomByCat,
1670
                'text_when_finished' => $text_when_finished,
1671
                'display_category_name' => $display_category_name,
1672
                'pass_percentage' => $pass_percentage,
1673
                'save_correct_answers' => (int) $saveCorrectAnswers,
1674
                'propagate_neg' => $propagate_neg,
1675
                'hide_question_title' => $this->getHideQuestionTitle()
1676
            ];
1677
1678
            $this->id = Database::insert($TBL_EXERCISES, $params);
1679
1680
            if ($this->id) {
1681
1682
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1683
                Database::query($sql);
1684
1685
                $sql = "UPDATE $TBL_EXERCISES
1686
                        SET question_selection_type= ".intval($this->getQuestionSelectionType())."
1687
                        WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1688
                Database::query($sql);
1689
1690
                // insert into the item_property table
1691
                api_item_property_update(
1692
                    $this->course,
1693
                    TOOL_QUIZ,
1694
                    $this->id,
1695
                    'QuizAdded',
1696
                    api_get_user_id()
1697
                );
1698
1699
                // This function save the quiz again, carefull about start_time
1700
                // and end_time if you remove this line (see above)
1701
                api_set_default_visibility(
1702
                    $this->id,
1703
                    TOOL_QUIZ,
1704
                    null,
1705
                    $this->course
1706
                );
1707
1708
                if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1709
                    $this->search_engine_save();
1710
                }
1711
            }
1712
        }
1713
1714
        $this->save_categories_in_exercise($this->categories);
1715
1716
        // Updates the question position
1717
        $this->update_question_positions();
1718
    }
1719
1720
    /**
1721
     * Updates question position
1722
     */
1723
    public function update_question_positions()
1724
    {
1725
        $quiz_question_table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1726
        //Fixes #3483 when updating order
1727
        $question_list = $this->selectQuestionList(true);
1728
        if (!empty($question_list)) {
1729
            foreach ($question_list as $position => $questionId) {
1730
                $sql = "UPDATE $quiz_question_table SET
1731
                        question_order ='".intval($position)."'
1732
                        WHERE
1733
                            c_id = ".$this->course_id." AND
1734
                            question_id = ".intval($questionId)." AND
1735
                            exercice_id=".intval($this->id);
1736
                Database::query($sql);
1737
            }
1738
        }
1739
    }
1740
1741
    /**
1742
     * Adds a question into the question list
1743
     *
1744
     * @author Olivier Brouckaert
1745
     * @param integer $questionId - question ID
1746
     * @return boolean - true if the question has been added, otherwise false
1747
     */
1748
    public function addToList($questionId)
1749
    {
1750
        // checks if the question ID is not in the list
1751
        if (!$this->isInList($questionId)) {
1752
            // selects the max position
1753
            if (!$this->selectNbrQuestions()) {
1754
                $pos = 1;
1755
            } else {
1756
                if (is_array($this->questionList)) {
1757
                    $pos = max(array_keys($this->questionList)) + 1;
1758
                }
1759
            }
1760
            $this->questionList[$pos] = $questionId;
1761
1762
            return true;
1763
        }
1764
1765
        return false;
1766
    }
1767
1768
    /**
1769
     * removes a question from the question list
1770
     *
1771
     * @author Olivier Brouckaert
1772
     * @param integer $questionId - question ID
1773
     * @return boolean - true if the question has been removed, otherwise false
1774
     */
1775
    public function removeFromList($questionId)
1776
    {
1777
        // searches the position of the question ID in the list
1778
        $pos = array_search($questionId,$this->questionList);
1779
1780
        // question not found
1781
        if ($pos === false) {
1782
            return false;
1783
        } else {
1784
            // dont reduce the number of random question if we use random by category option, or if
1785
            // random all questions
1786
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1787
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1788
                    $this->random -= 1;
1789
                    $this->save();
1790
                }
1791
            }
1792
            // deletes the position from the array containing the wanted question ID
1793
            unset($this->questionList[$pos]);
1794
1795
            return true;
1796
        }
1797
    }
1798
1799
    /**
1800
     * deletes the exercise from the database
1801
     * Notice : leaves the question in the data base
1802
     *
1803
     * @author Olivier Brouckaert
1804
     */
1805
    public function delete()
1806
    {
1807
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1808
        $sql = "UPDATE $TBL_EXERCISES SET active='-1'
1809
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
1810
        Database::query($sql);
1811
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'QuizDeleted', api_get_user_id());
1812
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'delete', api_get_user_id());
1813
1814
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian') ) {
1815
            $this->search_engine_delete();
1816
        }
1817
    }
1818
1819
    /**
1820
     * Creates the form to create / edit an exercise
1821
     * @param FormValidator $form
1822
     */
1823
    public function createForm($form, $type='full')
1824
    {
1825
        if (empty($type)) {
1826
            $type = 'full';
1827
        }
1828
1829
        // form title
1830
        if (!empty($_GET['exerciseId'])) {
1831
            $form_title = get_lang('ModifyExercise');
1832
        } else {
1833
            $form_title = get_lang('NewEx');
1834
        }
1835
1836
        $form->addElement('header', $form_title);
1837
1838
        // Title.
1839
        $form->addElement(
1840
            'text',
1841
            'exerciseTitle',
1842
            get_lang('ExerciseName'),
1843
            array('id' => 'exercise_title')
1844
        );
1845
1846
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1847
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1848
1849
        $editor_config = array(
1850
            'ToolbarSet' => 'TestQuestionDescription',
1851
            'Width' => '100%',
1852
            'Height' => '150',
1853
        );
1854
        if (is_array($type)){
1855
            $editor_config = array_merge($editor_config, $type);
1856
        }
1857
1858
        $form->addHtmlEditor(
1859
            'exerciseDescription',
1860
            get_lang('ExerciseDescription'),
1861
            false,
1862
            false,
1863
            $editor_config
1864
        );
1865
1866
        if ($type == 'full') {
1867
            //Can't modify a DirectFeedback question
1868
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1869
                // feedback type
1870
                $radios_feedback = array();
1871
                $radios_feedback[] = $form->createElement(
1872
                    'radio',
1873
                    'exerciseFeedbackType',
1874
                    null,
1875
                    get_lang('ExerciseAtTheEndOfTheTest'),
1876
                    '0',
1877
                    array(
1878
                        'id' => 'exerciseType_0',
1879
                        'onclick' => 'check_feedback()',
1880
                    )
1881
                );
1882
1883 View Code Duplication
                if (api_get_setting('enable_quiz_scenario') == 'true') {
1884
                    //Can't convert a question from one feedback to another if there is more than 1 question already added
1885
                    if ($this->selectNbrQuestions() == 0) {
1886
                        $radios_feedback[] = $form->createElement(
1887
                            'radio',
1888
                            'exerciseFeedbackType',
1889
                            null,
1890
                            get_lang('DirectFeedback'),
1891
                            '1',
1892
                            array(
1893
                                'id' => 'exerciseType_1',
1894
                                'onclick' => 'check_direct_feedback()',
1895
                            )
1896
                        );
1897
                    }
1898
                }
1899
1900
                $radios_feedback[] = $form->createElement(
1901
                    'radio',
1902
                    'exerciseFeedbackType',
1903
                    null,
1904
                    get_lang('NoFeedback'),
1905
                    '2',
1906
                    array('id' => 'exerciseType_2')
1907
                );
1908
                $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
1909
1910
                // Type of results display on the final page
1911
                $radios_results_disabled = array();
1912
                $radios_results_disabled[] = $form->createElement(
1913
                    'radio',
1914
                    'results_disabled',
1915
                    null,
1916
                    get_lang('ShowScoreAndRightAnswer'),
1917
                    '0',
1918
                    array('id' => 'result_disabled_0')
1919
                );
1920
                $radios_results_disabled[] = $form->createElement(
1921
                    'radio',
1922
                    'results_disabled',
1923
                    null,
1924
                    get_lang('DoNotShowScoreNorRightAnswer'),
1925
                    '1',
1926
                    array('id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()')
1927
                );
1928
                $radios_results_disabled[] = $form->createElement(
1929
                    'radio',
1930
                    'results_disabled',
1931
                    null,
1932
                    get_lang('OnlyShowScore'),
1933
                    '2',
1934
                    array('id' => 'result_disabled_2')
1935
                );
1936
1937
1938
                $radios_results_disabled[] = $form->createElement(
1939
                    'radio',
1940
                    'results_disabled',
1941
                    null,
1942
                    get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
1943
                    '4',
1944
                    array('id' => 'result_disabled_4')
1945
                );
1946
1947
                $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'));
1948
1949
                // Type of questions disposition on page
1950
                $radios = array();
1951
1952
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1953
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1954
1955
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
1956
1957
            } else {
1958
                // if is Directfeedback but has not questions we can allow to modify the question type
1959
                if ($this->selectNbrQuestions() == 0) {
1960
1961
                    // feedback type
1962
                    $radios_feedback = array();
1963
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('ExerciseAtTheEndOfTheTest'),'0',array('id' =>'exerciseType_0', 'onclick' => 'check_feedback()'));
1964
1965 View Code Duplication
                    if (api_get_setting('enable_quiz_scenario') == 'true') {
1966
                        $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('DirectFeedback'), '1', array('id' =>'exerciseType_1' , 'onclick' => 'check_direct_feedback()'));
1967
                    }
1968
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('NoFeedback'),'2',array('id' =>'exerciseType_2'));
1969
                    $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
1970
1971
                    //$form->addElement('select', 'exerciseFeedbackType',get_lang('FeedbackType'),$feedback_option,'onchange="javascript:feedbackselection()"');
1972
                    $radios_results_disabled = array();
1973
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1974
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
1975
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
1976
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
1977
1978
                    // Type of questions disposition on page
1979
                    $radios = array();
1980
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1');
1981
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2');
1982
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
1983
1984
                } else {
1985
                    //Show options freeze
1986
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1987
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
1988
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
1989
                    $result_disable_group = $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'));
1990
                    $result_disable_group->freeze();
1991
1992
                    //we force the options to the DirectFeedback exercisetype
1993
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
1994
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
1995
1996
                    // Type of questions disposition on page
1997
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1998
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1999
2000
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
2001
                    $type_group->freeze();
2002
                }
2003
            }
2004
2005
            if (true) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
2006
                $option = array(
2007
                    EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2008
                    //  defined by user
2009
                    EX_Q_SELECTION_RANDOM => get_lang('Random'),
2010
                    // 1-10, All
2011
                    'per_categories' => '--------'.get_lang(
2012
                            'UsingCategories'
2013
                        ).'----------',
2014
2015
                    // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2016
                    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang(
2017
                        'OrderedCategoriesAlphabeticallyWithQuestionsOrdered'
2018
                    ),
2019
                    // A 123 B 456 C 78 (0, 1, all)
2020
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang(
2021
                        'RandomCategoriesWithQuestionsOrdered'
2022
                    ),
2023
                    // C 78 B 456 A 123
2024
2025
                    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang(
2026
                        'OrderedCategoriesAlphabeticallyWithRandomQuestions'
2027
                    ),
2028
                    // A 321 B 654 C 87
2029
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang(
2030
                        'RandomCategoriesWithRandomQuestions'
2031
                    ),
2032
                    //C 87 B 654 A 321
2033
2034
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2035
                    /*    B 456 C 78 A 123
2036
                            456 78 123
2037
                            123 456 78
2038
                    */
2039
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2040
                    /*
2041
                        A 123 B 456 C 78
2042
                        B 456 C 78 A 123
2043
                        B 654 C 87 A 321
2044
                        654 87 321
2045
                        165 842 73
2046
                    */
2047
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2048
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2049
                );
2050
2051
                $form->addElement(
2052
                    'select',
2053
                    'question_selection_type',
2054
                    array(get_lang('QuestionSelection')),
2055
                    $option,
2056
                    array(
2057
                        'id' => 'questionSelection',
2058
                        'onclick' => 'checkQuestionSelection()'
2059
                    )
2060
                );
2061
2062
                $displayMatrix = 'none';
2063
                $displayRandom = 'none';
2064
                $selectionType = $this->getQuestionSelectionType();
2065
                switch ($selectionType) {
2066
                    case EX_Q_SELECTION_RANDOM:
2067
                        $displayRandom = 'block';
2068
                        break;
2069
                    case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2070
                        $displayMatrix = 'block';
2071
                        break;
2072
                }
2073
2074
                $form->addElement(
2075
                    'html',
2076
                    '<div id="hidden_random" style="display:'.$displayRandom.'">'
2077
                );
2078
                // Number of random question.
2079
                $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
2080
                $option = range(0, $max);
2081
                $option[0] = get_lang('No');
2082
                $option[-1] = get_lang('AllQuestionsShort');
2083
                $form->addElement(
2084
                    'select',
2085
                    'randomQuestions',
2086
                    array(
2087
                        get_lang('RandomQuestions'),
2088
                        get_lang('RandomQuestionsHelp')
2089
                    ),
2090
                    $option,
2091
                    array('id' => 'randomQuestions')
2092
                );
2093
                $form->addElement('html', '</div>');
2094
2095
                $form->addElement(
2096
                    'html',
2097
                    '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2098
                );
2099
2100
                // Category selection.
2101
                $cat = new TestCategory();
2102
                $cat_form = $cat->returnCategoryForm($this);
2103
                $form->addElement('label', null, $cat_form);
2104
                $form->addElement('html', '</div>');
2105
2106
                // Category name.
2107
                $radio_display_cat_name = array(
2108
                    $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2109
                    $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0')
2110
                );
2111
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'));
2112
2113
                // Random answers.
2114
                $radios_random_answers = array(
2115
                    $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2116
                    $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0')
2117
                );
2118
                $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'));
2119
2120
                // Hide question title.
2121
                $group = array(
2122
                    $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2123
                    $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0')
2124
                );
2125
                $form->addGroup($group, null, get_lang('HideQuestionTitle'));
2126
            } 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...
2127
2128
                // number of random question
2129
                /*
2130
                $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10 ;
2131
                $option = range(0, $max);
2132
                $option[0] = get_lang('No');
2133
                $option[-1] = get_lang('AllQuestionsShort');
2134
                $form->addElement('select', 'randomQuestions',array(get_lang('RandomQuestions'), get_lang('RandomQuestionsHelp')), $option, array('id'=>'randomQuestions'));
2135
2136
                // Random answers
2137
                $radios_random_answers = array();
2138
                $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'),'1');
2139
                $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('No'),'0');
2140
                $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'), '');
2141
2142
                // Random by category
2143
                $form->addElement('html','<div class="clear">&nbsp;</div>');
2144
                $radiocat = array();
2145
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesShuffled'),'1');
2146
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesSorted'),'2');
2147
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('No'),'0');
2148
                $radioCatGroup = $form->addGroup($radiocat, null, get_lang('RandomQuestionByCategory'), '');
2149
                $form->addElement('html','<div class="clear">&nbsp;</div>');
2150
2151
                // add the radio display the category name for student
2152
                $radio_display_cat_name = array();
2153
                $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1');
2154
                $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0');
2155
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'), '');*/
2156
            }
2157
            // Attempts
2158
            $attempt_option = range(0, 10);
2159
            $attempt_option[0] = get_lang('Infinite');
2160
2161
            $form->addElement(
2162
                'select',
2163
                'exerciseAttempts',
2164
                get_lang('ExerciseAttempts'),
2165
                $attempt_option,
2166
                ['id' => 'exerciseAttempts']
2167
            );
2168
2169
            // Exercise time limit
2170
            $form->addElement('checkbox', 'activate_start_date_check',null, get_lang('EnableStartTime'), array('onclick' => 'activate_start_date()'));
2171
2172
            $var = Exercise::selectTimeLimit();
2173
2174
            if (!empty($this->start_time)) {
2175
                $form->addElement('html', '<div id="start_date_div" style="display:block;">');
2176
            } else {
2177
                $form->addElement('html', '<div id="start_date_div" style="display:none;">');
2178
            }
2179
2180
            $form->addElement('date_time_picker', 'start_time');
2181
2182
            $form->addElement('html','</div>');
2183
2184
            $form->addElement('checkbox', 'activate_end_date_check', null , get_lang('EnableEndTime'), array('onclick' => 'activate_end_date()'));
2185
2186
            if (!empty($this->end_time)) {
2187
                $form->addElement('html', '<div id="end_date_div" style="display:block;">');
2188
            } else {
2189
                $form->addElement('html', '<div id="end_date_div" style="display:none;">');
2190
            }
2191
2192
            $form->addElement('date_time_picker', 'end_time');
2193
            $form->addElement('html','</div>');
2194
2195
            //$check_option=$this->selectType();
2196
            $diplay = 'block';
2197
            $form->addElement('checkbox', 'propagate_neg', null, get_lang('PropagateNegativeResults'));
2198
            $form->addCheckBox(
2199
                'save_correct_answers',
2200
                null,
2201
                get_lang('SaveTheCorrectAnswersForTheNextAttempt')
2202
            );
2203
            $form->addElement('html','<div class="clear">&nbsp;</div>');
2204
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2205
2206
            $form->addElement('html','<div id="divtimecontrol"  style="display:'.$diplay.';">');
2207
2208
            //Timer control
2209
            //$time_hours_option = range(0,12);
2210
            //$time_minutes_option = range(0,59);
2211
            $form->addElement(
2212
                'checkbox',
2213
                'enabletimercontrol',
2214
                null,
2215
                get_lang('EnableTimerControl'),
2216
                array(
2217
                    'onclick' => 'option_time_expired()',
2218
                    'id' => 'enabletimercontrol',
2219
                    'onload' => 'check_load_time()',
2220
                )
2221
            );
2222
2223
            $expired_date = (int)$this->selectExpiredTime();
2224
2225
            if (($expired_date!='0')) {
2226
                $form->addElement('html','<div id="timercontrol" style="display:block;">');
2227
            } else {
2228
                $form->addElement('html','<div id="timercontrol" style="display:none;">');
2229
            }
2230
            $form->addText(
2231
                'enabletimercontroltotalminutes',
2232
                get_lang('ExerciseTotalDurationInMinutes'),
2233
                false,
2234
                [
2235
                    'id' => 'enabletimercontroltotalminutes',
2236
                    'cols-size' => [2, 2, 8]
2237
                ]
2238
            );
2239
            $form->addElement('html','</div>');
2240
2241
            $form->addElement(
2242
                'text',
2243
                'pass_percentage',
2244
                array(get_lang('PassPercentage'), null, '%'),
2245
                array('id' => 'pass_percentage')
2246
            );
2247
2248
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2249
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2250
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2251
2252
            // add the text_when_finished textbox
2253
            $form->addHtmlEditor(
2254
                'text_when_finished',
2255
                get_lang('TextWhenFinished'),
2256
                false,
2257
                false,
2258
                $editor_config
2259
            );
2260
2261
            $defaults = array();
2262
2263
            if (api_get_setting('search_enabled') === 'true') {
2264
                require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2265
2266
                $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument'));
2267
                $form->addElement('select_language', 'language', get_lang('SearchFeatureDocumentLanguage'));
2268
2269
                $specific_fields = get_specific_field_list();
2270
2271
                foreach ($specific_fields as $specific_field) {
2272
                    $form->addElement ('text', $specific_field['code'], $specific_field['name']);
2273
                    $filter = array(
2274
                        'c_id' => api_get_course_int_id(),
2275
                        'field_id' => $specific_field['id'],
2276
                        'ref_id' => $this->id,
2277
                        'tool_id' => "'" . TOOL_QUIZ . "'"
2278
                    );
2279
                    $values = get_specific_field_values_list($filter, array('value'));
2280
                    if ( !empty($values) ) {
2281
                        $arr_str_values = array();
2282
                        foreach ($values as $value) {
2283
                            $arr_str_values[] = $value['value'];
2284
                        }
2285
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2286
                    }
2287
                }
2288
            }
2289
2290
            $form->addElement('html','</div>');  //End advanced setting
2291
            $form->addElement('html','</div>');
2292
        }
2293
2294
        // submit
2295
        if (isset($_GET['exerciseId'])) {
2296
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2297
        } else {
2298
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2299
        }
2300
2301
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2302
2303
        if ($type == 'full') {
2304
            // rules
2305
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2306
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2307
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2308
        }
2309
2310
        // defaults
2311
        if ($type=='full') {
2312
            if ($this->id > 0) {
2313
                if ($this->random > $this->selectNbrQuestions()) {
2314
                    $defaults['randomQuestions'] =  $this->selectNbrQuestions();
2315
                } else {
2316
                    $defaults['randomQuestions'] = $this->random;
2317
                }
2318
2319
                $defaults['randomAnswers'] = $this->selectRandomAnswers();
2320
                $defaults['exerciseType'] = $this->selectType();
2321
                $defaults['exerciseTitle'] = $this->get_formated_title();
2322
                $defaults['exerciseDescription'] = $this->selectDescription();
2323
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2324
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2325
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2326
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2327
                $defaults['save_correct_answers'] = $this->selectSaveCorrectAnswers();
2328
                $defaults['review_answers'] = $this->review_answers;
2329
                $defaults['randomByCat'] = $this->selectRandomByCat();
2330
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2331
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2332
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2333
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2334
                $defaults['hide_question_title'] = $this->getHideQuestionTitle();
2335
2336
                if (!empty($this->start_time)) {
2337
                    $defaults['activate_start_date_check'] = 1;
2338
                }
2339
                if (!empty($this->end_time)) {
2340
                    $defaults['activate_end_date_check'] = 1;
2341
                }
2342
2343
                $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2344
                $defaults['end_time'] = empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time()+84600);
2345
2346
                // Get expired time
2347
                if ($this->expired_time != '0') {
2348
                    $defaults['enabletimercontrol'] = 1;
2349
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2350
                } else {
2351
                    $defaults['enabletimercontroltotalminutes'] = 0;
2352
                }
2353
            } else {
2354
                $defaults['exerciseType'] = 2;
2355
                $defaults['exerciseAttempts'] = 0;
2356
                $defaults['randomQuestions'] = 0;
2357
                $defaults['randomAnswers'] = 0;
2358
                $defaults['exerciseDescription'] = '';
2359
                $defaults['exerciseFeedbackType'] = 0;
2360
                $defaults['results_disabled'] = 0;
2361
                $defaults['randomByCat'] = 0;
2362
                $defaults['text_when_finished'] = '';
2363
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2364
                $defaults['display_category_name'] = 1;
2365
                $defaults['end_time']   = date('Y-m-d 12:00:00', time()+84600);
2366
                $defaults['pass_percentage'] = '';
2367
                $defaults['end_button'] = $this->selectEndButton();
2368
                $defaults['question_selection_type'] = 1;
2369
                $defaults['hide_question_title'] = 0;
2370
                $defaults['on_success_message'] = null;
2371
                $defaults['on_failed_message'] = null;
2372
            }
2373
        } else {
2374
            $defaults['exerciseTitle'] = $this->selectTitle();
2375
            $defaults['exerciseDescription'] = $this->selectDescription();
2376
        }
2377
        if (api_get_setting('search_enabled') === 'true') {
2378
            $defaults['index_document'] = 'checked="checked"';
2379
        }
2380
        $form->setDefaults($defaults);
2381
2382
        // Freeze some elements.
2383
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2384
            $elementsToFreeze = array(
2385
                'randomQuestions',
2386
                //'randomByCat',
2387
                'exerciseAttempts',
2388
                'propagate_neg',
2389
                'enabletimercontrol',
2390
                'review_answers'
2391
            );
2392
2393
            foreach ($elementsToFreeze as $elementName) {
2394
                /** @var HTML_QuickForm_element $element */
2395
                $element = $form->getElement($elementName);
2396
                $element->freeze();
2397
            }
2398
2399
            //$radioCatGroup->freeze();
2400
        }
2401
    }
2402
2403
    /**
2404
     * function which process the creation of exercises
2405
     * @param FormValidator $form
2406
     * @param string
2407
     */
2408
    public function processCreation($form, $type = '')
2409
    {
2410
        $this->updateTitle(Exercise::format_title_variable($form->getSubmitValue('exerciseTitle')));
2411
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2412
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2413
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2414
        $this->updateType($form->getSubmitValue('exerciseType'));
2415
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2416
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2417
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2418
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2419
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2420
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2421
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2422
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2423
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2424
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2425
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2426
        $this->updateCategories($form->getSubmitValue('category'));
2427
        $this->updateEndButton($form->getSubmitValue('end_button'));
2428
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2429
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2430
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2431
        $this->updateEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2432
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2433
        $this->setModelType($form->getSubmitValue('model_type'));
2434
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2435
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2436
        $this->sessionId = api_get_session_id();
2437
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2438
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2439
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2440
2441
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2442
            $start_time = $form->getSubmitValue('start_time');
2443
            $this->start_time = api_get_utc_datetime($start_time);
2444
        } else {
2445
            $this->start_time = null;
2446
        }
2447
2448
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2449
            $end_time = $form->getSubmitValue('end_time');
2450
            $this->end_time = api_get_utc_datetime($end_time);
2451
        } else {
2452
            $this->end_time = null;
2453
        }
2454
2455
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2456
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2457
            if ($this->expired_time == 0) {
2458
                $this->expired_time = $expired_total_time;
2459
            }
2460
        } else {
2461
            $this->expired_time = 0;
2462
        }
2463
2464
        if ($form->getSubmitValue('randomAnswers') == 1) {
2465
            $this->random_answers=1;
2466
        } else {
2467
            $this->random_answers=0;
2468
        }
2469
        $this->save($type);
2470
    }
2471
2472
    function search_engine_save()
2473
    {
2474
        if ($_POST['index_document'] != 1) {
2475
            return;
2476
        }
2477
        $course_id = api_get_course_id();
2478
2479
        require_once api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php';
2480
        require_once api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php';
2481
        require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2482
2483
        $specific_fields = get_specific_field_list();
2484
        $ic_slide = new IndexableChunk();
2485
2486
        $all_specific_terms = '';
2487 View Code Duplication
        foreach ($specific_fields as $specific_field) {
2488
            if (isset($_REQUEST[$specific_field['code']])) {
2489
                $sterms = trim($_REQUEST[$specific_field['code']]);
2490
                if (!empty($sterms)) {
2491
                    $all_specific_terms .= ' '. $sterms;
2492
                    $sterms = explode(',', $sterms);
2493
                    foreach ($sterms as $sterm) {
2494
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2495
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2496
                    }
2497
                }
2498
            }
2499
        }
2500
2501
        // build the chunk to index
2502
        $ic_slide->addValue("title", $this->exercise);
2503
        $ic_slide->addCourseId($course_id);
2504
        $ic_slide->addToolId(TOOL_QUIZ);
2505
        $xapian_data = array(
2506
            SE_COURSE_ID => $course_id,
2507
            SE_TOOL_ID => TOOL_QUIZ,
2508
            SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2509
            SE_USER => (int)api_get_user_id(),
2510
        );
2511
        $ic_slide->xapian_data = serialize($xapian_data);
2512
        $exercise_description = $all_specific_terms .' '. $this->description;
2513
        $ic_slide->addValue("content", $exercise_description);
2514
2515
        $di = new ChamiloIndexer();
2516
        isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2517
        $di->connectDb(NULL, NULL, $lang);
2518
        $di->addChunk($ic_slide);
2519
2520
        //index and return search engine document id
2521
        $did = $di->index();
2522
        if ($did) {
2523
            // save it to db
2524
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2525
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2526
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2527
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2528
            Database::query($sql);
2529
        }
2530
    }
2531
2532
    function search_engine_edit()
2533
    {
2534
        // update search enchine and its values table if enabled
2535
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian')) {
2536
            $course_id = api_get_course_id();
2537
2538
            // actually, it consists on delete terms from db,
2539
            // insert new ones, create a new search engine document, and remove the old one
2540
            // get search_did
2541
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2542
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2543
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2544
            $res = Database::query($sql);
2545
2546
            if (Database::num_rows($res) > 0) {
2547
                require_once(api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php');
2548
                require_once(api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php');
2549
                require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
2550
2551
                $se_ref = Database::fetch_array($res);
2552
                $specific_fields = get_specific_field_list();
2553
                $ic_slide = new IndexableChunk();
2554
2555
                $all_specific_terms = '';
2556
                foreach ($specific_fields as $specific_field) {
2557
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2558
                    if (isset($_REQUEST[$specific_field['code']])) {
2559
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2560
                        $all_specific_terms .= ' '. $sterms;
2561
                        $sterms = explode(',', $sterms);
2562
                        foreach ($sterms as $sterm) {
2563
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2564
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2565
                        }
2566
                    }
2567
                }
2568
2569
                // build the chunk to index
2570
                $ic_slide->addValue("title", $this->exercise);
2571
                $ic_slide->addCourseId($course_id);
2572
                $ic_slide->addToolId(TOOL_QUIZ);
2573
                $xapian_data = array(
2574
                    SE_COURSE_ID => $course_id,
2575
                    SE_TOOL_ID => TOOL_QUIZ,
2576
                    SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2577
                    SE_USER => (int)api_get_user_id(),
2578
                );
2579
                $ic_slide->xapian_data = serialize($xapian_data);
2580
                $exercise_description = $all_specific_terms .' '. $this->description;
2581
                $ic_slide->addValue("content", $exercise_description);
2582
2583
                $di = new ChamiloIndexer();
2584
                isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2585
                $di->connectDb(NULL, NULL, $lang);
2586
                $di->remove_document((int)$se_ref['search_did']);
2587
                $di->addChunk($ic_slide);
2588
2589
                //index and return search engine document id
2590
                $did = $di->index();
2591
                if ($did) {
2592
                    // save it to db
2593
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2594
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2595
                    Database::query($sql);
2596
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2597
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2598
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2599
                    Database::query($sql);
2600
                }
2601
            } else {
2602
                $this->search_engine_save();
2603
            }
2604
        }
2605
2606
    }
2607
2608
    function search_engine_delete()
2609
    {
2610
        // remove from search engine if enabled
2611
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian') ) {
2612
            $course_id = api_get_course_id();
2613
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2614
            $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';
2615
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2616
            $res = Database::query($sql);
2617
            if (Database::num_rows($res) > 0) {
2618
                $row = Database::fetch_array($res);
2619
                require_once(api_get_path(LIBRARY_PATH) .'search/ChamiloIndexer.class.php');
2620
                $di = new ChamiloIndexer();
2621
                $di->remove_document((int)$row['search_did']);
2622
                unset($di);
2623
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2624
                foreach ( $this->questionList as $question_i) {
2625
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2626
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2627
                    $qres = Database::query($sql);
2628
                    if (Database::num_rows($qres) > 0) {
2629
                        $qrow = Database::fetch_array($qres);
2630
                        $objQuestion = Question::getInstance($qrow['type']);
2631
                        $objQuestion = Question::read((int)$question_i);
2632
                        $objQuestion->search_engine_edit($this->id, FALSE, TRUE);
2633
                        unset($objQuestion);
2634
                    }
2635
                }
2636
            }
2637
            $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';
2638
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2639
            Database::query($sql);
2640
2641
            // remove terms from db
2642
            require_once api_get_path(LIBRARY_PATH) .'specific_fields_manager.lib.php';
2643
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2644
        }
2645
    }
2646
2647
    public function selectExpiredTime()
2648
    {
2649
        return $this->expired_time;
2650
    }
2651
2652
    /**
2653
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2654
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2655
     * Works with exercises in sessions
2656
     * @param bool $cleanLpTests
2657
     * @param string $cleanResultBeforeDate
2658
     *
2659
     * @return int quantity of user's exercises deleted
2660
     */
2661
    public function clean_results($cleanLpTests = false, $cleanResultBeforeDate = null)
2662
    {
2663
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2664
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2665
2666
        $sql_where = '  AND
2667
                        orig_lp_id = 0 AND
2668
                        orig_lp_item_id = 0';
2669
2670
        // if we want to delete results from LP too
2671
        if ($cleanLpTests) {
2672
            $sql_where = "";
2673
        }
2674
2675
        // if we want to delete attempts before date $cleanResultBeforeDate
2676
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2677
2678
        if (!empty($cleanResultBeforeDate)) {
2679
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2680
            if (api_is_valid_date($cleanResultBeforeDate)) {
2681
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2682
            } else {
2683
                return 0;
2684
            }
2685
        }
2686
2687
        $sql = "SELECT exe_id
2688
                FROM $table_track_e_exercises
2689
                WHERE
2690
                    c_id = ".api_get_course_int_id()." AND
2691
                    exe_exo_id = ".$this->id." AND
2692
                    session_id = ".api_get_session_id()." ".
2693
                    $sql_where;
2694
2695
        $result   = Database::query($sql);
2696
        $exe_list = Database::store_result($result);
2697
2698
        // deleting TRACK_E_ATTEMPT table
2699
        // check if exe in learning path or not
2700
        $i = 0;
2701
        if (is_array($exe_list) && count($exe_list) > 0) {
2702
            foreach ($exe_list as $item) {
2703
                $sql = "DELETE FROM $table_track_e_attempt
2704
                        WHERE exe_id = '".$item['exe_id']."'";
2705
                Database::query($sql);
2706
                $i++;
2707
            }
2708
        }
2709
2710
        $session_id = api_get_session_id();
2711
        // delete TRACK_E_EXERCISES table
2712
        $sql = "DELETE FROM $table_track_e_exercises
2713
                WHERE c_id = ".api_get_course_int_id()."
2714
                AND exe_exo_id = ".$this->id."
2715
                $sql_where
2716
                AND session_id = ".$session_id."";
2717
        Database::query($sql);
2718
2719
        Event::addEvent(
2720
            LOG_EXERCISE_RESULT_DELETE,
2721
            LOG_EXERCISE_ID,
2722
            $this->id,
2723
            null,
2724
            null,
2725
            api_get_course_int_id(),
2726
            $session_id
2727
        );
2728
2729
        return $i;
2730
    }
2731
2732
    /**
2733
     * Copies an exercise (duplicate all questions and answers)
2734
     */
2735
    public function copy_exercise()
2736
    {
2737
        $exercise_obj = $this;
2738
2739
        // force the creation of a new exercise
2740
        $exercise_obj->updateTitle($exercise_obj->selectTitle().' - '.get_lang('Copy'));
2741
        //Hides the new exercise
2742
        $exercise_obj->updateStatus(false);
2743
        $exercise_obj->updateId(0);
2744
        $exercise_obj->save();
2745
2746
        $new_exercise_id = $exercise_obj->selectId();
2747
        $question_list 	 = $exercise_obj->selectQuestionList();
2748
2749
        if (!empty($question_list)) {
2750
            //Question creation
2751
2752
            foreach ($question_list as $old_question_id) {
2753
                $old_question_obj = Question::read($old_question_id);
2754
                $new_id = $old_question_obj->duplicate();
2755
                if ($new_id) {
2756
                    $new_question_obj = Question::read($new_id);
2757
2758
                    if (isset($new_question_obj) && $new_question_obj) {
2759
                        $new_question_obj->addToList($new_exercise_id);
2760
                        // This should be moved to the duplicate function
2761
                        $new_answer_obj = new Answer($old_question_id);
2762
                        $new_answer_obj->read();
2763
                        $new_answer_obj->duplicate($new_id);
2764
                    }
2765
                }
2766
            }
2767
        }
2768
    }
2769
2770
    /**
2771
     * Changes the exercise id
2772
     *
2773
     * @param int $id - exercise id
2774
     */
2775
    private function updateId($id)
2776
    {
2777
        $this->id = $id;
2778
    }
2779
2780
    /**
2781
     * Changes the exercise status
2782
     *
2783
     * @param string $status - exercise status
2784
     */
2785
    function updateStatus($status)
2786
    {
2787
        $this->active = $status;
2788
    }
2789
2790
    /**
2791
     * @param int $lp_id
2792
     * @param int $lp_item_id
2793
     * @param int $lp_item_view_id
2794
     * @param string $status
2795
     * @return array
2796
     */
2797
    public function get_stat_track_exercise_info(
2798
        $lp_id = 0,
2799
        $lp_item_id = 0,
2800
        $lp_item_view_id = 0,
2801
        $status = 'incomplete'
2802
    ) {
2803
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2804
        if (empty($lp_id)) {
2805
            $lp_id = 0;
2806
        }
2807
        if (empty($lp_item_id)) {
2808
            $lp_item_id   = 0;
2809
        }
2810
        if (empty($lp_item_view_id)) {
2811
            $lp_item_view_id = 0;
2812
        }
2813
        $condition = ' WHERE exe_exo_id 	= ' . "'" . $this->id . "'" .' AND
2814
					   exe_user_id 			= ' . "'" . api_get_user_id() . "'" . ' AND
2815
					   c_id                 = ' . api_get_course_int_id() . ' AND
2816
					   status 				= ' . "'" . Database::escape_string($status). "'" . ' AND
2817
					   orig_lp_id 			= ' . "'" . $lp_id . "'" . ' AND
2818
					   orig_lp_item_id 		= ' . "'" . $lp_item_id . "'" . ' AND
2819
                       orig_lp_item_view_id = ' . "'" . $lp_item_view_id . "'" . ' AND
2820
					   session_id 			= ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case
2821
2822
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2823
2824
        $result = Database::query($sql_track);
2825
        $new_array = array();
2826
        if (Database::num_rows($result) > 0 ) {
2827
            $new_array = Database::fetch_array($result, 'ASSOC');
2828
            $new_array['num_exe'] = Database::num_rows($result);
2829
        }
2830
2831
        return $new_array;
2832
    }
2833
2834
    /**
2835
     * Saves a test attempt
2836
     *
2837
     * @param int  clock_expired_time
2838
     * @param int  int lp id
2839
     * @param int  int lp item id
2840
     * @param int  int lp item_view id
2841
     * @param float $weight
2842
     * @param array question list
2843
     */
2844
    public function save_stat_track_exercise_info(
2845
        $clock_expired_time = 0,
2846
        $safe_lp_id = 0,
2847
        $safe_lp_item_id = 0,
2848
        $safe_lp_item_view_id = 0,
2849
        $questionList = array(),
2850
        $weight = 0
2851
    ) {
2852
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2853
        $safe_lp_id = intval($safe_lp_id);
2854
        $safe_lp_item_id = intval($safe_lp_item_id);
2855
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
2856
2857
        if (empty($safe_lp_id)) {
2858
            $safe_lp_id = 0;
2859
        }
2860
        if (empty($safe_lp_item_id)) {
2861
            $safe_lp_item_id = 0;
2862
        }
2863
        if (empty($clock_expired_time)) {
2864
            $clock_expired_time = null;
2865
        }
2866
2867
        $questionList = array_map('intval', $questionList);
2868
2869
        $params = array(
2870
            'exe_exo_id' => $this->id ,
2871
            'exe_user_id' => api_get_user_id(),
2872
            'c_id' => api_get_course_int_id(),
2873
            'status' =>  'incomplete',
2874
            'session_id'  => api_get_session_id(),
2875
            'data_tracking'  => implode(',', $questionList) ,
2876
            'start_date' => api_get_utc_datetime(),
2877
            'orig_lp_id' => $safe_lp_id,
2878
            'orig_lp_item_id'  => $safe_lp_item_id,
2879
            'orig_lp_item_view_id'  => $safe_lp_item_view_id,
2880
            'exe_weighting'=> $weight,
2881
            'user_ip' => api_get_real_ip(),
2882
            'exe_date' => api_get_utc_datetime(),
2883
            'exe_result' => 0,
2884
            'steps_counter' => 0,
2885
            'exe_duration' => 0,
2886
            'expired_time_control' => $clock_expired_time,
2887
            'questions_to_check' => ''
2888
        );
2889
2890
        $id = Database::insert($track_exercises, $params);
2891
2892
        return $id;
2893
    }
2894
2895
    /**
2896
     * @param int $question_id
2897
     * @param int $questionNum
2898
     * @param array $questions_in_media
2899
     * @param string $currentAnswer
2900
     * @return string
2901
     */
2902
    public function show_button($question_id, $questionNum, $questions_in_media = array(), $currentAnswer = '')
2903
    {
2904
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
2905
2906
        $nbrQuestions = $this->get_count_question_list();
2907
2908
        $all_button = $html = $label = '';
2909
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']):null;
2910
2911
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
2912
            $urlTitle = get_lang('ContinueTest');
2913
2914
            if ($questionNum == count($this->questionList)) {
2915
                $urlTitle = get_lang('EndTest');
2916
            }
2917
2918
            $html .= Display::url(
2919
                $urlTitle,
2920
                'exercise_submit_modal.php?' . http_build_query([
2921
                    'learnpath_id' => $safe_lp_id,
2922
                    'learnpath_item_id' => $safe_lp_item_id,
2923
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
2924
                    'origin' => $origin,
2925
                    'hotspot' => $hotspot_get,
2926
                    'nbrQuestions' => $nbrQuestions,
2927
                    'num' => $questionNum,
2928
                    'exerciseType' => $this->type,
2929
                    'exerciseId' => $this->id
2930
                ]),
2931
                [
2932
                    'class' => 'ajax btn btn-default',
2933
                    'data-title' => $urlTitle,
2934
                    'data-size' => 'md'
2935
                ]
2936
            );
2937
            $html .='<br />';
2938
        } else {
2939
            // User
2940
            if (api_is_allowed_to_session_edit()) {
2941
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum) {
2942 View Code Duplication
                    if ($this->review_answers) {
2943
                        $label = get_lang('ReviewQuestions');
2944
                        $class = 'btn btn-success';
2945
                    } else {
2946
                        $label = get_lang('EndTest');
2947
                        $class = 'btn btn-warning';
2948
                    }
2949
                } else {
2950
                    $label = get_lang('NextQuestion');
2951
                    $class = 'btn btn-primary';
2952
                }
2953
				$class .= ' question-validate-btn'; // used to select it with jquery
2954
                if ($this->type == ONE_PER_PAGE) {
2955
                    if ($questionNum != 1) {
2956
                        $prev_question = $questionNum - 2;
2957
                        $all_button .= '<a href="javascript://" class="btn btn-default" onclick="previous_question_and_save('.$prev_question.', '.$question_id.' ); ">'.get_lang('PreviousQuestion').'</a>';
2958
                    }
2959
2960
                    //Next question
2961
                    if (!empty($questions_in_media)) {
2962
                        $questions_in_media = "['".implode("','",$questions_in_media)."']";
2963
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_question_list('.$questions_in_media.'); ">'.$label.'</a>';
2964
                    } else {
2965
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_now('.$question_id.', \'\', \''.$currentAnswer.'\'); ">'.$label.'</a>';
2966
                    }
2967
                    $all_button .= '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
2968
2969
                    $html .= $all_button;
2970
                } else {
2971 View Code Duplication
                    if ($this->review_answers) {
2972
                        $all_label = get_lang('ReviewQuestions');
2973
                        $class = 'btn btn-success';
2974
                    } else {
2975
                        $all_label = get_lang('EndTest');
2976
                        $class = 'btn btn-warning';
2977
                    }
2978
					$class .= ' question-validate-btn'; // used to select it with jquery
2979
                    $all_button = '&nbsp;<a href="javascript://" class="'.$class.'" onclick="validate_all(); ">'.$all_label.'</a>';
2980
                    $all_button .= '&nbsp;' . Display::span(null, ['id' => 'save_all_reponse']);
2981
                    $html .= $all_button;
2982
                }
2983
            }
2984
        }
2985
2986
        return $html;
2987
    }
2988
2989
    /**
2990
     * So the time control will work
2991
     *
2992
     * @param string $time_left
2993
     * @return string
2994
     */
2995
    public function show_time_control_js($time_left)
2996
    {
2997
        $time_left = intval($time_left);
2998
        return "<script>
2999
3000
            function get_expired_date_string(expired_time) {
3001
                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
3002
                var day, month, year, hours, minutes, seconds, date_string;
3003
                var obj_date = new Date(expired_time);
3004
                day     = obj_date.getDate();
3005
                if (day < 10) day = '0' + day;
3006
                    month   = obj_date.getMonth();
3007
                    year    = obj_date.getFullYear();
3008
                    hours   = obj_date.getHours();
3009
                if (hours < 10) hours = '0' + hours;
3010
                minutes = obj_date.getMinutes();
3011
                if (minutes < 10) minutes = '0' + minutes;
3012
                seconds = obj_date.getSeconds();
3013
                if (seconds < 10) seconds = '0' + seconds;
3014
                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
3015
                return date_string;
3016
            }
3017
3018
            function open_clock_warning() {
3019
                $('#clock_warning').dialog({
3020
                    modal:true,
3021
                    height:250,
3022
                    closeOnEscape: false,
3023
                    resizable: false,
3024
                    buttons: {
3025
                        '".addslashes(get_lang("EndTest"))."': function() {
3026
                            $('#clock_warning').dialog('close');
3027
                        }
3028
                    },
3029
                    close: function() {
3030
                        send_form();
3031
                    }
3032
                });
3033
                $('#clock_warning').dialog('open');
3034
3035
                $('#counter_to_redirect').epiclock({
3036
                    mode: $.epiclock.modes.countdown,
3037
                    offset: {seconds: 5},
3038
                    format: 's'
3039
                }).bind('timer', function () {
3040
                    send_form();
3041
                });
3042
3043
            }
3044
3045
            function send_form() {
3046
                if ($('#exercise_form').length) {
3047
                    $('#exercise_form').submit();
3048
                } else {
3049
                    //In reminder
3050
                    final_submit();
3051
                }
3052
            }
3053
3054
            function onExpiredTimeExercise() {
3055
                $('#wrapper-clock').hide();
3056
                $('#exercise_form').hide();
3057
                $('#expired-message-id').show();
3058
3059
                //Fixes bug #5263
3060
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3061
                open_clock_warning();
3062
            }
3063
3064
			$(document).ready(function() {
3065
3066
				var current_time = new Date().getTime();
3067
                var time_left    = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
3068
				var expired_time = current_time + (time_left*1000);
3069
				var expired_date = get_expired_date_string(expired_time);
3070
3071
                $('#exercise_clock_warning').epiclock({
3072
                    mode: $.epiclock.modes.countdown,
3073
                    offset: {seconds: time_left},
3074
                    format: 'x:i:s',
3075
                    renderer: 'minute'
3076
                }).bind('timer', function () {
3077
                    onExpiredTimeExercise();
3078
                });
3079
	       		$('#submit_save').click(function () {});
3080
	    });
3081
	    </script>";
3082
    }
3083
3084
    /**
3085
     * Lp javascript for hotspots
3086
     */
3087
    public function show_lp_javascript()
3088
    {
3089
        return '';
3090
    }
3091
3092
    /**
3093
     * This function was originally found in the exercise_show.php
3094
     * @param int       $exeId
3095
     * @param int       $questionId
3096
     * @param int       $choice the user selected
3097
     * @param string    $from  function is called from 'exercise_show' or 'exercise_result'
3098
     * @param array     $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates
3099
     * @param bool      $saved_results save results in the DB or just show the reponse
3100
     * @param bool      $from_database gets information from DB or from the current selection
3101
     * @param bool      $show_result show results or not
3102
     * @param int       $propagate_neg
3103
     * @param array     $hotspot_delineation_result
3104
     * @param boolean $showTotalScoreAndUserChoicesInLastAttempt
3105
     * @todo    reduce parameters of this function
3106
     * @return  string  html code
3107
     */
3108
    public function manage_answer(
3109
        $exeId,
3110
        $questionId,
3111
        $choice,
3112
        $from = 'exercise_show',
3113
        $exerciseResultCoordinates = array(),
3114
        $saved_results = true,
3115
        $from_database = false,
3116
        $show_result = true,
3117
        $propagate_neg = 0,
3118
        $hotspot_delineation_result = array(),
3119
        $showTotalScoreAndUserChoicesInLastAttempt = true
3120
    ) {
3121
        global $debug;
3122
        //needed in order to use in the exercise_attempt() for the time
3123
        global $learnpath_id, $learnpath_item_id;
3124
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3125
3126
        $em = Database::getManager();
3127
3128
        $feedback_type = $this->selectFeedbackType();
3129
        $results_disabled = $this->selectResultsDisabled();
3130
3131
        if ($debug) {
3132
            error_log("<------ manage_answer ------> ");
3133
            error_log('exe_id: '.$exeId);
3134
            error_log('$from:  '.$from);
3135
            error_log('$saved_results: '.intval($saved_results));
3136
            error_log('$from_database: '.intval($from_database));
3137
            error_log('$show_result: '.$show_result);
3138
            error_log('$propagate_neg: '.$propagate_neg);
3139
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3140
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3141
            error_log('$learnpath_id: '.$learnpath_id);
3142
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3143
            error_log('$choice: '.print_r($choice, 1));
3144
        }
3145
3146
        $extra_data = array();
3147
        $final_overlap = 0;
3148
        $final_missing = 0;
3149
        $final_excess = 0;
3150
        $overlap_color = 0;
3151
        $missing_color = 0;
3152
        $excess_color = 0;
3153
        $threadhold1 = 0;
3154
        $threadhold2 = 0;
3155
        $threadhold3 = 0;
3156
3157
        $arrques = null;
3158
        $arrans  = null;
3159
3160
        $questionId = intval($questionId);
3161
        $exeId = intval($exeId);
3162
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3163
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3164
3165
        // Creates a temporary Question object
3166
        $course_id = $this->course_id;
3167
        $objQuestionTmp = Question::read($questionId, $course_id);
3168
3169
        if ($objQuestionTmp === false) {
3170
            return false;
3171
        }
3172
3173
        $questionName = $objQuestionTmp->selectTitle();
3174
        $questionWeighting = $objQuestionTmp->selectWeighting();
3175
        $answerType = $objQuestionTmp->selectType();
3176
        $quesId = $objQuestionTmp->selectId();
3177
        $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...
3178
3179
        $next = 1; //not for now
3180
3181
        // Extra information of the question
3182
        if (!empty($extra)) {
3183
            $extra = explode(':', $extra);
3184
            if ($debug) {
3185
                error_log(print_r($extra, 1));
3186
            }
3187
            // Fixes problems with negatives values using intval
3188
            $true_score = floatval(trim($extra[0]));
3189
            $false_score = floatval(trim($extra[1]));
3190
            $doubt_score = floatval(trim($extra[2]));
3191
        }
3192
3193
        $totalWeighting = 0;
3194
        $totalScore = 0;
3195
3196
        // Construction of the Answer object
3197
        $objAnswerTmp = new Answer($questionId);
3198
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3199
3200
        if ($debug) {
3201
            error_log('Count of answers: '.$nbrAnswers);
3202
            error_log('$answerType: '.$answerType);
3203
        }
3204
3205
        if ($answerType == FREE_ANSWER ||
3206
            $answerType == ORAL_EXPRESSION ||
3207
            $answerType == CALCULATED_ANSWER
3208
        ) {
3209
            $nbrAnswers = 1;
3210
        }
3211
3212
        if ($answerType == ORAL_EXPRESSION) {
3213
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3214
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3215
3216
            $objQuestionTmp->initFile(
3217
                api_get_session_id(),
3218
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3219
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3220
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3221
            );
3222
3223
            //probably this attempt came in an exercise all question by page
3224
            if ($feedback_type == 0) {
3225
                $objQuestionTmp->replaceWithRealExe($exeId);
3226
            }
3227
        }
3228
3229
        $user_answer = '';
3230
3231
        // Get answer list for matching
3232
        $sql = "SELECT id_auto, id, answer
3233
                FROM $table_ans
3234
                WHERE c_id = $course_id AND question_id = $questionId";
3235
        $res_answer = Database::query($sql);
3236
3237
        $answerMatching = array();
3238
        while ($real_answer = Database::fetch_array($res_answer)) {
3239
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3240
        }
3241
3242
        $real_answers = array();
3243
        $quiz_question_options = Question::readQuestionOption(
3244
            $questionId,
3245
            $course_id
3246
        );
3247
3248
        $organs_at_risk_hit = 0;
3249
        $questionScore = 0;
3250
        $answer_correct_array = array();
3251
        $orderedHotspots = [];
3252
3253
        if ($answerType == HOT_SPOT) {
3254
            $orderedHotspots = $em
3255
                ->getRepository('ChamiloCoreBundle:TrackEHotspot')
3256
                ->findBy([
3257
                        'hotspotQuestionId' => $questionId,
3258
                        'cId' => $course_id,
3259
                        'hotspotExeId' => $exeId
3260
                    ],
3261
                    ['hotspotId' => 'ASC']
3262
                );
3263
        }
3264
3265
        if ($debug) error_log('Start answer loop ');
3266
3267
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3268
            $answer = $objAnswerTmp->selectAnswer($answerId);
3269
            $answerComment = $objAnswerTmp->selectComment($answerId);
3270
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3271
            $answerWeighting = (float)$objAnswerTmp->selectWeighting($answerId);
3272
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3273
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? $objAnswerTmp->iid[$answerId] : '';
3274
3275
            $answer_correct_array[$answerId] = (bool)$answerCorrect;
3276
3277
            if ($debug) {
3278
                error_log("answer auto id: $answerAutoId ");
3279
                error_log("answer correct: $answerCorrect ");
3280
            }
3281
3282
            // Delineation
3283
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3284
            $answer_delineation_destination=$objAnswerTmp->selectDestination(1);
3285
3286
            switch ($answerType) {
3287
                // for unique answer
3288
                case UNIQUE_ANSWER:
3289
                case UNIQUE_ANSWER_IMAGE:
3290
                case UNIQUE_ANSWER_NO_OPTION:
3291
                    if ($from_database) {
3292
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3293
                                WHERE
3294
                                    exe_id = '".$exeId."' AND
3295
                                    question_id= '".$questionId."'";
3296
                        $result = Database::query($sql);
3297
                        $choice = Database::result($result,0,"answer");
3298
3299
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3300
                        if ($studentChoice) {
3301
                            $questionScore += $answerWeighting;
3302
                            $totalScore += $answerWeighting;
3303
                        }
3304
                    } else {
3305
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3306
                        if ($studentChoice) {
3307
                            $questionScore += $answerWeighting;
3308
                            $totalScore += $answerWeighting;
3309
                        }
3310
                    }
3311
                    break;
3312
                // for multiple answers
3313
                case MULTIPLE_ANSWER_TRUE_FALSE:
3314
                    if ($from_database) {
3315
                        $choice = array();
3316
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3317
                                WHERE
3318
                                    exe_id = $exeId AND
3319
                                    question_id = ".$questionId;
3320
3321
                        $result = Database::query($sql);
3322 View Code Duplication
                        while ($row = Database::fetch_array($result)) {
3323
                            $ind = $row['answer'];
3324
                            $values = explode(':', $ind);
3325
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3326
                            $option = isset($values[1]) ? $values[1] : '';
3327
                            $choice[$my_answer_id] = $option;
3328
                        }
3329
                    }
3330
3331
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3332
3333
                    if (!empty($studentChoice)) {
3334
                        if ($studentChoice == $answerCorrect) {
3335
                            $questionScore += $true_score;
3336
                        } else {
3337
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3338
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3339
                            ) {
3340
                                $questionScore += $doubt_score;
3341
                            } else {
3342
                                $questionScore += $false_score;
3343
                            }
3344
                        }
3345
                    } else {
3346
                        // If no result then the user just hit don't know
3347
                        $studentChoice = 3;
3348
                        $questionScore  +=  $doubt_score;
3349
                    }
3350
                    $totalScore = $questionScore;
3351
                    break;
3352 View Code Duplication
                case MULTIPLE_ANSWER: //2
3353
                    if ($from_database) {
3354
                        $choice = array();
3355
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3356
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3357
                        $resultans = Database::query($sql);
3358
                        while ($row = Database::fetch_array($resultans)) {
3359
                            $ind = $row['answer'];
3360
                            $choice[$ind] = 1;
3361
                        }
3362
3363
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3364
                        $real_answers[$answerId] = (bool)$studentChoice;
3365
3366
                        if ($studentChoice) {
3367
                            $questionScore  +=$answerWeighting;
3368
                        }
3369
                    } else {
3370
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3371
                        $real_answers[$answerId] = (bool)$studentChoice;
3372
3373
                        if (isset($studentChoice)) {
3374
                            $questionScore  += $answerWeighting;
3375
                        }
3376
                    }
3377
                    $totalScore += $answerWeighting;
3378
3379
                    if ($debug) error_log("studentChoice: $studentChoice");
3380
                    break;
3381 View Code Duplication
                case GLOBAL_MULTIPLE_ANSWER:
3382
                    if ($from_database) {
3383
                        $choice = array();
3384
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3385
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3386
                        $resultans = Database::query($sql);
3387
                        while ($row = Database::fetch_array($resultans)) {
3388
                            $ind = $row['answer'];
3389
                            $choice[$ind] = 1;
3390
                        }
3391
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3392
                        $real_answers[$answerId] = (bool)$studentChoice;
3393
                        if ($studentChoice) {
3394
                            $questionScore +=$answerWeighting;
3395
                        }
3396
                    } else {
3397
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3398
                        if (isset($studentChoice)) {
3399
                            $questionScore += $answerWeighting;
3400
                        }
3401
                        $real_answers[$answerId] = (bool)$studentChoice;
3402
                    }
3403
                    $totalScore += $answerWeighting;
3404
                    if ($debug) error_log("studentChoice: $studentChoice");
3405
                    break;
3406
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3407
                    if ($from_database) {
3408
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3409
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3410
                        $resultans = Database::query($sql);
3411 View Code Duplication
                        while ($row = Database::fetch_array($resultans)) {
3412
                            $ind = $row['answer'];
3413
                            $result = explode(':',$ind);
3414
                            if (isset($result[0])) {
3415
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3416
                                $option = isset($result[1]) ? $result[1] : '';
3417
                                $choice[$my_answer_id] = $option;
3418
                            }
3419
                        }
3420
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3421
3422
                        if ($answerCorrect == $studentChoice) {
3423
                            //$answerCorrect = 1;
3424
                            $real_answers[$answerId] = true;
3425
                        } else {
3426
                            //$answerCorrect = 0;
3427
                            $real_answers[$answerId] = false;
3428
                        }
3429
                    } else {
3430
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3431
                        if ($answerCorrect == $studentChoice) {
3432
                            //$answerCorrect = 1;
3433
                            $real_answers[$answerId] = true;
3434
                        } else {
3435
                            //$answerCorrect = 0;
3436
                            $real_answers[$answerId] = false;
3437
                        }
3438
                    }
3439
                    break;
3440
                case MULTIPLE_ANSWER_COMBINATION:
3441
                    if ($from_database) {
3442
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3443
                                WHERE exe_id = $exeId AND question_id= $questionId";
3444
                        $resultans = Database::query($sql);
3445
                        while ($row = Database::fetch_array($resultans)) {
3446
                            $ind = $row['answer'];
3447
                            $choice[$ind] = 1;
3448
                        }
3449
3450
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3451
3452 View Code Duplication
                        if ($answerCorrect == 1) {
3453
                            if ($studentChoice) {
3454
                                $real_answers[$answerId] = true;
3455
                            } else {
3456
                                $real_answers[$answerId] = false;
3457
                            }
3458
                        } else {
3459
                            if ($studentChoice) {
3460
                                $real_answers[$answerId] = false;
3461
                            } else {
3462
                                $real_answers[$answerId] = true;
3463
                            }
3464
                        }
3465
                    } else {
3466
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3467
3468 View Code Duplication
                        if ($answerCorrect == 1) {
3469
                            if ($studentChoice) {
3470
                                $real_answers[$answerId] = true;
3471
                            } else {
3472
                                $real_answers[$answerId] = false;
3473
                            }
3474
                        } else {
3475
                            if ($studentChoice) {
3476
                                $real_answers[$answerId] = false;
3477
                            } else {
3478
                                $real_answers[$answerId] = true;
3479
                            }
3480
                        }
3481
                    }
3482
                    break;
3483
                case FILL_IN_BLANKS:
3484
                    $str = '';
3485
                    if ($from_database) {
3486
                        $sql = "SELECT answer
3487
                                    FROM $TBL_TRACK_ATTEMPT
3488
                                    WHERE
3489
                                        exe_id = $exeId AND
3490
                                        question_id= ".intval($questionId);
3491
                        $result = Database::query($sql);
3492
                        $str = Database::result($result, 0, 'answer');
3493
                    }
3494
3495
                    if ($saved_results == false && strpos($str, 'font color') !== false) {
3496
                        // the question is encoded like this
3497
                        // [A] B [C] D [E] F::10,10,10@1
3498
                        // number 1 before the "@" means that is a switchable fill in blank question
3499
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3500
                        // means that is a normal fill blank question
3501
                        // first we explode the "::"
3502
                        $pre_array = explode('::', $answer);
3503
3504
                        // is switchable fill blank or not
3505
                        $last = count($pre_array) - 1;
3506
                        $is_set_switchable = explode('@', $pre_array[$last]);
3507
                        $switchable_answer_set = false;
3508
                        if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3509
                            $switchable_answer_set = true;
3510
                        }
3511
                        $answer = '';
3512
                        for ($k = 0; $k < $last; $k++) {
3513
                            $answer .= $pre_array[$k];
3514
                        }
3515
                        // splits weightings that are joined with a comma
3516
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3517
                        // we save the answer because it will be modified
3518
                        $temp = $answer;
3519
                        $answer = '';
3520
                        $j = 0;
3521
                        //initialise answer tags
3522
                        $user_tags = $correct_tags = $real_text = array();
3523
                        // the loop will stop at the end of the text
3524
                        while (1) {
3525
                            // quits the loop if there are no more blanks (detect '[')
3526
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
3527
                                // adds the end of the text
3528
                                $answer = $temp;
3529
                                $real_text[] = $answer;
3530
                                break; //no more "blanks", quit the loop
3531
                            }
3532
                            // adds the piece of text that is before the blank
3533
                            //and ends with '[' into a general storage array
3534
                            $real_text[] = api_substr($temp, 0, $pos +1);
3535
                            $answer .= api_substr($temp, 0, $pos +1);
3536
                            //take the string remaining (after the last "[" we found)
3537
                            $temp = api_substr($temp, $pos +1);
3538
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3539
                            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 3537 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...
3540
                                // adds the end of the text
3541
                                $answer .= $temp;
3542
                                break;
3543
                            }
3544
                            if ($from_database) {
3545
                                $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3546
                                          WHERE
3547
                                            exe_id = '".$exeId."' AND
3548
                                            question_id= ".intval($questionId)."";
3549
                                $resfill = Database::query($queryfill);
3550
                                $str = Database::result($resfill, 0, 'answer');
3551
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3552
                                $str = str_replace('\r\n', '', $str);
3553
3554
                                $choice = $arr[1];
3555
                                if (isset($choice[$j])) {
3556
                                    $tmp = api_strrpos($choice[$j], ' / ');
3557
                                    $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 3556 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...
3558
                                    $choice[$j] = trim($choice[$j]);
3559
                                    // Needed to let characters ' and " to work as part of an answer
3560
                                    $choice[$j] = stripslashes($choice[$j]);
3561
                                } else {
3562
                                    $choice[$j] = null;
3563
                                }
3564
                            } else {
3565
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3566
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3567
                            }
3568
3569
                            $user_tags[] = $choice[$j];
3570
                            //put the contents of the [] answer tag into correct_tags[]
3571
                            $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 3537 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...
3572
                            $j++;
3573
                            $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 3573 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...
3574
                        }
3575
                        $answer = '';
3576
                        $real_correct_tags = $correct_tags;
3577
                        $chosen_list = array();
3578
3579
                        for ($i = 0; $i < count($real_correct_tags); $i++) {
3580
                            if ($i == 0) {
3581
                                $answer .= $real_text[0];
3582
                            }
3583
                            if (!$switchable_answer_set) {
3584
                                // Needed to parse ' and " characters
3585
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3586 View Code Duplication
                                if ($correct_tags[$i] == $user_tags[$i]) {
3587
                                    // gives the related weighting to the student
3588
                                    $questionScore += $answerWeighting[$i];
3589
                                    // increments total score
3590
                                    $totalScore += $answerWeighting[$i];
3591
                                    // adds the word in green at the end of the string
3592
                                    $answer .= $correct_tags[$i];
3593
                                } elseif (!empty($user_tags[$i])) {
3594
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3595
                                    // adds the word in red at the end of the string, and strikes it
3596
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3597
                                } else {
3598
                                    // adds a tabulation if no word has been typed by the student
3599
                                    $answer .= ''; // remove &nbsp; that causes issue
3600
                                }
3601
                            } else {
3602
                                // switchable fill in the blanks
3603
                                if (in_array($user_tags[$i], $correct_tags)) {
3604
                                    $chosen_list[] = $user_tags[$i];
3605
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3606
                                    // gives the related weighting to the student
3607
                                    $questionScore += $answerWeighting[$i];
3608
                                    // increments total score
3609
                                    $totalScore += $answerWeighting[$i];
3610
                                    // adds the word in green at the end of the string
3611
                                    $answer .= $user_tags[$i];
3612
                                } elseif (!empty ($user_tags[$i])) {
3613
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3614
                                    // adds the word in red at the end of the string, and strikes it
3615
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3616
                                } else {
3617
                                    // adds a tabulation if no word has been typed by the student
3618
                                    $answer .= '';  // remove &nbsp; that causes issue
3619
                                }
3620
                            }
3621
3622
                            // adds the correct word, followed by ] to close the blank
3623
                            $answer .= ' / <font color="green"><b>' . $real_correct_tags[$i] . '</b></font>]';
3624
                            if (isset($real_text[$i +1])) {
3625
                                $answer .= $real_text[$i +1];
3626
                            }
3627
                        }
3628
                    } else {
3629
                        // insert the student result in the track_e_attempt table, field answer
3630
                        // $answer is the answer like in the c_quiz_answer table for the question
3631
                        // student data are choice[]
3632
                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
3633
                            $answer
3634
                        );
3635
3636
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
3637
                        $answerWeighting = $listCorrectAnswers['tabweighting'];
3638
                        // user choices is an array $choice
3639
3640
                        // get existing user data in n the BDD
3641
                        if ($from_database) {
3642
                            $sql = "SELECT answer
3643
                                    FROM $TBL_TRACK_ATTEMPT
3644
                                    WHERE
3645
                                        exe_id = $exeId AND
3646
                                        question_id= ".intval($questionId);
3647
                            $result = Database::query($sql);
3648
                            $str = Database::result($result, 0, 'answer');
3649
                            $listStudentResults = FillBlanks::getAnswerInfo(
3650
                                $str,
3651
                                true
3652
                            );
3653
                            $choice = $listStudentResults['studentanswer'];
3654
                        }
3655
3656
                        // loop other all blanks words
3657
                        if (!$switchableAnswerSet) {
3658
                            // not switchable answer, must be in the same place than teacher order
3659
                            for ($i = 0; $i < count($listCorrectAnswers['tabwords']); $i++) {
3660
                                $studentAnswer = isset($choice[$i]) ? trim($choice[$i]) : '';
3661
                                $correctAnswer = $listCorrectAnswers['tabwords'][$i];
3662
3663
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3664
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3665
                                if (!$from_database) {
3666
                                    $studentAnswer = htmlentities(
3667
                                        api_utf8_encode($studentAnswer)
3668
                                    );
3669
                                }
3670
3671
                                $isAnswerCorrect = 0;
3672 View Code Duplication
                                if (FillBlanks::isGoodStudentAnswer($studentAnswer, $correctAnswer)) {
3673
                                    // gives the related weighting to the student
3674
                                    $questionScore += $answerWeighting[$i];
3675
                                    // increments total score
3676
                                    $totalScore += $answerWeighting[$i];
3677
                                    $isAnswerCorrect = 1;
3678
                                }
3679
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3680
                                $listCorrectAnswers['studentscore'][$i] = $isAnswerCorrect;
3681
                            }
3682
                        } else {
3683
                            // switchable answer
3684
                            $listStudentAnswerTemp = $choice;
3685
                            $listTeacherAnswerTemp = $listCorrectAnswers['tabwords'];
3686
                            // for every teacher answer, check if there is a student answer
3687
                            for ($i = 0; $i < count($listStudentAnswerTemp); $i++) {
3688
                                $studentAnswer = trim(
3689
                                    $listStudentAnswerTemp[$i]
3690
                                );
3691
                                $found = false;
3692
                                for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
3693
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3694
                                    if (!$found) {
3695 View Code Duplication
                                        if (FillBlanks::isGoodStudentAnswer(
3696
                                            $studentAnswer,
3697
                                            $correctAnswer
3698
                                        )
3699
                                        ) {
3700
                                            $questionScore += $answerWeighting[$i];
3701
                                            $totalScore += $answerWeighting[$i];
3702
                                            $listTeacherAnswerTemp[$j] = "";
3703
                                            $found = true;
3704
                                        }
3705
                                    }
3706
                                }
3707
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3708
                                if (!$found) {
3709
                                    $listCorrectAnswers['studentscore'][$i] = 0;
3710
                                } else {
3711
                                    $listCorrectAnswers['studentscore'][$i] = 1;
3712
                                }
3713
                            }
3714
                        }
3715
                        $answer = FillBlanks::getAnswerInStudentAttempt(
3716
                            $listCorrectAnswers
3717
                        );
3718
                    }
3719
                    break;
3720
                case CALCULATED_ANSWER:
3721
                    $answer = $objAnswerTmp->selectAnswer($_SESSION['calculatedAnswerId'][$questionId]);
3722
                    $preArray = explode('@@', $answer);
3723
                    $last = count($preArray) - 1;
3724
                    $answer = '';
3725
                    for ($k = 0; $k < $last; $k++) {
3726
                        $answer .= $preArray[$k];
3727
                    }
3728
                    $answerWeighting = array($answerWeighting);
3729
                    // we save the answer because it will be modified
3730
                    $temp = $answer;
3731
                    $answer = '';
3732
                    $j = 0;
3733
                    //initialise answer tags
3734
                    $userTags = $correctTags = $realText = array();
3735
                    // the loop will stop at the end of the text
3736
                    while (1) {
3737
                        // quits the loop if there are no more blanks (detect '[')
3738
                        if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
3739
                            // adds the end of the text
3740
                            $answer = $temp;
3741
                            $realText[] = $answer;
3742
                            break; //no more "blanks", quit the loop
3743
                        }
3744
                        // adds the piece of text that is before the blank
3745
                        //and ends with '[' into a general storage array
3746
                        $realText[] = api_substr($temp, 0, $pos +1);
3747
                        $answer .= api_substr($temp, 0, $pos +1);
3748
                        //take the string remaining (after the last "[" we found)
3749
                        $temp = api_substr($temp, $pos +1);
3750
                        // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3751
                        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 3749 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...
3752
                            // adds the end of the text
3753
                            $answer .= $temp;
3754
                            break;
3755
                        }
3756
                        if ($from_database) {
3757
                            $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3758
                                          WHERE
3759
                                            exe_id = '".$exeId."' AND
3760
                                            question_id= ".intval($questionId);
3761
                            $resfill = Database::query($queryfill);
3762
                            $str = Database::result($resfill, 0, 'answer');
3763
                            api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3764
                            $str = str_replace('\r\n', '', $str);
3765
                            $choice = $arr[1];
3766
                            if (isset($choice[$j])) {
3767
                                $tmp = api_strrpos($choice[$j], ' / ');
3768
3769
                                if ($tmp) {
3770
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3771
                                } else {
3772
                                    $tmp = ltrim($tmp, '[');
3773
                                    $tmp = rtrim($tmp, ']');
3774
                                }
3775
3776
                                $choice[$j] = trim($choice[$j]);
3777
                                // Needed to let characters ' and " to work as part of an answer
3778
                                $choice[$j] = stripslashes($choice[$j]);
3779
                            } else {
3780
                                $choice[$j] = null;
3781
                            }
3782
                        } else {
3783
                            // This value is the user input, not escaped while correct answer is escaped by fckeditor
3784
                            $choice[$j] = api_htmlentities(trim($choice[$j]));
3785
                        }
3786
                        $userTags[] = $choice[$j];
3787
                        //put the contents of the [] answer tag into correct_tags[]
3788
                        $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 3749 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...
3789
                        $j++;
3790
                        $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 3790 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...
3791
                    }
3792
                    $answer = '';
3793
                    $realCorrectTags = $correctTags;
3794
                    for ($i = 0; $i < count($realCorrectTags); $i++) {
3795
                        if ($i == 0) {
3796
                            $answer .= $realText[0];
3797
                        }
3798
                        // Needed to parse ' and " characters
3799
                        $userTags[$i] = stripslashes($userTags[$i]);
3800 View Code Duplication
                        if ($correctTags[$i] == $userTags[$i]) {
3801
                            // gives the related weighting to the student
3802
                            $questionScore += $answerWeighting[$i];
3803
                            // increments total score
3804
                            $totalScore += $answerWeighting[$i];
3805
                            // adds the word in green at the end of the string
3806
                            $answer .= $correctTags[$i];
3807
                        } elseif (!empty($userTags[$i])) {
3808
                            // else if the word entered by the student IS NOT the same as the one defined by the professor
3809
                            // adds the word in red at the end of the string, and strikes it
3810
                            $answer .= '<font color="red"><s>' . $userTags[$i] . '</s></font>';
3811
                        } else {
3812
                            // adds a tabulation if no word has been typed by the student
3813
                            $answer .= ''; // remove &nbsp; that causes issue
3814
                        }
3815
                        // adds the correct word, followed by ] to close the blank
3816
3817
                        if (
3818
                            $this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM
3819
                        ) {
3820
                            $answer .= ' / <font color="green"><b>' . $realCorrectTags[$i] . '</b></font>';
3821
                        }
3822
3823
                        $answer .= ']';
3824
3825
                        if (isset($realText[$i +1])) {
3826
                            $answer .= $realText[$i +1];
3827
                        }
3828
                    }
3829
                    break;
3830
                case FREE_ANSWER:
3831
                    if ($from_database) {
3832
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3833
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3834
                        $resq = Database::query($query);
3835
                        $data = Database::fetch_array($resq);
3836
3837
                        $choice = $data['answer'];
3838
                        $choice = str_replace('\r\n', '', $choice);
3839
                        $choice = stripslashes($choice);
3840
                        $questionScore = $data['marks'];
3841
3842
                        if ($questionScore == -1) {
3843
                            $totalScore+= 0;
3844
                        } else {
3845
                            $totalScore+= $questionScore;
3846
                        }
3847
                        if ($questionScore == '') {
3848
                            $questionScore = 0;
3849
                        }
3850
                        $arrques = $questionName;
3851
                        $arrans  = $choice;
3852
                    } else {
3853
                        $studentChoice = $choice;
3854
                        if ($studentChoice) {
3855
                            //Fixing negative puntation see #2193
3856
                            $questionScore = 0;
3857
                            $totalScore += 0;
3858
                        }
3859
                    }
3860
                    break;
3861
                case ORAL_EXPRESSION:
3862
                    if ($from_database) {
3863
                        $query = "SELECT answer, marks 
3864
                                  FROM $TBL_TRACK_ATTEMPT
3865
                                  WHERE 
3866
                                        exe_id = $exeId AND 
3867
                                        question_id = $questionId
3868
                                 ";
3869
                        $resq = Database::query($query);
3870
                        $row = Database::fetch_assoc($resq);
3871
                        $choice = $row['answer'];
3872
                        $choice = str_replace('\r\n', '', $choice);
3873
                        $choice = stripslashes($choice);
3874
                        $questionScore = $row['marks'];
3875
                        if ($questionScore == -1) {
3876
                            $totalScore += 0;
3877
                        } else {
3878
                            $totalScore += $questionScore;
3879
                        }
3880
                        $arrques = $questionName;
3881
                        $arrans  = $choice;
3882
                    } else {
3883
                        $studentChoice = $choice;
3884
                        if ($studentChoice) {
3885
                            //Fixing negative puntation see #2193
3886
                            $questionScore = 0;
3887
                            $totalScore += 0;
3888
                        }
3889
                    }
3890
                    break;
3891
                case DRAGGABLE:
3892
                    //no break
3893
                case MATCHING_DRAGGABLE:
3894
                    //no break
3895
                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...
3896
                    if ($from_database) {
3897
                        $sql = "SELECT id, answer, id_auto
3898
                                FROM $table_ans
3899
                                WHERE
3900
                                    c_id = $course_id AND
3901
                                    question_id = $questionId AND
3902
                                    correct = 0
3903
                                ";
3904
                        $res_answer = Database::query($sql);
3905
                        // Getting the real answer
3906
                        $real_list = array();
3907
                        while ($real_answer = Database::fetch_array($res_answer)) {
3908
                            $real_list[$real_answer['id_auto']] = $real_answer['answer'];
3909
                        }
3910
3911
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
3912
                                FROM $table_ans
3913
                                WHERE
3914
                                    c_id = $course_id AND
3915
                                    question_id = $questionId AND
3916
                                    correct <> 0
3917
                                ORDER BY id_auto";
3918
                        $res_answers = Database::query($sql);
3919
3920
                        $questionScore = 0;
3921
3922
                        while ($a_answers = Database::fetch_array($res_answers)) {
3923
                            $i_answer_id = $a_answers['id']; //3
3924
                            $s_answer_label = $a_answers['answer'];  // your daddy - your mother
3925
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
3926
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
3927
3928
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3929
                                    WHERE
3930
                                        exe_id = '$exeId' AND
3931
                                        question_id = '$questionId' AND
3932
                                        position = '$i_answer_id_auto'";
3933
3934
                            $res_user_answer = Database::query($sql);
3935
3936
                            if (Database::num_rows($res_user_answer) > 0) {
3937
                                //  rich - good looking
3938
                                $s_user_answer = Database::result($res_user_answer, 0, 0);
3939
                            } else {
3940
                                $s_user_answer = 0;
3941
                            }
3942
3943
                            $i_answerWeighting = $a_answers['ponderation'];
3944
3945
                            $user_answer = '';
3946
                            if (!empty($s_user_answer)) {
3947
                                if ($answerType == DRAGGABLE) {
3948
                                    if ($s_user_answer == $i_answer_correct_answer) {
3949
                                        $questionScore += $i_answerWeighting;
3950
                                        $totalScore += $i_answerWeighting;
3951
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
3952
                                    } else {
3953
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3954
                                    }
3955
                                } else {
3956
                                    if ($s_user_answer == $i_answer_correct_answer) {
3957
                                        $questionScore += $i_answerWeighting;
3958
                                        $totalScore += $i_answerWeighting;
3959
3960
                                        // Try with id
3961
                                        if (isset($real_list[$i_answer_id])) {
3962
                                            $user_answer = Display::span($real_list[$i_answer_id]);
3963
                                        }
3964
3965
                                        // Try with $i_answer_id_auto
3966
                                        if (empty($user_answer)) {
3967
                                            if (isset($real_list[$i_answer_id_auto])) {
3968
                                                $user_answer = Display::span(
3969
                                                    $real_list[$i_answer_id_auto]
3970
                                                );
3971
                                            }
3972
                                        }
3973
                                    } else {
3974
                                        $user_answer = Display::span(
3975
                                            $real_list[$s_user_answer],
3976
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
3977
                                        );
3978
                                    }
3979
                                }
3980
                            } elseif ($answerType == DRAGGABLE) {
3981
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3982
                            } else {
3983
                                $user_answer = Display::span(
3984
                                    get_lang('Incorrect').' &nbsp;',
3985
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
3986
                                );
3987
                            }
3988
3989
                            if ($show_result) {
3990
                                if ($showTotalScoreAndUserChoicesInLastAttempt === false) {
3991
                                    $user_answer = '';
3992
                                }
3993
                                echo '<tr>';
3994
                                echo '<td>' . $s_answer_label . '</td>';
3995
                                echo '<td>' . $user_answer;
3996
3997
                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
3998
                                    if (isset($real_list[$i_answer_correct_answer]) &&
3999
                                        $showTotalScoreAndUserChoicesInLastAttempt === true
4000
                                    ) {
4001
                                        echo Display::span(
4002
                                            $real_list[$i_answer_correct_answer],
4003
                                            ['style' => 'color: #008000; font-weight: bold;']
4004
                                        );
4005
                                    }
4006
                                }
4007
                                echo '</td>';
4008
                                echo '</tr>';
4009
                            }
4010
                        }
4011
                        break(2); // break the switch and the "for" condition
4012
                    } else {
4013
                        if ($answerCorrect) {
4014
                            if (isset($choice[$answerAutoId]) &&
4015
                                $answerCorrect == $choice[$answerAutoId]
4016
                            ) {
4017
                                $questionScore += $answerWeighting;
4018
                                $totalScore += $answerWeighting;
4019
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4020
                            } else {
4021
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4022
                                    $user_answer = Display::span(
4023
                                        $answerMatching[$choice[$answerAutoId]],
4024
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4025
                                    );
4026
                                }
4027
                            }
4028
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4029
                        }
4030
                        break;
4031
                    }
4032
                case HOT_SPOT:
4033
                    if ($from_database) {
4034
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4035
                        // Check auto id
4036
                        $sql = "SELECT hotspot_correct
4037
                                FROM $TBL_TRACK_HOTSPOT
4038
                                WHERE
4039
                                    hotspot_exe_id = $exeId AND
4040
                                    hotspot_question_id= $questionId AND
4041
                                    hotspot_answer_id = ".intval($answerAutoId);
4042
                        $result = Database::query($sql);
4043
                        if (Database::num_rows($result)) {
4044
                            $studentChoice = Database::result(
4045
                                $result,
4046
                                0,
4047
                                'hotspot_correct'
4048
                            );
4049
4050
                            if ($studentChoice) {
4051
                                $questionScore += $answerWeighting;
4052
                                $totalScore += $answerWeighting;
4053
                            }
4054
                        } else {
4055
                            // If answer.id is different:
4056
                            $sql = "SELECT hotspot_correct
4057
                                FROM $TBL_TRACK_HOTSPOT
4058
                                WHERE
4059
                                    hotspot_exe_id = $exeId AND
4060
                                    hotspot_question_id= $questionId AND
4061
                                    hotspot_answer_id = ".intval($answerId);
4062
                            $result = Database::query($sql);
4063
4064
                            if (Database::num_rows($result)) {
4065
                                $studentChoice = Database::result(
4066
                                    $result,
4067
                                    0,
4068
                                    'hotspot_correct'
4069
                                );
4070
4071
                                if ($studentChoice) {
4072
                                    $questionScore += $answerWeighting;
4073
                                    $totalScore += $answerWeighting;
4074
                                }
4075
                            } else {
4076
                                // check answer.iid
4077
                                if (!empty($answerIid)) {
4078
                                    $sql = "SELECT hotspot_correct
4079
                                            FROM $TBL_TRACK_HOTSPOT
4080
                                            WHERE
4081
                                                hotspot_exe_id = $exeId AND
4082
                                                hotspot_question_id= $questionId AND
4083
                                                hotspot_answer_id = ".intval($answerIid);
4084
                                    $result = Database::query($sql);
4085
4086
                                    $studentChoice = Database::result(
4087
                                        $result,
4088
                                        0,
4089
                                        'hotspot_correct'
4090
                                    );
4091
4092
                                    if ($studentChoice) {
4093
                                        $questionScore += $answerWeighting;
4094
                                        $totalScore += $answerWeighting;
4095
                                    }
4096
                                }
4097
                            }
4098
                        }
4099
                    } else {
4100
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4101
                            $choice[$answerAutoId] = 0;
4102
                            $choice[$answerIid] = 0;
4103
                        } else {
4104
                            $studentChoice = $choice[$answerAutoId];
4105
                            if (empty($studentChoice)) {
4106
                                $studentChoice = $choice[$answerIid];
4107
                            }
4108
                            $choiceIsValid = false;
4109
                            if (!empty($studentChoice)) {
4110
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4111
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4112
                                $choicePoint = Geometry::decodePoint($studentChoice);
4113
4114
                                switch ($hotspotType) {
4115
                                    case 'square':
4116
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4117
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4118
                                        break;
4119
                                    case 'circle':
4120
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4121
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4122
                                        break;
4123
                                    case 'poly':
4124
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4125
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4126
                                        break;
4127
                                }
4128
                            }
4129
4130
                            $choice[$answerAutoId] = 0;
4131
                            if ($choiceIsValid) {
4132
                                $questionScore += $answerWeighting;
4133
                                $totalScore += $answerWeighting;
4134
                                $choice[$answerAutoId] = 1;
4135
                                $choice[$answerIid] = 1;
4136
                            }
4137
                        }
4138
                    }
4139
                    break;
4140
                // @todo never added to chamilo
4141
                //for hotspot with fixed order
4142
                case HOT_SPOT_ORDER:
4143
                    $studentChoice = $choice['order'][$answerId];
4144
                    if ($studentChoice == $answerId) {
4145
                        $questionScore  += $answerWeighting;
4146
                        $totalScore     += $answerWeighting;
4147
                        $studentChoice = true;
4148
                    } else {
4149
                        $studentChoice = false;
4150
                    }
4151
                    break;
4152
                // for hotspot with delineation
4153
                case HOT_SPOT_DELINEATION:
4154
                    if ($from_database) {
4155
                        // getting the user answer
4156
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4157
                        $query   = "SELECT hotspot_correct, hotspot_coordinate
4158
                                    FROM $TBL_TRACK_HOTSPOT
4159
                                    WHERE
4160
                                        hotspot_exe_id = '".$exeId."' AND
4161
                                        hotspot_question_id= '".$questionId."' AND
4162
                                        hotspot_answer_id='1'";
4163
                        //by default we take 1 because it's a delineation
4164
                        $resq = Database::query($query);
4165
                        $row = Database::fetch_array($resq,'ASSOC');
4166
4167
                        $choice = $row['hotspot_correct'];
4168
                        $user_answer = $row['hotspot_coordinate'];
4169
4170
                        // THIS is very important otherwise the poly_compile will throw an error!!
4171
                        // round-up the coordinates
4172
                        $coords = explode('/',$user_answer);
4173
                        $user_array = '';
4174 View Code Duplication
                        foreach ($coords as $coord) {
4175
                            list($x,$y) = explode(';',$coord);
4176
                            $user_array .= round($x).';'.round($y).'/';
4177
                        }
4178
                        $user_array = substr($user_array,0,-1);
4179
                    } else {
4180
                        if (!empty($studentChoice)) {
4181
                            $newquestionList[] = $questionId;
4182
                        }
4183
4184
                        if ($answerId === 1) {
4185
                            $studentChoice = $choice[$answerId];
4186
                            $questionScore += $answerWeighting;
4187
4188
                            if ($hotspot_delineation_result[1]==1) {
4189
                                $totalScore += $answerWeighting; //adding the total
4190
                            }
4191
                        }
4192
                    }
4193
                    $_SESSION['hotspot_coord'][1]	= $delineation_cord;
4194
                    $_SESSION['hotspot_dest'][1]	= $answer_delineation_destination;
4195
                    break;
4196
            } // end switch Answertype
4197
4198
            if ($show_result) {
4199
                if ($debug) error_log('Showing questions $from '.$from);
4200
                if ($from == 'exercise_result') {
4201
                    //display answers (if not matching type, or if the answer is correct)
4202
                    if (
4203
                        !in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4204
                        $answerCorrect
4205
                    ) {
4206
                        if (
4207
                            in_array(
4208
                                $answerType,
4209
                                array(
4210
                                    UNIQUE_ANSWER,
4211
                                    UNIQUE_ANSWER_IMAGE,
4212
                                    UNIQUE_ANSWER_NO_OPTION,
4213
                                    MULTIPLE_ANSWER,
4214
                                    MULTIPLE_ANSWER_COMBINATION,
4215
                                    GLOBAL_MULTIPLE_ANSWER
4216
                                )
4217
                            )
4218
                        ) {
4219
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4220
                                $feedback_type,
4221
                                $answerType,
4222
                                $studentChoice,
4223
                                $answer,
4224
                                $answerComment,
4225
                                $answerCorrect,
4226
                                0,
4227
                                0,
4228
                                0,
4229
                                $results_disabled,
4230
                                $showTotalScoreAndUserChoicesInLastAttempt
4231
                            );
4232
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4233
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4234
                                $feedback_type,
4235
                                $answerType,
4236
                                $studentChoice,
4237
                                $answer,
4238
                                $answerComment,
4239
                                $answerCorrect,
4240
                                0,
4241
                                $questionId,
4242
                                0,
4243
                                $results_disabled,
4244
                                $showTotalScoreAndUserChoicesInLastAttempt
4245
                            );
4246
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ) {
4247
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4248
                                $feedback_type,
4249
                                $answerType,
4250
                                $studentChoice,
4251
                                $answer,
4252
                                $answerComment,
4253
                                $answerCorrect,
4254
                                0,
4255
                                0,
4256
                                0,
4257
                                $results_disabled,
4258
                                $showTotalScoreAndUserChoicesInLastAttempt
4259
                            );
4260
                        } elseif ($answerType == FILL_IN_BLANKS) {
4261
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4262
                                $feedback_type,
4263
                                $answer,
4264
                                0,
4265
                                0,
4266
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3129 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...
4267
                                '',
4268
                                $showTotalScoreAndUserChoicesInLastAttempt
4269
                            );
4270
                        } elseif ($answerType == CALCULATED_ANSWER) {
4271
                            ExerciseShowFunctions::display_calculated_answer(
4272
                                $feedback_type,
4273
                                $answer,
4274
                                0,
4275
                                0,
4276
                                $results_disabled,
4277
                                $showTotalScoreAndUserChoicesInLastAttempt
4278
                            );
4279
                        } elseif ($answerType == FREE_ANSWER) {
4280
                            ExerciseShowFunctions::display_free_answer(
4281
                                $feedback_type,
4282
                                $choice,
4283
                                $exeId,
4284
                                $questionId,
4285
                                $questionScore,
4286
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3129 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...
4287
                            );
4288
                        } elseif ($answerType == ORAL_EXPRESSION) {
4289
                            // to store the details of open questions in an array to be used in mail
4290
                            /** @var OralExpression $objQuestionTmp */
4291
                            ExerciseShowFunctions::display_oral_expression_answer(
4292
                                $feedback_type,
4293
                                $choice,
4294
                                0,
4295
                                0,
4296
                                $objQuestionTmp->getFileUrl(true),
4297
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3129 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...
4298
                            );
4299
                        } elseif ($answerType == HOT_SPOT) {
4300
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4301
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4302
                                    break;
4303
                                }
4304
                            }
4305
4306
                            ExerciseShowFunctions::display_hotspot_answer(
4307
                                $feedback_type,
4308
                                $answerId,
4309
                                $answer,
4310
                                $studentChoice,
4311
                                $answerComment,
4312
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3129 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...
4313
                                $answerId,
4314
                                $showTotalScoreAndUserChoicesInLastAttempt
4315
                            );
4316
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4317
                            ExerciseShowFunctions::display_hotspot_order_answer(
4318
                                $feedback_type,
4319
                                $answerId,
4320
                                $answer,
4321
                                $studentChoice,
4322
                                $answerComment
4323
                            );
4324
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4325
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4326
4327
                            //round-up the coordinates
4328
                            $coords = explode('/',$user_answer);
4329
                            $user_array = '';
4330 View Code Duplication
                            foreach ($coords as $coord) {
4331
                                list($x,$y) = explode(';',$coord);
4332
                                $user_array .= round($x).';'.round($y).'/';
4333
                            }
4334
                            $user_array = substr($user_array,0,-1);
4335
4336 View Code Duplication
                            if ($next) {
4337
4338
                                $user_answer = $user_array;
4339
4340
                                // we compare only the delineation not the other points
4341
                                $answer_question = $_SESSION['hotspot_coord'][1];
4342
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4343
4344
                                //calculating the area
4345
                                $poly_user = convert_coordinates($user_answer, '/');
4346
                                $poly_answer = convert_coordinates($answer_question, '|');
4347
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4348
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4349
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4350
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4351
4352
                                $overlap = $poly_results['both'];
4353
                                $poly_answer_area = $poly_results['s1'];
4354
                                $poly_user_area = $poly_results['s2'];
4355
                                $missing = $poly_results['s1Only'];
4356
                                $excess = $poly_results['s2Only'];
4357
4358
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4359
                                // //this is an area in pixels
4360
                                if ($debug > 0) {
4361
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4362
                                }
4363
4364
                                if ($overlap < 1) {
4365
                                    //shortcut to avoid complicated calculations
4366
                                    $final_overlap = 0;
4367
                                    $final_missing = 100;
4368
                                    $final_excess = 100;
4369
                                } else {
4370
                                    // the final overlap is the percentage of the initial polygon
4371
                                    // that is overlapped by the user's polygon
4372
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4373
                                    if ($debug > 1) {
4374
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4375
                                    }
4376
                                    // the final missing area is the percentage of the initial polygon
4377
                                    // that is not overlapped by the user's polygon
4378
                                    $final_missing = 100 - $final_overlap;
4379
                                    if ($debug > 1) {
4380
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4381
                                    }
4382
                                    // the final excess area is the percentage of the initial polygon's size
4383
                                    // that is covered by the user's polygon outside of the initial polygon
4384
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4385
                                    if ($debug > 1) {
4386
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4387
                                    }
4388
                                }
4389
4390
                                //checking the destination parameters parsing the "@@"
4391
                                $destination_items= explode('@@', $answerDestination);
4392
                                $threadhold_total = $destination_items[0];
4393
                                $threadhold_items=explode(';',$threadhold_total);
4394
                                $threadhold1 = $threadhold_items[0]; // overlap
4395
                                $threadhold2 = $threadhold_items[1]; // excess
4396
                                $threadhold3 = $threadhold_items[2];	 //missing
4397
4398
                                // if is delineation
4399
                                if ($answerId===1) {
4400
                                    //setting colors
4401
                                    if ($final_overlap>=$threadhold1) {
4402
                                        $overlap_color=true; //echo 'a';
4403
                                    }
4404
                                    //echo $excess.'-'.$threadhold2;
4405
                                    if ($final_excess<=$threadhold2) {
4406
                                        $excess_color=true; //echo 'b';
4407
                                    }
4408
                                    //echo '--------'.$missing.'-'.$threadhold3;
4409
                                    if ($final_missing<=$threadhold3) {
4410
                                        $missing_color=true; //echo 'c';
4411
                                    }
4412
4413
                                    // if pass
4414
                                    if (
4415
                                        $final_overlap >= $threadhold1 &&
4416
                                        $final_missing <= $threadhold3 &&
4417
                                        $final_excess <= $threadhold2
4418
                                    ) {
4419
                                        $next=1; //go to the oars
4420
                                        $result_comment=get_lang('Acceptable');
4421
                                        $final_answer = 1;	// do not update with  update_exercise_attempt
4422
                                    } else {
4423
                                        $next=0;
4424
                                        $result_comment=get_lang('Unacceptable');
4425
                                        $comment=$answerDestination=$objAnswerTmp->selectComment(1);
4426
                                        $answerDestination=$objAnswerTmp->selectDestination(1);
4427
                                        //checking the destination parameters parsing the "@@"
4428
                                        $destination_items= explode('@@', $answerDestination);
4429
                                    }
4430
                                } elseif($answerId>1) {
4431
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4432
                                        if ($debug>0) {
4433
                                            error_log(__LINE__.' - answerId is of type noerror',0);
4434
                                        }
4435
                                        //type no error shouldn't be treated
4436
                                        $next = 1;
4437
                                        continue;
4438
                                    }
4439
                                    if ($debug>0) {
4440
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR',0);
4441
                                    }
4442
                                    //check the intersection between the oar and the user
4443
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4444
                                    //echo 'official';print_r($x_list);print_r($y_list);
4445
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4446
                                    $inter= $result['success'];
4447
4448
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4449
                                    $delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4450
4451
                                    $poly_answer = convert_coordinates($delineation_cord,'|');
4452
                                    $max_coord = poly_get_max($poly_user,$poly_answer);
4453
                                    $poly_answer_compiled = poly_compile($poly_answer,$max_coord);
4454
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4455
4456
                                    if ($overlap == false) {
4457
                                        //all good, no overlap
4458
                                        $next = 1;
4459
                                        continue;
4460
                                    } else {
4461
                                        if ($debug>0) {
4462
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit',0);
4463
                                        }
4464
                                        $organs_at_risk_hit++;
4465
                                        //show the feedback
4466
                                        $next=0;
4467
                                        $comment=$answerDestination=$objAnswerTmp->selectComment($answerId);
4468
                                        $answerDestination=$objAnswerTmp->selectDestination($answerId);
4469
4470
                                        $destination_items= explode('@@', $answerDestination);
4471
                                        $try_hotspot=$destination_items[1];
4472
                                        $lp_hotspot=$destination_items[2];
4473
                                        $select_question_hotspot=$destination_items[3];
4474
                                        $url_hotspot=$destination_items[4];
4475
                                    }
4476
                                }
4477
                            } else {	// the first delineation feedback
4478
                                if ($debug>0) {
4479
                                    error_log(__LINE__.' first',0);
4480
                                }
4481
                            }
4482
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4483
                            echo '<tr>';
4484
                            echo Display::tag('td', $answerMatching[$answerId]);
4485
                            echo Display::tag(
4486
                                'td',
4487
                                "$user_answer / " . Display::tag(
4488
                                    'strong',
4489
                                    $answerMatching[$answerCorrect],
4490
                                    ['style' => 'color: #008000; font-weight: bold;']
4491
                                )
4492
                            );
4493
                            echo '</tr>';
4494
                        }
4495
                    }
4496
                } else {
4497
                    if ($debug) error_log('Showing questions $from '.$from);
4498
4499
                    switch ($answerType) {
4500
                        case UNIQUE_ANSWER:
4501
                        case UNIQUE_ANSWER_IMAGE:
4502
                        case UNIQUE_ANSWER_NO_OPTION:
4503
                        case MULTIPLE_ANSWER:
4504
                        case GLOBAL_MULTIPLE_ANSWER :
4505 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION:
4506
                            if ($answerId == 1) {
4507
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4508
                                    $feedback_type,
4509
                                    $answerType,
4510
                                    $studentChoice,
4511
                                    $answer,
4512
                                    $answerComment,
4513
                                    $answerCorrect,
4514
                                    $exeId,
4515
                                    $questionId,
4516
                                    $answerId,
4517
                                    $results_disabled,
4518
                                    $showTotalScoreAndUserChoicesInLastAttempt
4519
                                );
4520
                            } else {
4521
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4522
                                    $feedback_type,
4523
                                    $answerType,
4524
                                    $studentChoice,
4525
                                    $answer,
4526
                                    $answerComment,
4527
                                    $answerCorrect,
4528
                                    $exeId,
4529
                                    $questionId,
4530
                                    '',
4531
                                    $results_disabled,
4532
                                    $showTotalScoreAndUserChoicesInLastAttempt
4533
                                );
4534
                            }
4535
                            break;
4536 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4537
                            if ($answerId == 1) {
4538
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4539
                                    $feedback_type,
4540
                                    $answerType,
4541
                                    $studentChoice,
4542
                                    $answer,
4543
                                    $answerComment,
4544
                                    $answerCorrect,
4545
                                    $exeId,
4546
                                    $questionId,
4547
                                    $answerId,
4548
                                    $results_disabled,
4549
                                    $showTotalScoreAndUserChoicesInLastAttempt
4550
                                );
4551
                            } else {
4552
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4553
                                    $feedback_type,
4554
                                    $answerType,
4555
                                    $studentChoice,
4556
                                    $answer,
4557
                                    $answerComment,
4558
                                    $answerCorrect,
4559
                                    $exeId,
4560
                                    $questionId,
4561
                                    '',
4562
                                    $results_disabled,
4563
                                    $showTotalScoreAndUserChoicesInLastAttempt
4564
                                );
4565
                            }
4566
                            break;
4567 View Code Duplication
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4568
                            if ($answerId == 1) {
4569
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4570
                                    $feedback_type,
4571
                                    $answerType,
4572
                                    $studentChoice,
4573
                                    $answer,
4574
                                    $answerComment,
4575
                                    $answerCorrect,
4576
                                    $exeId,
4577
                                    $questionId,
4578
                                    $answerId,
4579
                                    $results_disabled,
4580
                                    $showTotalScoreAndUserChoicesInLastAttempt
4581
                                );
4582
                            } else {
4583
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4584
                                    $feedback_type,
4585
                                    $answerType,
4586
                                    $studentChoice,
4587
                                    $answer,
4588
                                    $answerComment,
4589
                                    $answerCorrect,
4590
                                    $exeId,
4591
                                    $questionId,
4592
                                    '',
4593
                                    $results_disabled,
4594
                                    $showTotalScoreAndUserChoicesInLastAttempt
4595
                                );
4596
                            }
4597
                            break;
4598
                        case FILL_IN_BLANKS:
4599
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4600
                                $feedback_type,
4601
                                $answer,
4602
                                $exeId,
4603
                                $questionId,
4604
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3129 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...
4605
                                $str,
4606
                                $showTotalScoreAndUserChoicesInLastAttempt
4607
                            );
4608
                            break;
4609
                        case CALCULATED_ANSWER:
4610
                            ExerciseShowFunctions::display_calculated_answer(
4611
                                $feedback_type,
4612
                                $answer,
4613
                                $exeId,
4614
                                $questionId,
4615
                                $results_disabled,
4616
                                '',
4617
                                $showTotalScoreAndUserChoicesInLastAttempt
4618
                            );
4619
                            break;
4620
                        case FREE_ANSWER:
4621
                            echo ExerciseShowFunctions::display_free_answer(
4622
                                $feedback_type,
4623
                                $choice,
4624
                                $exeId,
4625
                                $questionId,
4626
                                $questionScore,
4627
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3129 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...
4628
                            );
4629
                            break;
4630
                        case ORAL_EXPRESSION:
4631
                            echo '<tr>
4632
                                <td valign="top">' . ExerciseShowFunctions::display_oral_expression_answer(
4633
                                    $feedback_type,
4634
                                    $choice,
4635
                                    $exeId,
4636
                                    $questionId,
4637
                                    $objQuestionTmp->getFileUrl(),
4638
                                    $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3129 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...
4639
                                ) . '</td>
4640
                                </tr>
4641
                                </table>';
4642
                            break;
4643
                        case HOT_SPOT:
4644
                            ExerciseShowFunctions::display_hotspot_answer(
4645
                                $feedback_type,
4646
                                $answerId,
4647
                                $answer,
4648
                                $studentChoice,
4649
                                $answerComment,
4650
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3129 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...
4651
                                $answerId,
4652
                                $showTotalScoreAndUserChoicesInLastAttempt
4653
                            );
4654
                            break;
4655
                        case HOT_SPOT_DELINEATION:
4656
                            $user_answer = $user_array;
4657 View Code Duplication
                            if ($next) {
4658
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4659
                                // Save into db
4660
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
4661
                                 * hotspot_user_id,
4662
                                 *  hotspot_course_code,
4663
                                 *  hotspot_exe_id,
4664
                                 *  hotspot_question_id,
4665
                                 *  hotspot_answer_id,
4666
                                 *  hotspot_correct,
4667
                                 *  hotspot_coordinate
4668
                                 *  )
4669
                                VALUES (
4670
                                 * '".Database::escape_string($_user['user_id'])."',
4671
                                 *  '".Database::escape_string($_course['id'])."',
4672
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
4673
                                 *  '".Database::escape_string($answerId)."',
4674
                                 *  '".Database::escape_string($studentChoice)."',
4675
                                 *  '".Database::escape_string($user_array)."')";
4676
                                $result = Database::query($sql,__FILE__,__LINE__);
4677
                                 */
4678
                                $user_answer = $user_array;
4679
4680
                                // we compare only the delineation not the other points
4681
                                $answer_question = $_SESSION['hotspot_coord'][1];
4682
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4683
4684
                                //calculating the area
4685
                                $poly_user = convert_coordinates($user_answer, '/');
4686
                                $poly_answer = convert_coordinates($answer_question, '|');
4687
4688
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4689
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4690
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4691
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4692
4693
                                $overlap = $poly_results['both'];
4694
                                $poly_answer_area = $poly_results['s1'];
4695
                                $poly_user_area = $poly_results['s2'];
4696
                                $missing = $poly_results['s1Only'];
4697
                                $excess = $poly_results['s2Only'];
4698
4699
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user)); //this is an area in pixels
4700
                                if ($debug > 0) {
4701
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4702
                                }
4703
                                if ($overlap < 1) {
4704
                                    //shortcut to avoid complicated calculations
4705
                                    $final_overlap = 0;
4706
                                    $final_missing = 100;
4707
                                    $final_excess = 100;
4708
                                } else {
4709
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
4710
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4711
                                    if ($debug > 1) {
4712
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4713
                                    }
4714
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
4715
                                    $final_missing = 100 - $final_overlap;
4716
                                    if ($debug > 1) {
4717
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4718
                                    }
4719
                                    // 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
4720
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4721
                                    if ($debug > 1) {
4722
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4723
                                    }
4724
                                }
4725
4726
                                //checking the destination parameters parsing the "@@"
4727
                                $destination_items = explode('@@', $answerDestination);
4728
                                $threadhold_total = $destination_items[0];
4729
                                $threadhold_items = explode(';', $threadhold_total);
4730
                                $threadhold1 = $threadhold_items[0]; // overlap
4731
                                $threadhold2 = $threadhold_items[1]; // excess
4732
                                $threadhold3 = $threadhold_items[2];  //missing
4733
                                // if is delineation
4734
                                if ($answerId === 1) {
4735
                                    //setting colors
4736
                                    if ($final_overlap >= $threadhold1) {
4737
                                        $overlap_color = true; //echo 'a';
4738
                                    }
4739
                                    //echo $excess.'-'.$threadhold2;
4740
                                    if ($final_excess <= $threadhold2) {
4741
                                        $excess_color = true; //echo 'b';
4742
                                    }
4743
                                    //echo '--------'.$missing.'-'.$threadhold3;
4744
                                    if ($final_missing <= $threadhold3) {
4745
                                        $missing_color = true; //echo 'c';
4746
                                    }
4747
4748
                                    // if pass
4749
                                    if ($final_overlap >= $threadhold1 && $final_missing <= $threadhold3 && $final_excess <= $threadhold2) {
4750
                                        $next = 1; //go to the oars
4751
                                        $result_comment = get_lang('Acceptable');
4752
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4753
                                    } else {
4754
                                        $next = 0;
4755
                                        $result_comment = get_lang('Unacceptable');
4756
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4757
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4758
                                        //checking the destination parameters parsing the "@@"
4759
                                        $destination_items = explode('@@', $answerDestination);
4760
                                    }
4761
                                } elseif ($answerId > 1) {
4762
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4763
                                        if ($debug > 0) {
4764
                                            error_log(__LINE__ . ' - answerId is of type noerror', 0);
4765
                                        }
4766
                                        //type no error shouldn't be treated
4767
                                        $next = 1;
4768
                                        continue;
4769
                                    }
4770
                                    if ($debug > 0) {
4771
                                        error_log(__LINE__ . ' - answerId is >1 so we\'re probably in OAR', 0);
4772
                                    }
4773
                                    //check the intersection between the oar and the user
4774
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4775
                                    //echo 'official';print_r($x_list);print_r($y_list);
4776
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4777
                                    $inter = $result['success'];
4778
4779
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4780
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4781
4782
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4783
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4784
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4785
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4786
4787
                                    if ($overlap == false) {
4788
                                        //all good, no overlap
4789
                                        $next = 1;
4790
                                        continue;
4791
                                    } else {
4792
                                        if ($debug > 0) {
4793
                                            error_log(__LINE__ . ' - Overlap is ' . $overlap . ': OAR hit', 0);
4794
                                        }
4795
                                        $organs_at_risk_hit++;
4796
                                        //show the feedback
4797
                                        $next = 0;
4798
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4799
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4800
4801
                                        $destination_items = explode('@@', $answerDestination);
4802
                                        $try_hotspot = $destination_items[1];
4803
                                        $lp_hotspot = $destination_items[2];
4804
                                        $select_question_hotspot = $destination_items[3];
4805
                                        $url_hotspot=$destination_items[4];
4806
                                    }
4807
                                }
4808
                            } else {	// the first delineation feedback
4809
                                if ($debug > 0) {
4810
                                    error_log(__LINE__ . ' first', 0);
4811
                                }
4812
                            }
4813
                            break;
4814
                        case HOT_SPOT_ORDER:
4815
                            ExerciseShowFunctions::display_hotspot_order_answer(
4816
                                $feedback_type,
4817
                                $answerId,
4818
                                $answer,
4819
                                $studentChoice,
4820
                                $answerComment
4821
                            );
4822
                            break;
4823
                        case DRAGGABLE:
4824
                            //no break
4825
                        case MATCHING_DRAGGABLE:
4826
                            //no break
4827
                        case MATCHING:
4828
                            echo '<tr>';
4829
                            echo Display::tag('td', $answerMatching[$answerId]);
4830
                            echo Display::tag(
4831
                                'td',
4832
                                "$user_answer / " . Display::tag(
4833
                                    'strong',
4834
                                    $answerMatching[$answerCorrect],
4835
                                    ['style' => 'color: #008000; font-weight: bold;']
4836
                                )
4837
                            );
4838
                            echo '</tr>';
4839
4840
                            break;
4841
                    }
4842
                }
4843
            }
4844
            if ($debug) error_log(' ------ ');
4845
        } // end for that loops over all answers of the current question
4846
4847
        if ($debug) error_log('-- end answer loop --');
4848
4849
        $final_answer = true;
4850
4851
        foreach ($real_answers as $my_answer) {
4852
            if (!$my_answer) {
4853
                $final_answer = false;
4854
            }
4855
        }
4856
4857
        //we add the total score after dealing with the answers
4858
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
4859
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
4860
        ) {
4861
            if ($final_answer) {
4862
                //getting only the first score where we save the weight of all the question
4863
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
4864
                $questionScore += $answerWeighting;
4865
                $totalScore += $answerWeighting;
4866
            }
4867
        }
4868
4869
        //Fixes multiple answer question in order to be exact
4870
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
4871
       /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
4872
            $diff = @array_diff($answer_correct_array, $real_answers);
4873
4874
            // All good answers or nothing works like exact
4875
4876
            $counter = 1;
4877
            $correct_answer = true;
4878
            foreach ($real_answers as $my_answer) {
4879
                if ($debug)
4880
                    error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
4881
                if ($my_answer != $answer_correct_array[$counter]) {
4882
                    $correct_answer = false;
4883
                    break;
4884
                }
4885
                $counter++;
4886
            }
4887
4888
            if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
4889
            if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
4890
            if ($debug) error_log(" correct_answer: ".$correct_answer);
4891
4892
            if ($correct_answer == false) {
4893
                $questionScore = 0;
4894
            }
4895
4896
            // This makes the result non exact
4897
            if (!empty($diff)) {
4898
                $questionScore = 0;
4899
            }
4900
        }*/
4901
4902
        $extra_data = array(
4903
            'final_overlap' => $final_overlap,
4904
            'final_missing'=>$final_missing,
4905
            'final_excess'=> $final_excess,
4906
            'overlap_color' => $overlap_color,
4907
            'missing_color'=>$missing_color,
4908
            'excess_color'=> $excess_color,
4909
            'threadhold1'   => $threadhold1,
4910
            'threadhold2'=>$threadhold2,
4911
            'threadhold3'=> $threadhold3,
4912
        );
4913
        if ($from == 'exercise_result') {
4914
            // if answer is hotspot. To the difference of exercise_show.php,
4915
            //  we use the results from the session (from_db=0)
4916
            // TODO Change this, because it is wrong to show the user
4917
            //  some results that haven't been stored in the database yet
4918
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION ) {
4919
4920
                if ($debug) error_log('$from AND this is a hotspot kind of question ');
4921
4922
                $my_exe_id = 0;
4923
                $from_database = 0;
4924
                if ($answerType == HOT_SPOT_DELINEATION) {
4925
                    if (0) {
4926
                        if ($overlap_color) {
4927
                            $overlap_color='green';
4928
                        } else {
4929
                            $overlap_color='red';
4930
                        }
4931
                        if ($missing_color) {
4932
                            $missing_color='green';
4933
                        } else {
4934
                            $missing_color='red';
4935
                        }
4936
                        if ($excess_color) {
4937
                            $excess_color='green';
4938
                        } else {
4939
                            $excess_color='red';
4940
                        }
4941
                        if (!is_numeric($final_overlap)) {
4942
                            $final_overlap = 0;
4943
                        }
4944
                        if (!is_numeric($final_missing)) {
4945
                            $final_missing = 0;
4946
                        }
4947
                        if (!is_numeric($final_excess)) {
4948
                            $final_excess = 0;
4949
                        }
4950
4951
                        if ($final_overlap>100) {
4952
                            $final_overlap = 100;
4953
                        }
4954
4955
                        $table_resume='<table class="data_table">
4956
                                <tr class="row_odd" >
4957
                                    <td></td>
4958
                                    <td ><b>' . get_lang('Requirements') . '</b></td>
4959
                                    <td><b>' . get_lang('YourAnswer') . '</b></td>
4960
                                </tr>
4961
                                <tr class="row_even">
4962
                                    <td><b>' . get_lang('Overlap') . '</b></td>
4963
                                    <td>' . get_lang('Min') . ' ' . $threadhold1 . '</td>
4964
                                    <td><div style="color:' . $overlap_color . '">'
4965
                                        . (($final_overlap < 0) ? 0 : intval($final_overlap)) . '</div></td>
4966
                                </tr>
4967
                                <tr>
4968
                                    <td><b>' . get_lang('Excess') . '</b></td>
4969
                                    <td>' . get_lang('Max') . ' ' . $threadhold2 . '</td>
4970
                                    <td><div style="color:' . $excess_color . '">'
4971
                                        . (($final_excess < 0) ? 0 : intval($final_excess)) . '</div></td>
4972
                                </tr>
4973
                                <tr class="row_even">
4974
                                    <td><b>' . get_lang('Missing') . '</b></td>
4975
                                    <td>' . get_lang('Max') . ' ' . $threadhold3 . '</td>
4976
                                    <td><div style="color:' . $missing_color . '">'
4977
                                        . (($final_missing < 0) ? 0 : intval($final_missing)) . '</div></td>
4978
                                </tr>
4979
                            </table>';
4980 View Code Duplication
                        if ($next == 0) {
4981
                            $try = $try_hotspot;
4982
                            $lp = $lp_hotspot;
4983
                            $destinationid = $select_question_hotspot;
4984
                            $url = $url_hotspot;
4985
                        } else {
4986
                            //show if no error
4987
                            //echo 'no error';
4988
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
4989
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
4990
                        }
4991
4992
                        echo '<h1><div style="color:#333;">' . get_lang('Feedback') . '</div></h1>
4993
                            <p style="text-align:center">';
4994
4995
                        $message = '<p>' . get_lang('YourDelineation') . '</p>';
4996
                        $message .= $table_resume;
4997
                        $message .= '<br />' . get_lang('ResultIs') . ' ' . $result_comment . '<br />';
4998
                        if ($organs_at_risk_hit > 0) {
4999
                            $message .= '<p><b>' . get_lang('OARHit') . '</b></p>';
5000
                        }
5001
                        $message .='<p>' . $comment . '</p>';
5002
                        echo $message;
5003
                    } else {
5004
                        echo $hotspot_delineation_result[0]; //prints message
5005
                        $from_database = 1;  // the hotspot_solution.swf needs this variable
5006
                    }
5007
5008
                    //save the score attempts
5009
5010
                    if (1) {
5011
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5012
                        $final_answer = $hotspot_delineation_result[1];
5013
                        if ($final_answer == 0) {
5014
                            $questionScore = 0;
5015
                        }
5016
                        // we always insert the answer_id 1 = delineation
5017
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5018
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5019
                        $hotspotValue = (int) $hotspot_delineation_result[1] === 1 ? 1 : 0;
5020
                        Event::saveExerciseAttemptHotspot(
5021
                            $exeId,
5022
                            $quesId,
5023
                            1,
5024
                            $hotspotValue,
5025
                            $exerciseResultCoordinates[$quesId]
5026
                        );
5027
                    } else {
5028
                        if ($final_answer==0) {
5029
                            $questionScore = 0;
5030
                            $answer=0;
5031
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5032
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5033
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
5034
                                    Event::saveExerciseAttemptHotspot(
5035
                                        $exeId,
5036
                                        $quesId,
5037
                                        $idx,
5038
                                        0,
5039
                                        $val
5040
                                    );
5041
                                }
5042
                            }
5043
                        } else {
5044
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5045
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5046
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
5047
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5048
                                    Event::saveExerciseAttemptHotspot(
5049
                                        $exeId,
5050
                                        $quesId,
5051
                                        $idx,
5052
                                        $hotspotValue,
5053
                                        $val
5054
                                    );
5055
                                }
5056
                            }
5057
                        }
5058
                    }
5059
                    $my_exe_id = $exeId;
5060
                }
5061
            }
5062
5063
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5064
                // We made an extra table for the answers
5065
5066
                if ($show_result) {
5067
                    $relPath = api_get_path(WEB_CODE_PATH);
5068
                    //	if ($origin != 'learnpath') {
5069
                    echo '</table></td></tr>';
5070
                    echo "
5071
                        <tr>
5072
                            <td colspan=\"2\">
5073
                                <p><em>" . get_lang('HotSpot') . "</em></p>
5074
                                <div id=\"hotspot-solution-$questionId\"></div>
5075
                                <script>
5076
                                    $(document).on('ready', function () {
5077
                                        new HotspotQuestion({
5078
                                            questionId: $questionId,
5079
                                            exerciseId: $exeId,
5080
                                            selector: '#hotspot-solution-$questionId',
5081
                                            for: 'solution',
5082
                                            relPath: '$relPath'
5083
                                        });
5084
                                    });
5085
                                </script>
5086
                            </td>
5087
                        </tr>
5088
                    ";
5089
                    //	}
5090
                }
5091
            }
5092
5093
            //if ($origin != 'learnpath') {
5094
            if ($show_result) {
5095
                echo '</table>';
5096
            }
5097
            //	}
5098
        }
5099
        unset ($objAnswerTmp);
5100
5101
        $totalWeighting += $questionWeighting;
5102
        // Store results directly in the database
5103
        // For all in one page exercises, the results will be
5104
        // stored by exercise_results.php (using the session)
5105
5106
        if ($saved_results) {
5107
            if ($debug) error_log("Save question results $saved_results");
5108
            if ($debug) error_log(print_r($choice ,1 ));
5109
5110
            if (empty($choice)) {
5111
                $choice = 0;
5112
            }
5113
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5114
                if ($choice != 0) {
5115
                    $reply = array_keys($choice);
5116
                    for ($i = 0; $i < sizeof($reply); $i++) {
5117
                        $ans = $reply[$i];
5118
                        Event::saveQuestionAttempt(
5119
                            $questionScore,
5120
                            $ans . ':' . $choice[$ans],
5121
                            $quesId,
5122
                            $exeId,
5123
                            $i,
5124
                            $this->id
5125
                        );
5126
                        if ($debug) {
5127
                            error_log('result =>' . $questionScore . ' ' . $ans . ':' . $choice[$ans]);
5128
                        }
5129
                    }
5130
                } else {
5131
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5132
                }
5133
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5134
                if ($choice != 0) {
5135
                    $reply = array_keys($choice);
5136
5137
                    if ($debug) {
5138
                        error_log("reply " . print_r($reply, 1) . "");
5139
                    }
5140 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5141
                        $ans = $reply[$i];
5142
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5143
                    }
5144
                } else {
5145
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5146
                }
5147
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5148
                if ($choice != 0) {
5149
                    $reply = array_keys($choice);
5150 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5151
                        $ans = $reply[$i];
5152
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5153
                    }
5154
                } else {
5155
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5156
                }
5157
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5158
                if (isset($matching)) {
5159
                    foreach ($matching as $j => $val) {
5160
                        Event::saveQuestionAttempt($questionScore, $val, $quesId, $exeId, $j, $this->id);
5161
                    }
5162
                }
5163
            } elseif ($answerType == FREE_ANSWER) {
5164
                $answer = $choice;
5165
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5166
            } elseif ($answerType == ORAL_EXPRESSION) {
5167
                $answer = $choice;
5168
                Event::saveQuestionAttempt(
5169
                    $questionScore,
5170
                    $answer,
5171
                    $quesId,
5172
                    $exeId,
5173
                    0,
5174
                    $this->id,
5175
                    false,
5176
                    $objQuestionTmp->getAbsoluteFilePath()
5177
                );
5178
            } elseif (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION])) {
5179
                $answer = $choice;
5180
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5181
                //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
5182
            } elseif ($answerType == HOT_SPOT) {
5183
                $answer = [];
5184
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5185
                    Database::delete(
5186
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5187
                        [
5188
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5189
                                $exeId,
5190
                                $questionId,
5191
                                api_get_course_int_id()
5192
                            ]
5193
                        ]
5194
                    );
5195
5196
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5197
                        $answer[] = $val;
5198
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5199
                        Event::saveExerciseAttemptHotspot(
5200
                            $exeId,
5201
                            $quesId,
5202
                            $idx,
5203
                            $hotspotValue,
5204
                            $val,
5205
                            false,
5206
                            $this->id
5207
                        );
5208
                    }
5209
                }
5210
5211
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5212
            } else {
5213
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0,$this->id);
5214
            }
5215
        }
5216
5217
        if ($propagate_neg == 0 && $questionScore < 0) {
5218
            $questionScore = 0;
5219
        }
5220
5221
        if ($saved_results) {
5222
            $stat_table = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5223
            $sql = 'UPDATE ' . $stat_table . ' SET
5224
                        exe_result = exe_result + ' . floatval($questionScore) . '
5225
                    WHERE exe_id = ' . $exeId;
5226
            Database::query($sql);
5227
        }
5228
5229
        $return_array = array(
5230
            'score'         => $questionScore,
5231
            'weight'        => $questionWeighting,
5232
            'extra'         => $extra_data,
5233
            'open_question' => $arrques,
5234
            'open_answer'   => $arrans,
5235
            'answer_type'   => $answerType
5236
        );
5237
5238
        return $return_array;
5239
    }
5240
5241
    /**
5242
     * Sends a notification when a user ends an examn
5243
     *
5244
     * @param array $question_list_answers
5245
     * @param string $origin
5246
     * @param int $exe_id
5247
     * @param float $score
5248
     * @param float $weight
5249
     * @return bool
5250
     */
5251
    public function send_mail_notification_for_exam($question_list_answers, $origin, $exe_id, $score, $weight)
5252
    {
5253
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1) {
5254
            return false;
5255
        }
5256
5257
        // Email configuration settings
5258
        $courseCode = api_get_course_id();
5259
        $courseInfo = api_get_course_info($courseCode);
5260
        $sessionId = api_get_session_id();
5261
5262
        if (empty($courseInfo)) {
5263
            return false;
5264
        }
5265
5266
        $url_email = api_get_path(WEB_CODE_PATH)
5267
            . 'exercise/exercise_show.php?'
5268
            . api_get_cidreq()
5269
            . '&id_session='
5270
            . $sessionId
5271
            . '&id='
5272
            . $exe_id
5273
            . '&action=qualify';
5274
        $user_info = api_get_user_info(api_get_user_id());
5275
5276
        $scoreLabel = '';
5277
        if (api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true) {
5278
            $scoreLabel = ExerciseLib::show_score($score, $weight, false, true);
5279
            $scoreLabel = "<tr>
5280
                            <td>".get_lang('Score')."</td>
5281
                            <td>&nbsp;$scoreLabel</td>
5282
                        </tr>";
5283
        }
5284
5285
        $msg = get_lang('ExerciseAttempted').'<br /><br />'
5286
                    .get_lang('AttemptDetails').' : <br /><br />
5287
                    <table>
5288
                        <tr>
5289
                            <td><em>'.get_lang('CourseName').'</em></td>
5290
                            <td>&nbsp;<b>#course#</b></td>
5291
                        </tr>
5292
                        <tr>
5293
                            <td>'.get_lang('TestAttempted').'</td>
5294
                            <td>&nbsp;#exercise#</td>
5295
                        </tr>
5296
                        <tr>
5297
                            <td>'.get_lang('StudentName').'</td>
5298
                            <td>&nbsp;#firstName# #lastName#</td>
5299
                        </tr>
5300
                        <tr>
5301
                            <td>'.get_lang('StudentEmail').'</td>
5302
                            <td>&nbsp;#email#</td>
5303
                        </tr>
5304
                        '.$scoreLabel.'
5305
                    </table>';
5306
        $open_question_list = null;
5307
5308
        $msg = str_replace("#email#", $user_info['email'], $msg);
5309
        $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5310
        $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5311
        $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5312
        $msg = str_replace("#course#", $courseInfo['name'], $msg1);
5313
5314
        if ($origin != 'learnpath') {
5315
            $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5316
        }
5317
        $msg1 = str_replace("#url#", $url_email, $msg);
5318
        $mail_content = $msg1;
5319
        $subject = get_lang('ExerciseAttempted');
5320
5321
        if (!empty($sessionId)) {
5322
            $addGeneralCoach = true;
5323
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
5324
            if ($setting === true) {
5325
                $addGeneralCoach = false;
5326
            }
5327
            $teachers = CourseManager::get_coach_list_from_course_code(
5328
                $courseCode,
5329
                $sessionId,
5330
                $addGeneralCoach
5331
            );
5332
        } else {
5333
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5334
        }
5335
5336
        if (!empty($teachers)) {
5337
            foreach ($teachers as $user_id => $teacher_data) {
5338
                MessageManager::send_message_simple(
5339
                    $user_id,
5340
                    $subject,
5341
                    $mail_content
5342
                );
5343
            }
5344
        }
5345
    }
5346
5347
    /**
5348
     * Sends a notification when a user ends an examn
5349
     *
5350
     * @param integer $exe_id
5351
     */
5352
    public function send_notification_for_open_questions($question_list_answers, $origin, $exe_id)
5353
    {
5354
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5355
            return null;
5356
        }
5357
        // Email configuration settings
5358
        $courseCode     = api_get_course_id();
5359
        $course_info    = api_get_course_info($courseCode);
5360
5361
        $url_email = api_get_path(WEB_CODE_PATH)
5362
            . 'exercise/exercise_show.php?'
5363
            . api_get_cidreq()
5364
            . '&id_session='
5365
            . api_get_session_id()
5366
            . '&id='
5367
            . $exe_id
5368
            . '&action=qualify';
5369
        $user_info = api_get_user_info(api_get_user_id());
5370
5371
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
5372
                    .get_lang('AttemptDetails').' : <br /><br />'
5373
                    .'<table>'
5374
                        .'<tr>'
5375
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5376
                            .'<td>&nbsp;<b>#course#</b></td>'
5377
                        .'</tr>'
5378
                        .'<tr>'
5379
                            .'<td>'.get_lang('TestAttempted').'</td>'
5380
                            .'<td>&nbsp;#exercise#</td>'
5381
                        .'</tr>'
5382
                        .'<tr>'
5383
                            .'<td>'.get_lang('StudentName').'</td>'
5384
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5385
                        .'</tr>'
5386
                        .'<tr>'
5387
                            .'<td>'.get_lang('StudentEmail').'</td>'
5388
                            .'<td>&nbsp;#mail#</td>'
5389
                        .'</tr>'
5390
                    .'</table>';
5391
        $open_question_list = null;
5392 View Code Duplication
        foreach ($question_list_answers as $item) {
5393
            $question    = $item['question'];
5394
            $answer      = $item['answer'];
5395
            $answer_type = $item['answer_type'];
5396
5397
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
5398
                $open_question_list .=
5399
                    '<tr>'
5400
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5401
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5402
                    .'</tr>'
5403
                    .'<tr>'
5404
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5405
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5406
                    .'</tr>';
5407
            }
5408
        }
5409
5410
        if (!empty($open_question_list)) {
5411
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
5412
                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
5413
            $msg .= $open_question_list;
5414
            $msg .= '</table><br />';
5415
5416
5417
            $msg1   = str_replace("#exercise#",    $this->exercise, $msg);
5418
            $msg    = str_replace("#firstName#",   $user_info['firstname'],$msg1);
5419
            $msg1   = str_replace("#lastName#",    $user_info['lastname'],$msg);
5420
            $msg    = str_replace("#mail#",        $user_info['email'],$msg1);
5421
            $msg    = str_replace("#course#",      $course_info['name'],$msg1);
5422
5423
            if ($origin != 'learnpath') {
5424
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5425
            }
5426
            $msg1 = str_replace("#url#", $url_email, $msg);
5427
            $mail_content = $msg1;
5428
            $subject = get_lang('OpenQuestionsAttempted');
5429
5430 View Code Duplication
            if (api_get_session_id()) {
5431
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5432
            } else {
5433
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5434
            }
5435
5436
            if (!empty($teachers)) {
5437
                foreach ($teachers as $user_id => $teacher_data) {
5438
                    MessageManager::send_message_simple(
5439
                        $user_id,
5440
                        $subject,
5441
                        $mail_content
5442
                    );
5443
                }
5444
            }
5445
        }
5446
    }
5447
5448
    function send_notification_for_oral_questions($question_list_answers, $origin, $exe_id)
5449
    {
5450
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5451
            return null;
5452
        }
5453
        // Email configuration settings
5454
        $courseCode     = api_get_course_id();
5455
        $course_info    = api_get_course_info($courseCode);
5456
5457
        $url_email = api_get_path(WEB_CODE_PATH)
5458
            . 'exercise/exercise_show.php?'
5459
            . api_get_cidreq()
5460
            . '&id_session='
5461
            . api_get_session_id()
5462
            . '&id='
5463
            . $exe_id
5464
            . '&action=qualify';
5465
        $user_info = api_get_user_info(api_get_user_id());
5466
5467
        $oral_question_list = null;
5468 View Code Duplication
        foreach ($question_list_answers as $item) {
5469
            $question    = $item['question'];
5470
            $answer      = $item['answer'];
5471
            $answer_type = $item['answer_type'];
5472
5473
            if (!empty($question) && !empty($answer) && $answer_type == ORAL_EXPRESSION) {
5474
                $oral_question_list.='<br /><table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'
5475
                    .'<tr>'
5476
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5477
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5478
                    .'</tr>'
5479
                    .'<tr>'
5480
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5481
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5482
                    .'</tr></table>';
5483
            }
5484
        }
5485
5486
        if (!empty($oral_question_list)) {
5487
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
5488
                    '.get_lang('AttemptDetails').' : <br /><br />'
5489
                    .'<table>'
5490
                        .'<tr>'
5491
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5492
                            .'<td>&nbsp;<b>#course#</b></td>'
5493
                        .'</tr>'
5494
                        .'<tr>'
5495
                            .'<td>'.get_lang('TestAttempted').'</td>'
5496
                            .'<td>&nbsp;#exercise#</td>'
5497
                        .'</tr>'
5498
                        .'<tr>'
5499
                            .'<td>'.get_lang('StudentName').'</td>'
5500
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5501
                        .'</tr>'
5502
                        .'<tr>'
5503
                            .'<td>'.get_lang('StudentEmail').'</td>'
5504
                            .'<td>&nbsp;#mail#</td>'
5505
                        .'</tr>'
5506
                    .'</table>';
5507
            $msg .=  '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'),$oral_question_list).'<br />';
5508
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5509
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5510
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5511
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5512
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5513
5514
            if ($origin != 'learnpath') {
5515
                $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5516
            }
5517
            $msg1 = str_replace("#url#", $url_email, $msg);
5518
            $mail_content = $msg1;
5519
            $subject = get_lang('OralQuestionsAttempted');
5520
5521 View Code Duplication
            if (api_get_session_id()) {
5522
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5523
            } else {
5524
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5525
            }
5526
5527
            if (!empty($teachers)) {
5528
                foreach ($teachers as $user_id => $teacher_data) {
5529
                    MessageManager::send_message_simple(
5530
                        $user_id,
5531
                        $subject,
5532
                        $mail_content
5533
                    );
5534
                }
5535
            }
5536
        }
5537
    }
5538
5539
    /**
5540
     * @param array $user_data result of api_get_user_info()
5541
     * @param string $start_date
5542
     * @param null $duration
5543
     * @param string $ip Optional. The user IP
5544
     * @return string
5545
     */
5546
    public function show_exercise_result_header($user_data, $start_date = null, $duration = null, $ip = null)
5547
    {
5548
        $array = array();
5549
5550
        if (!empty($user_data)) {
5551
            $array[] = array('title' => get_lang('Name'), 'content' => $user_data['complete_name']);
5552
            $array[] = array('title' => get_lang('Username'), 'content' => $user_data['username']);
5553
            if (!empty($user_data['official_code'])) {
5554
                $array[] = array(
5555
                    'title' => get_lang('OfficialCode'),
5556
                    'content' => $user_data['official_code']
5557
                );
5558
            }
5559
        }
5560
        // Description can be very long and is generally meant to explain
5561
        //   rules *before* the exam. Leaving here to make display easier if
5562
        //   necessary
5563
        /*
5564
        if (!empty($this->description)) {
5565
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
5566
        }
5567
        */
5568 View Code Duplication
        if (!empty($start_date)) {
5569
            $array[] = array('title' => get_lang('StartDate'), 'content' => $start_date);
5570
        }
5571
5572 View Code Duplication
        if (!empty($duration)) {
5573
            $array[] = array('title' => get_lang('Duration'), 'content' => $duration);
5574
        }
5575
5576 View Code Duplication
        if (!empty($ip)) {
5577
            $array[] = array('title' => get_lang('IP'), 'content' => $ip);
5578
        }
5579
        $html  = '<div class="question-result">';
5580
        $html .= Display::page_header(
5581
            Display::return_icon('test-quiz.png', get_lang('Result'),null, ICON_SIZE_MEDIUM).' '.$this->exercise.' : '.get_lang('Result')
5582
        );
5583
        $html .= Display::description($array);
5584
        $html .="</div>";
5585
        return $html;
5586
    }
5587
5588
    /**
5589
     * Create a quiz from quiz data
5590
     * @param string  Title
5591
     * @param int     Time before it expires (in minutes)
5592
     * @param int     Type of exercise
5593
     * @param int     Whether it's randomly picked questions (1) or not (0)
5594
     * @param int     Whether the exercise is visible to the user (1) or not (0)
5595
     * @param int     Whether the results are show to the user (0) or not (1)
5596
     * @param int     Maximum number of attempts (0 if no limit)
5597
     * @param int     Feedback type
5598
     * @todo this was function was added due the import exercise via CSV
5599
     * @return    int New exercise ID
5600
     */
5601
    public function createExercise(
5602
        $title,
5603
        $expired_time = 0,
5604
        $type = 2,
5605
        $random = 0,
5606
        $active = 1,
5607
        $results_disabled = 0,
5608
        $max_attempt = 0,
5609
        $feedback = 3,
5610
        $propagateNegative = 0
5611
    ) {
5612
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
5613
        $type = intval($type);
5614
        $random = intval($random);
5615
        $active = intval($active);
5616
        $results_disabled = intval($results_disabled);
5617
        $max_attempt = intval($max_attempt);
5618
        $feedback = intval($feedback);
5619
        $expired_time = intval($expired_time);
5620
        $title = Database::escape_string($title);
5621
        $propagateNegative = intval($propagateNegative);
5622
        $sessionId = api_get_session_id();
5623
        $course_id = api_get_course_int_id();
5624
        // Save a new quiz
5625
        $sql = "INSERT INTO $tbl_quiz (
5626
                c_id,
5627
                title,
5628
                type,
5629
                random,
5630
                active,
5631
                results_disabled,
5632
                max_attempt,
5633
                start_time,
5634
                end_time,
5635
                feedback_type,
5636
                expired_time,
5637
                session_id,
5638
                propagate_neg
5639
            )
5640
            VALUES (
5641
                '$course_id',
5642
                '$title',
5643
                $type,
5644
                $random,
5645
                $active,
5646
                $results_disabled,
5647
                $max_attempt,
5648
                '',
5649
                '',
5650
                $feedback,
5651
                $expired_time,
5652
                $sessionId,
5653
                $propagateNegative
5654
            )";
5655
        Database::query($sql);
5656
        $quiz_id = Database::insert_id();
5657
5658
        if ($quiz_id) {
5659
5660
            $sql = "UPDATE $tbl_quiz SET id = iid WHERE iid = {$quiz_id} ";
5661
            Database::query($sql);
5662
        }
5663
5664
        return $quiz_id;
5665
    }
5666
5667
    function process_geometry()
5668
    {
5669
5670
    }
5671
5672
    /**
5673
     * Returns the exercise result
5674
     * @param 	int		attempt id
5675
     * @return 	float 	exercise result
5676
     */
5677
    public function get_exercise_result($exe_id)
5678
    {
5679
        $result = array();
5680
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
5681
5682
        if (!empty($track_exercise_info)) {
5683
            $totalScore = 0;
5684
            $objExercise = new Exercise();
5685
            $objExercise->read($track_exercise_info['exe_exo_id']);
5686
            if (!empty($track_exercise_info['data_tracking'])) {
5687
                $question_list = explode(',', $track_exercise_info['data_tracking']);
5688
            }
5689
            foreach ($question_list as $questionId) {
5690
                $question_result = $objExercise->manage_answer(
5691
                    $exe_id,
5692
                    $questionId,
5693
                    '',
5694
                    'exercise_show',
5695
                    array(),
5696
                    false,
5697
                    true,
5698
                    false,
5699
                    $objExercise->selectPropagateNeg()
5700
                );
5701
                $totalScore      += $question_result['score'];
5702
            }
5703
5704
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
5705
                $totalScore = 0;
5706
            }
5707
            $result = array(
5708
                'score' => $totalScore,
5709
                'weight' => $track_exercise_info['exe_weighting']
5710
            );
5711
        }
5712
        return $result;
5713
    }
5714
5715
    /**
5716
     * Checks if the exercise is visible due a lot of conditions
5717
     * visibility, time limits, student attempts
5718
     * Return associative array
5719
     * value : true if execise visible
5720
     * message : HTML formated message
5721
     * rawMessage : text message
5722
     * @param int $lpId
5723
     * @param int $lpItemId
5724
     * @param int $lpItemViewId
5725
     * @param bool $filterByAdmin
5726
     * @return array
5727
     */
5728
    public function is_visible(
5729
        $lpId = 0,
5730
        $lpItemId = 0,
5731
        $lpItemViewId = 0,
5732
        $filterByAdmin = true
5733
    ) {
5734
        // 1. By default the exercise is visible
5735
        $isVisible = true;
5736
        $message = null;
5737
5738
        // 1.1 Admins and teachers can access to the exercise
5739
        if ($filterByAdmin) {
5740
            if (api_is_platform_admin() || api_is_course_admin()) {
5741
                return array('value' => true, 'message' => '');
5742
            }
5743
        }
5744
5745
        // Deleted exercise.
5746 View Code Duplication
        if ($this->active == -1) {
5747
            return array(
5748
                'value' => false,
5749
                'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5750
                'rawMessage' => get_lang('ExerciseNotFound')
5751
            );
5752
        }
5753
5754
        // Checking visibility in the item_property table.
5755
        $visibility = api_get_item_visibility(
5756
            api_get_course_info(),
5757
            TOOL_QUIZ,
5758
            $this->id,
5759
            api_get_session_id()
5760
        );
5761
5762
        if ($visibility == 0 || $visibility == 2) {
5763
            $this->active = 0;
5764
        }
5765
5766
        // 2. If the exercise is not active.
5767
        if (empty($lpId)) {
5768
            // 2.1 LP is OFF
5769 View Code Duplication
            if ($this->active == 0) {
5770
                return array(
5771
                    'value' => false,
5772
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5773
                    'rawMessage' => get_lang('ExerciseNotFound')
5774
                );
5775
            }
5776
        } else {
5777
            // 2.1 LP is loaded
5778
            if ($this->active == 0 && !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())) {
5779
                return array(
5780
                    'value' => false,
5781
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5782
                    'rawMessage' => get_lang('ExerciseNotFound')
5783
                );
5784
            }
5785
        }
5786
5787
        //3. We check if the time limits are on
5788
        if (!empty($this->start_time) || !empty($this->end_time)) {
5789
            $limitTimeExists = true;
5790
        } else {
5791
            $limitTimeExists = false;
5792
        }
5793
5794
        if ($limitTimeExists) {
5795
            $timeNow = time();
5796
5797
            $existsStartDate = false;
5798
            $nowIsAfterStartDate = true;
5799
            $existsEndDate = false;
5800
            $nowIsBeforeEndDate = true;
5801
5802
            if (!empty($this->start_time)) {
5803
                $existsStartDate = true;
5804
            }
5805
5806
            if (!empty($this->end_time)) {
5807
                $existsEndDate = true;
5808
            }
5809
5810
            // check if we are before-or-after end-or-start date
5811
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
5812
                $nowIsAfterStartDate = false;
5813
                    }
5814
5815
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
5816
                $nowIsBeforeEndDate = false;
5817
                }
5818
5819
            // lets check all cases
5820
            if ($existsStartDate && !$existsEndDate) {
5821
                // exists start date and dont exists end date
5822
                if ($nowIsAfterStartDate) {
5823
                    // after start date, no end date
5824
                    $isVisible = true;
5825
                    $message = sprintf(get_lang('ExerciseAvailableSinceX'),
5826
                        api_convert_and_format_date($this->start_time));
5827
                } else {
5828
                    // before start date, no end date
5829
                    $isVisible = false;
5830
                    $message = sprintf(get_lang('ExerciseAvailableFromX'),
5831
                        api_convert_and_format_date($this->start_time));
5832
            }
5833
            } else if (!$existsStartDate && $existsEndDate) {
5834
                // doesnt exist start date, exists end date
5835
                if ($nowIsBeforeEndDate) {
5836
                    // before end date, no start date
5837
                    $isVisible = true;
5838
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5839
                        api_convert_and_format_date($this->end_time));
5840
                } else {
5841
                    // after end date, no start date
5842
                    $isVisible = false;
5843
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5844
                        api_convert_and_format_date($this->end_time));
5845
                }
5846
            } elseif ($existsStartDate && $existsEndDate) {
5847
                // exists start date and end date
5848
                if ($nowIsAfterStartDate) {
5849
                    if ($nowIsBeforeEndDate) {
5850
                        // after start date and before end date
5851
                        $isVisible = true;
5852
                        $message = sprintf(get_lang('ExerciseIsActivatedFromXToY'),
5853
                    api_convert_and_format_date($this->start_time),
5854
                            api_convert_and_format_date($this->end_time));
5855 View Code Duplication
                    } else {
5856
                        // after start date and after end date
5857
                        $isVisible = false;
5858
                        $message = sprintf(get_lang('ExerciseWasActivatedFromXToY'),
5859
                            api_convert_and_format_date($this->start_time),
5860
                            api_convert_and_format_date($this->end_time));
5861
                    }
5862 View Code Duplication
                } else {
5863
                    if ($nowIsBeforeEndDate) {
5864
                        // before start date and before end date
5865
                        $isVisible = false;
5866
                        $message = sprintf(get_lang('ExerciseWillBeActivatedFromXToY'),
5867
                            api_convert_and_format_date($this->start_time),
5868
                            api_convert_and_format_date($this->end_time));
5869
                    }
5870
                    // case before start date and after end date is impossible
5871
                }
5872
            } elseif (!$existsStartDate && !$existsEndDate) {
5873
                // doesnt exist start date nor end date
5874
                $isVisible = true;
5875
                $message = "";
5876
            }
5877
        }
5878
5879
        // 4. We check if the student have attempts
5880
        $exerciseAttempts = $this->selectAttempts();
5881
5882
        if ($isVisible) {
5883
            if ($exerciseAttempts > 0) {
5884
5885
                $attemptCount = Event::get_attempt_count_not_finished(
5886
                    api_get_user_id(),
5887
                    $this->id,
5888
                    $lpId,
5889
                    $lpItemId,
5890
                    $lpItemViewId
5891
                );
5892
5893
                if ($attemptCount >= $exerciseAttempts) {
5894
                    $message = sprintf(
5895
                        get_lang('ReachedMaxAttempts'),
5896
                        $this->name,
5897
                        $exerciseAttempts
5898
                    );
5899
                    $isVisible = false;
5900
                }
5901
            }
5902
        }
5903
5904
        $rawMessage = "";
5905
        if (!empty($message)){
5906
            $rawMessage = $message;
5907
            $message = Display::return_message($message, 'warning', false);
5908
        }
5909
5910
        return array(
5911
            'value' => $isVisible,
5912
            'message' => $message,
5913
            'rawMessage' => $rawMessage
5914
        );
5915
    }
5916
5917
    public function added_in_lp()
5918
    {
5919
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
5920
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
5921
            WHERE c_id = {$this->course_id} AND item_type = '" . TOOL_QUIZ . "' AND path = '{$this->id}'";
5922
        $result = Database::query($sql);
5923
        if (Database::num_rows($result) > 0) {
5924
            return true;
5925
        }
5926
        return false;
5927
    }
5928
5929
    /**
5930
     * Returns an array with the media list
5931
     * @param array question list
5932
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
5933
     * <code>
5934
     * array (size=2)
5935
     *  999 =>
5936
     *    array (size=3)
5937
     *      0 => int 7
5938
     *      1 => int 6
5939
     *      2 => int 3254
5940
     *  100 =>
5941
     *   array (size=1)
5942
     *      0 => int 5
5943
     *  </code>
5944
     * @return array
5945
     */
5946
    private function setMediaList($questionList)
5947
    {
5948
        $mediaList = array();
5949
        if (!empty($questionList)) {
5950
            foreach ($questionList as $questionId) {
5951
                $objQuestionTmp = Question::read($questionId, $this->course_id);
5952
5953
                // If a media question exists
5954
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
5955
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
5956
                } else {
5957
                    //Always the last item
5958
                    $mediaList[999][] = $objQuestionTmp->id;
5959
                }
5960
            }
5961
        }
5962
        $this->mediaList = $mediaList;
5963
    }
5964
5965
    /**
5966
     * Returns an array with this form
5967
     * @example
5968
     * <code>
5969
     * array (size=3)
5970
    999 =>
5971
    array (size=3)
5972
    0 => int 3422
5973
    1 => int 3423
5974
    2 => int 3424
5975
    100 =>
5976
    array (size=2)
5977
    0 => int 3469
5978
    1 => int 3470
5979
    101 =>
5980
    array (size=1)
5981
    0 => int 3482
5982
     * </code>
5983
     * The array inside the key 999 means the question list that belongs to the media id = 999,
5984
     * this case is special because 999 means "no media".
5985
     * @return array
5986
     */
5987
    public function getMediaList()
5988
    {
5989
        return $this->mediaList;
5990
    }
5991
5992
    /**
5993
     * Is media question activated?
5994
     * @return bool
5995
     */
5996
    public function mediaIsActivated()
5997
    {
5998
        $mediaQuestions = $this->getMediaList();
5999
        $active = false;
6000
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6001
            $media_count = count($mediaQuestions);
6002
            if ($media_count > 1) {
6003
                return true;
6004
            } elseif ($media_count == 1) {
6005
                if (isset($mediaQuestions[999])) {
6006
                    return false;
6007
                } else {
6008
                    return true;
6009
                }
6010
            }
6011
        }
6012
6013
        return $active;
6014
    }
6015
6016
    /**
6017
     * Gets question list from the exercise
6018
     *
6019
     * @return array
6020
     */
6021
    public function getQuestionList()
6022
    {
6023
        return $this->questionList;
6024
    }
6025
6026
    /**
6027
     * Question list with medias compressed like this
6028
     * @example
6029
     * <code>
6030
     * array(
6031
     *      question_id_1,
6032
     *      question_id_2,
6033
     *      media_id, <- this media id contains question ids
6034
     *      question_id_3,
6035
     * )
6036
     * </code>
6037
     * @return array
6038
     */
6039
    public function getQuestionListWithMediasCompressed()
6040
    {
6041
        return $this->questionList;
6042
    }
6043
6044
    /**
6045
     * Question list with medias uncompressed like this
6046
     * @example
6047
     * <code>
6048
     * array(
6049
     *      question_id,
6050
     *      question_id,
6051
     *      question_id, <- belongs to a media id
6052
     *      question_id, <- belongs to a media id
6053
     *      question_id,
6054
     * )
6055
     * </code>
6056
     * @return array
6057
     */
6058
    public function getQuestionListWithMediasUncompressed()
6059
    {
6060
        return $this->questionListUncompressed;
6061
    }
6062
6063
    /**
6064
     * Sets the question list when the exercise->read() is executed
6065
     */
6066
    public function setQuestionList()
6067
    {
6068
        // Getting question list.
6069
        $questionList = $this->selectQuestionList(true);
6070
6071
        $this->setMediaList($questionList);
6072
6073
        $this->questionList = $this->transformQuestionListWithMedias($questionList, false);
6074
        $this->questionListUncompressed = $this->transformQuestionListWithMedias($questionList, true);
6075
    }
6076
6077
    /**
6078
     *
6079
     * @params array question list
6080
     * @params bool expand or not question list (true show all questions, false show media question id instead of the question ids)
6081
     *
6082
     **/
6083 View Code Duplication
    public function transformQuestionListWithMedias($question_list, $expand_media_questions = false)
6084
    {
6085
        $new_question_list = array();
6086
        if (!empty($question_list)) {
6087
            $media_questions = $this->getMediaList();
6088
6089
            $media_active = $this->mediaIsActivated($media_questions);
6090
6091
            if ($media_active) {
6092
                $counter = 1;
6093
                foreach ($question_list as $question_id) {
6094
                    $add_question = true;
6095
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6096
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6097
                            $add_question = false;
6098
                            if (!in_array($media_id, $new_question_list)) {
6099
                                $new_question_list[$counter] = $media_id;
6100
                                $counter++;
6101
                            }
6102
                            break;
6103
                        }
6104
                    }
6105
                    if ($add_question) {
6106
                        $new_question_list[$counter] = $question_id;
6107
                        $counter++;
6108
                    }
6109
                }
6110
                if ($expand_media_questions) {
6111
                    $media_key_list = array_keys($media_questions);
6112
                    foreach ($new_question_list as &$question_id) {
6113
                        if (in_array($question_id, $media_key_list)) {
6114
                            $question_id = $media_questions[$question_id];
6115
                        }
6116
                    }
6117
                    $new_question_list = array_flatten($new_question_list);
6118
                }
6119
            } else {
6120
                $new_question_list = $question_list;
6121
            }
6122
        }
6123
6124
        return $new_question_list;
6125
    }
6126
6127
    function get_validated_question_list()
6128
    {
6129
        $tabres = array();
6130
        $isRandomByCategory = $this->isRandomByCat();
6131
        if ($isRandomByCategory == 0) {
6132
            if ($this->isRandom()) {
6133
                $tabres = $this->selectRandomList();
6134
            } else {
6135
                $tabres = $this->selectQuestionList();
6136
            }
6137
        } else {
6138
            if ($this->isRandom()) {
6139
                // USE question categories
6140
                // get questions by category for this exercise
6141
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6142
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6143
                // value is the array of question id of this category
6144
                $questionList = array();
6145
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6146
                $isRandomByCategory = $this->selectRandomByCat();
6147
                // We sort categories based on the term between [] in the head
6148
                // of the category's description
6149
                /* examples of categories :
6150
                 * [biologie] Maitriser les mecanismes de base de la genetique
6151
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6152
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6153
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6154
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6155
                 * [chimie] Connaître les charges des particules
6156
                 * We want that in the order of the groups defined by the term
6157
                 * between brackets at the beginning of the category title
6158
                */
6159
                // If test option is Grouped By Categories
6160
                if ($isRandomByCategory == 2) {
6161
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6162
                }
6163
                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...
6164
                    $number_of_random_question = $this->random;
6165
                    if ($this->random == -1) {
6166
                        $number_of_random_question = count($this->questionList);
6167
                    }
6168
                    $questionList = array_merge(
6169
                        $questionList,
6170
                        TestCategory::getNElementsFromArray(
6171
                            $tabquestion,
6172
                            $number_of_random_question
6173
                        )
6174
                    );
6175
                }
6176
                // shuffle the question list if test is not grouped by categories
6177
                if ($isRandomByCategory == 1) {
6178
                    shuffle($questionList); // or not
6179
                }
6180
                $tabres = $questionList;
6181
            } 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...
6182
                // Problem, random by category has been selected and
6183
                // we have no $this->isRandom number of question selected
6184
                // Should not happened
6185
            }
6186
        }
6187
        return $tabres;
6188
    }
6189
6190
    function get_question_list($expand_media_questions = false)
6191
    {
6192
        $question_list = $this->get_validated_question_list();
6193
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6194
        return $question_list;
6195
    }
6196
6197 View Code Duplication
    function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6198
    {
6199
        $new_question_list = array();
6200
        if (!empty($question_list)) {
6201
            $media_questions = $this->getMediaList();
6202
            $media_active = $this->mediaIsActivated($media_questions);
6203
6204
            if ($media_active) {
6205
                $counter = 1;
6206
                foreach ($question_list as $question_id) {
6207
                    $add_question = true;
6208
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6209
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6210
                            $add_question = false;
6211
                            if (!in_array($media_id, $new_question_list)) {
6212
                                $new_question_list[$counter] = $media_id;
6213
                                $counter++;
6214
                            }
6215
                            break;
6216
                        }
6217
                    }
6218
                    if ($add_question) {
6219
                        $new_question_list[$counter] = $question_id;
6220
                        $counter++;
6221
                    }
6222
                }
6223
                if ($expand_media_questions) {
6224
                    $media_key_list = array_keys($media_questions);
6225
                    foreach ($new_question_list as &$question_id) {
6226
                        if (in_array($question_id, $media_key_list)) {
6227
                            $question_id = $media_questions[$question_id];
6228
                        }
6229
                    }
6230
                    $new_question_list = array_flatten($new_question_list);
6231
                }
6232
            } else {
6233
                $new_question_list = $question_list;
6234
            }
6235
        }
6236
        return $new_question_list;
6237
    }
6238
6239
    /**
6240
     * @param int $exe_id
6241
     * @return array|mixed
6242
     */
6243
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6244
    {
6245
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6246
        $exe_id = intval($exe_id);
6247
        $sql_track = "SELECT * FROM $track_exercises WHERE exe_id = $exe_id ";
6248
        $result = Database::query($sql_track);
6249
        $new_array = array();
6250
        if (Database::num_rows($result) > 0 ) {
6251
            $new_array = Database::fetch_array($result, 'ASSOC');
6252
6253
            $new_array['duration'] = null;
6254
6255
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6256
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6257
6258
            if (!empty($start_date) && !empty($end_date)) {
6259
                $start_date = api_strtotime($start_date, 'UTC');
0 ignored issues
show
Bug introduced by
It seems like $start_date defined by api_strtotime($start_date, 'UTC') on line 6259 can also be of type object<DateTime>; however, api_strtotime() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

    return array();
}

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

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

Loading history...
6261
                if ($start_date && $end_date) {
6262
                    $mytime = $end_date- $start_date;
6263
                    $new_learnpath_item = new learnpathItem(null);
6264
                    $time_attemp = $new_learnpath_item->get_scorm_time('js', $mytime);
6265
                    $h = get_lang('h');
6266
                    $time_attemp = str_replace('NaN', '00' . $h . '00\'00"', $time_attemp);
6267
                    $new_array['duration'] = $time_attemp;
6268
                }
6269
            }
6270
        }
6271
        return $new_array;
6272
    }
6273
6274
    public function edit_question_to_remind($exe_id, $question_id, $action = 'add')
6275
    {
6276
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6277
        $question_id = intval($question_id);
6278
        $exe_id = intval($exe_id);
6279
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6280
        if ($exercise_info) {
6281
6282
            if (empty($exercise_info['questions_to_check'])) {
6283
                if ($action == 'add') {
6284
                    $sql = "UPDATE $track_exercises SET questions_to_check = '$question_id' WHERE exe_id = $exe_id ";
6285
                    Database::query($sql);
6286
                }
6287
            } else {
6288
                $remind_list = explode(',',$exercise_info['questions_to_check']);
6289
6290
                $remind_list_string = '';
6291
                if ($action == 'add') {
6292
                    if (!in_array($question_id, $remind_list)) {
6293
                        $remind_list[] = $question_id;
6294
                        if (!empty($remind_list)) {
6295
                            sort($remind_list);
6296
                            array_filter($remind_list);
6297
                        }
6298
                        $remind_list_string = implode(',', $remind_list);
6299
                    }
6300
                } elseif ($action == 'delete')  {
6301
                    if (!empty($remind_list)) {
6302
                        if (in_array($question_id, $remind_list)) {
6303
                            $remind_list = array_flip($remind_list);
6304
                            unset($remind_list[$question_id]);
6305
                            $remind_list = array_flip($remind_list);
6306
6307
                            if (!empty($remind_list)) {
6308
                                sort($remind_list);
6309
                                array_filter($remind_list);
6310
                                $remind_list_string = implode(',', $remind_list);
6311
                            }
6312
                        }
6313
                    }
6314
                }
6315
                $remind_list_string = Database::escape_string($remind_list_string);
6316
                $sql = "UPDATE $track_exercises SET questions_to_check = '$remind_list_string' WHERE exe_id = $exe_id ";
6317
                Database::query($sql);
6318
            }
6319
        }
6320
    }
6321
6322
    public function fill_in_blank_answer_to_array($answer)
6323
    {
6324
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6325
        $teacher_answer_list = $teacher_answer_list[0];
6326
        return $teacher_answer_list;
6327
    }
6328
6329
    public function fill_in_blank_answer_to_string($answer)
6330
    {
6331
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6332
        $result = '';
6333
        if (!empty($teacher_answer_list)) {
6334
            $i = 0;
6335
            foreach ($teacher_answer_list as $teacher_item) {
6336
                $value = null;
6337
                //Cleaning student answer list
6338
                $value = strip_tags($teacher_item);
6339
                $value = api_substr($value, 1, api_strlen($value) - 2);
6340
                $value = explode('/', $value);
6341
                if (!empty($value[0])) {
6342
                    $value = trim($value[0]);
6343
                    $value = str_replace('&nbsp;', '', $value);
6344
                    $result .= $value;
6345
                }
6346
            }
6347
        }
6348
        return $result;
6349
    }
6350
6351
    function return_time_left_div()
6352
    {
6353
        $html = '<div id="clock_warning" style="display:none">';
6354
        $html .= Display::return_message(
6355
            get_lang('ReachedTimeLimit'),
6356
            'warning'
6357
        );
6358
        $html .= ' ';
6359
        $html .= sprintf(
6360
            get_lang('YouWillBeRedirectedInXSeconds'),
6361
            '<span id="counter_to_redirect" class="red_alert"></span>'
6362
        );
6363
        $html .= '</div>';
6364
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6365
        return $html;
6366
    }
6367
6368
    function get_count_question_list()
6369
    {
6370
        //Real question count
6371
        $question_count = 0;
6372
        $question_list = $this->get_question_list();
6373
        if (!empty($question_list)) {
6374
            $question_count = count($question_list);
6375
        }
6376
        return $question_count;
6377
    }
6378
6379
    function get_exercise_list_ordered()
6380
    {
6381
        $table_exercise_order = Database::get_course_table(TABLE_QUIZ_ORDER);
6382
        $course_id = api_get_course_int_id();
6383
        $session_id = api_get_session_id();
6384
        $sql = "SELECT exercise_id, exercise_order
6385
                FROM $table_exercise_order
6386
                WHERE c_id = $course_id AND session_id = $session_id
6387
                ORDER BY exercise_order";
6388
        $result = Database::query($sql);
6389
        $list = array();
6390
        if (Database::num_rows($result)) {
6391
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6392
                $list[$row['exercise_order']] = $row['exercise_id'];
6393
            }
6394
        }
6395
        return $list;
6396
    }
6397
6398
    /**
6399
     * Get categories added in the exercise--category matrix
6400
     * @return bool
6401
     */
6402
    public function get_categories_in_exercise()
6403
    {
6404
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6405
        if (!empty($this->id)) {
6406
            $sql = "SELECT * FROM $table
6407
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6408
            $result = Database::query($sql);
6409
            $list = array();
6410
            if (Database::num_rows($result)) {
6411
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6412
                    $list[$row['category_id']] = $row;
6413
                }
6414
                return $list;
6415
            }
6416
        }
6417
        return false;
6418
    }
6419
6420
    /**
6421
     * @param null $order
6422
     * @return bool
6423
     */
6424
    public function get_categories_with_name_in_exercise($order = null)
6425
    {
6426
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6427
        $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
6428
        $sql = "SELECT * FROM $table qc
6429
                INNER JOIN $table_category c
6430
                ON (category_id = c.iid)
6431
                WHERE exercise_id = {$this->id} AND qc.c_id = {$this->course_id} ";
6432
        if (!empty($order)) {
6433
            $sql .= "ORDER BY $order ";
6434
        }
6435
        $result = Database::query($sql);
6436
        if (Database::num_rows($result)) {
6437
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6438
                $list[$row['category_id']] = $row;
6439
            }
6440
            return $list;
6441
        }
6442
        return false;
6443
    }
6444
6445
    /**
6446
     * Get total number of question that will be parsed when using the category/exercise
6447
     */
6448 View Code Duplication
    public function getNumberQuestionExerciseCategory()
6449
    {
6450
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6451
        if (!empty($this->id)) {
6452
            $sql = "SELECT SUM(count_questions) count_questions
6453
                    FROM $table
6454
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6455
            $result = Database::query($sql);
6456
            if (Database::num_rows($result)) {
6457
                $row = Database::fetch_array($result);
6458
                return $row['count_questions'];
6459
            }
6460
        }
6461
        return 0;
6462
    }
6463
6464
    /**
6465
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table
6466
     * @param array $categories
6467
     */
6468
    public function save_categories_in_exercise($categories)
6469
    {
6470
        if (!empty($categories) && !empty($this->id)) {
6471
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6472
            $sql = "DELETE FROM $table
6473
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6474
            Database::query($sql);
6475
            if (!empty($categories)) {
6476
                foreach ($categories as $category_id => $count_questions) {
6477
                    $params = array(
6478
                        'c_id' => $this->course_id,
6479
                        'exercise_id' => $this->id,
6480
                        'category_id' => $category_id,
6481
                        'count_questions' => $count_questions
6482
                    );
6483
                    Database::insert($table, $params);
6484
                }
6485
            }
6486
        }
6487
    }
6488
6489
    /**
6490
     * @param array $questionList
6491
     * @param int $currentQuestion
6492
     * @param array $conditions
6493
     * @param string $link
6494
     * @return string
6495
     */
6496
    public function progressExercisePaginationBar($questionList, $currentQuestion, $conditions, $link)
6497
    {
6498
        $mediaQuestions = $this->getMediaList();
6499
6500
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6501
        $counter = 0;
6502
        $nextValue = 0;
6503
        $wasMedia = false;
6504
        $before = 0;
6505
        $counterNoMedias = 0;
6506
        foreach ($questionList as $questionId) {
6507
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6508
6509
            if (!empty($nextValue)) {
6510
                if ($wasMedia) {
6511
                    $nextValue = $nextValue - $before + 1;
6512
                }
6513
            }
6514
6515
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6516
                $fixedValue = $counterNoMedias;
6517
6518
                $html .= Display::progressPaginationBar(
6519
                    $nextValue,
6520
                    $mediaQuestions[$questionId],
6521
                    $currentQuestion,
6522
                    $fixedValue,
6523
                    $conditions,
6524
                    $link,
6525
                    true,
6526
                    true
6527
                );
6528
6529
                $counter += count($mediaQuestions[$questionId]) - 1 ;
6530
                $before = count($questionList);
6531
                $wasMedia = true;
6532
                $nextValue += count($questionList);
6533
            } else {
6534
                $html .= Display::parsePaginationItem($questionId, $isCurrent, $conditions, $link, $counter);
6535
                $counter++;
6536
                $nextValue++;
6537
                $wasMedia = false;
6538
            }
6539
            $counterNoMedias++;
6540
        }
6541
        $html .= '</ul></div>';
6542
        return $html;
6543
    }
6544
6545
6546
    /**
6547
     *  Shows a list of numbers that represents the question to answer in a exercise
6548
     *
6549
     * @param array $categories
6550
     * @param int $current
6551
     * @param array $conditions
6552
     * @param string $link
6553
     * @return string
6554
     */
6555
    public function progressExercisePaginationBarWithCategories(
6556
        $categories,
6557
        $current,
6558
        $conditions = array(),
6559
        $link = null
6560
    ) {
6561
        $html = null;
6562
        $counterNoMedias = 0;
6563
        $nextValue = 0;
6564
        $wasMedia = false;
6565
        $before = 0;
6566
6567
        if (!empty($categories)) {
6568
            $selectionType = $this->getQuestionSelectionType();
6569
            $useRootAsCategoryTitle = false;
6570
6571
            // Grouping questions per parent category see BT#6540
6572
6573
            if (in_array(
6574
                $selectionType,
6575
                array(
6576
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
6577
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM
6578
                )
6579
            )) {
6580
                $useRootAsCategoryTitle = true;
6581
            }
6582
6583
            // If the exercise is set to only show the titles of the categories
6584
            // at the root of the tree, then pre-order the categories tree by
6585
            // removing children and summing their questions into the parent
6586
            // categories
6587
6588
            if ($useRootAsCategoryTitle) {
6589
                // The new categories list starts empty
6590
                $newCategoryList = array();
6591
                foreach ($categories as $category) {
6592
                    $rootElement = $category['root'];
6593
6594
                    if (isset($category['parent_info'])) {
6595
                        $rootElement = $category['parent_info']['id'];
6596
                    }
6597
6598
                    //$rootElement = $category['id'];
6599
                    // If the current category's ancestor was never seen
6600
                    // before, then declare it and assign the current
6601
                    // category to it.
6602
                    if (!isset($newCategoryList[$rootElement])) {
6603
                        $newCategoryList[$rootElement] = $category;
6604
                    } else {
6605
                        // If it was already seen, then merge the previous with
6606
                        // the current category
6607
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
6608
                        $category['question_list'] = array_merge($oldQuestionList , $category['question_list']);
6609
                        $newCategoryList[$rootElement] = $category;
6610
                    }
6611
                }
6612
                // Now use the newly built categories list, with only parents
6613
                $categories = $newCategoryList;
6614
            }
6615
6616
            foreach ($categories as $category) {
6617
                $questionList = $category['question_list'];
6618
                // Check if in this category there questions added in a media
6619
                $mediaQuestionId = $category['media_question'];
6620
                $isMedia = false;
6621
                $fixedValue = null;
6622
6623
                // Media exists!
6624
                if ($mediaQuestionId != 999) {
6625
                    $isMedia = true;
6626
                    $fixedValue = $counterNoMedias;
6627
                }
6628
6629
                //$categoryName = $category['path']; << show the path
6630
                $categoryName = $category['name'];
6631
6632
                if ($useRootAsCategoryTitle) {
6633
                    if (isset($category['parent_info'])) {
6634
                        $categoryName  = $category['parent_info']['title'];
6635
                    }
6636
                }
6637
                $html .= '<div class="row">';
6638
                $html .= '<div class="span2">'.$categoryName.'</div>';
6639
                $html .= '<div class="span8">';
6640
6641
                if (!empty($nextValue)) {
6642
                    if ($wasMedia) {
6643
                        $nextValue = $nextValue - $before + 1;
6644
                    }
6645
                }
6646
                $html .= Display::progressPaginationBar(
6647
                    $nextValue,
6648
                    $questionList,
6649
                    $current,
6650
                    $fixedValue,
6651
                    $conditions,
6652
                    $link,
6653
                    $isMedia,
6654
                    true
6655
                );
6656
                $html .= '</div>';
6657
                $html .= '</div>';
6658
6659
                if ($mediaQuestionId == 999) {
6660
                    $counterNoMedias += count($questionList);
6661
                } else {
6662
                    $counterNoMedias++;
6663
                }
6664
6665
                $nextValue += count($questionList);
6666
                $before = count($questionList);
6667
6668
                if ($mediaQuestionId != 999) {
6669
                    $wasMedia = true;
6670
                } else {
6671
                    $wasMedia = false;
6672
                }
6673
6674
            }
6675
        }
6676
        return $html;
6677
    }
6678
6679
    /**
6680
     * Renders a question list
6681
     *
6682
     * @param array $questionList (with media questions compressed)
6683
     * @param int $currentQuestion
6684
     * @param array $exerciseResult
6685
     * @param array $attemptList
6686
     * @param array $remindList
6687
     */
6688
    public function renderQuestionList($questionList, $currentQuestion, $exerciseResult, $attemptList, $remindList)
6689
    {
6690
        $mediaQuestions = $this->getMediaList();
6691
        $i = 0;
6692
6693
        // Normal question list render (medias compressed)
6694
        foreach ($questionList as $questionId) {
6695
            $i++;
6696
            // For sequential exercises
6697
6698
            if ($this->type == ONE_PER_PAGE) {
6699
                // If it is not the right question, goes to the next loop iteration
6700
                if ($currentQuestion != $i) {
6701
                    continue;
6702
                } else {
6703
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
6704
                        // if the user has already answered this question
6705
                        if (isset($exerciseResult[$questionId])) {
6706
                            Display::display_normal_message(get_lang('AlreadyAnswered'));
0 ignored issues
show
Deprecated Code introduced by
The method Display::display_normal_message() has been deprecated with message: use Display::addFlash with Display::return_message($message, 'normal');

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...
6707
                            break;
6708
                        }
6709
                    }
6710
                }
6711
            }
6712
6713
            // The $questionList contains the media id we check if this questionId is a media question type
6714
6715
            if (isset($mediaQuestions[$questionId]) && $mediaQuestions[$questionId] != 999) {
6716
6717
                // The question belongs to a media
6718
                $mediaQuestionList = $mediaQuestions[$questionId];
6719
                $objQuestionTmp = Question::read($questionId);
6720
6721
                $counter = 1;
6722
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
6723
                    echo $objQuestionTmp->show_media_content();
6724
6725
                    $countQuestionsInsideMedia = count($mediaQuestionList);
6726
6727
                    // Show questions that belongs to a media
6728
                    if (!empty($mediaQuestionList)) {
6729
                        // In order to parse media questions we use letters a, b, c, etc.
6730
                        $letterCounter = 97;
6731
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
6732
                            $isLastQuestionInMedia = false;
6733
                            if ($counter == $countQuestionsInsideMedia) {
6734
                                $isLastQuestionInMedia = true;
6735
                            }
6736
                            $this->renderQuestion(
6737
                                $questionIdInsideMedia,
6738
                                $attemptList,
6739
                                $remindList,
6740
                                chr($letterCounter),
6741
                                $currentQuestion,
6742
                                $mediaQuestionList,
6743
                                $isLastQuestionInMedia,
6744
                                $questionList
6745
                            );
6746
                            $letterCounter++;
6747
                            $counter++;
6748
                        }
6749
                    }
6750
                } else {
6751
                    $this->renderQuestion(
6752
                        $questionId,
6753
                        $attemptList,
6754
                        $remindList,
6755
                        $i,
6756
                        $currentQuestion,
6757
                        null,
6758
                        null,
6759
                        $questionList
6760
                    );
6761
                    $i++;
6762
                }
6763
            } else {
6764
                // Normal question render.
6765
                $this->renderQuestion($questionId, $attemptList, $remindList, $i, $currentQuestion, null, null, $questionList);
6766
            }
6767
6768
            // For sequential exercises.
6769
            if ($this->type == ONE_PER_PAGE) {
6770
                // quits the loop
6771
                break;
6772
            }
6773
        }
6774
        // end foreach()
6775
6776
        if ($this->type == ALL_ON_ONE_PAGE) {
6777
            $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 6694. 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...
6778
            echo Display::div($exercise_actions, array('class'=>'exercise_actions'));
6779
        }
6780
    }
6781
6782
    /**
6783
     * @param int $questionId
6784
     * @param array $attemptList
6785
     * @param array $remindList
6786
     * @param int $i
6787
     * @param int $current_question
6788
     * @param array $questions_in_media
6789
     * @param bool $last_question_in_media
6790
     * @param array $realQuestionList
6791
     * @param bool $generateJS
6792
     * @return null
6793
     */
6794
    public function renderQuestion(
6795
        $questionId,
6796
        $attemptList,
6797
        $remindList,
6798
        $i,
6799
        $current_question,
6800
        $questions_in_media = array(),
6801
        $last_question_in_media = false,
6802
        $realQuestionList,
6803
        $generateJS = true
6804
    ) {
6805
6806
        // With this option on the question is loaded via AJAX
6807
        //$generateJS = true;
6808
        //$this->loadQuestionAJAX = true;
6809
6810
        if ($generateJS && $this->loadQuestionAJAX) {
6811
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
6812
            $params = array(
6813
                'questionId' => $questionId,
6814
                'attemptList'=> $attemptList,
6815
                'remindList' => $remindList,
6816
                'i' => $i,
6817
                'current_question' => $current_question,
6818
                'questions_in_media' => $questions_in_media,
6819
                'last_question_in_media' => $last_question_in_media
6820
            );
6821
            $params = json_encode($params);
6822
6823
            $script = '<script>
6824
            $(function(){
6825
                var params = '.$params.';
6826
                $.ajax({
6827
                    type: "GET",
6828
                    async: false,
6829
                    data: params,
6830
                    url: "'.$url.'",
6831
                    success: function(return_value) {
6832
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
6833
                    }
6834
                });
6835
            });
6836
            </script>
6837
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
6838
            echo $script;
6839
        } else {
6840
6841
            global $origin;
6842
            $question_obj = Question::read($questionId);
6843
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
6844
6845
            $remind_highlight = null;
6846
6847
            //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
6848
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
6849
                $remind_highlight = 'no_remind_highlight';
6850
                if (in_array($question_obj->type, Question::question_type_no_review())) {
6851
                    return null;
6852
                }
6853
            }
6854
6855
            $attributes = array('id' =>'remind_list['.$questionId.']');
6856
            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...
6857
                //$attributes['checked'] = 1;
6858
                //$remind_highlight = ' remind_highlight ';
6859
            }
6860
6861
            // Showing the question
6862
6863
            $exercise_actions  = null;
6864
6865
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
6866
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
6867
6868
            // Shows the question + possible answers
6869
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
6870
            echo $this->showQuestion($question_obj, false, $origin, $i, $showTitle, false, $user_choice, false, null, false, $this->getModelType(), $this->categoryMinusOne);
6871
6872
            // Button save and continue
6873 View Code Duplication
            switch ($this->type) {
6874
                case ONE_PER_PAGE:
6875
                    $exercise_actions .= $this->show_button($questionId, $current_question, null, $remindList);
6876
                    break;
6877
                case ALL_ON_ONE_PAGE:
6878
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', null, true, 1); ">'.get_lang('SaveForNow').'</a>';
6879
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6880
                    $exercise_actions .= Display::div($button, array('class'=>'exercise_save_now_button'));
6881
                    break;
6882
            }
6883
6884
            if (!empty($questions_in_media)) {
6885
                $count_of_questions_inside_media = count($questions_in_media);
6886
                if ($count_of_questions_inside_media > 1) {
6887
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', false, false, 0); ">'.get_lang('SaveForNow').'</a>';
6888
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6889
                    $exercise_actions = Display::div($button, array('class'=>'exercise_save_now_button'));
6890
                }
6891
6892
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
6893
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
6894
                }
6895
            }
6896
6897
            // Checkbox review answers
6898
            if ($this->review_answers && !in_array($question_obj->type, Question::question_type_no_review())) {
6899
                $remind_question_div = Display::tag('label', Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), array('class' => 'checkbox', 'for' =>'remind_list['.$questionId.']'));
6900
                $exercise_actions   .= Display::div($remind_question_div, array('class'=>'exercise_save_now_button'));
6901
            }
6902
6903
            echo Display::div(' ', array('class'=>'clear'));
6904
6905
            $paginationCounter = null;
6906
            if ($this->type == ONE_PER_PAGE) {
6907
                if (empty($questions_in_media)) {
6908
                    $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6909
                } else {
6910
                    if ($last_question_in_media) {
6911
                        $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6912
                    }
6913
                }
6914
            }
6915
6916
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
6917
            echo Display::div($exercise_actions, array('class'=>'form-actions'));
6918
            echo '</div>';
6919
        }
6920
    }
6921
6922
    /**
6923
     * Shows a question
6924
     * @param Question $objQuestionTmp
6925
     * @param bool $only_questions if true only show the questions, no exercise title
6926
     * @param bool $origin origin i.e = learnpath
6927
     * @param string $current_item current item from the list of questions
6928
     * @param bool $show_title
6929
     * @param bool $freeze
6930
     * @param array $user_choice
6931
     * @param bool $show_comment
6932
     * @param null $exercise_feedback
6933
     * @param bool $show_answers
6934
     * @param null $modelType
6935
     * @param bool $categoryMinusOne
6936
     * @return bool|null|string
6937
     */
6938
    public function showQuestion(
6939
        Question $objQuestionTmp,
6940
        $only_questions = false,
6941
        $origin = false,
6942
        $current_item = '',
6943
        $show_title = true,
6944
        $freeze = false,
6945
        $user_choice = array(),
6946
        $show_comment = false,
6947
        $exercise_feedback = null,
6948
        $show_answers = false,
6949
        $modelType = null,
6950
        $categoryMinusOne = true
6951
    ) {
6952
        // Text direction for the current language
6953
        //$is_ltr_text_direction = api_get_text_direction() != 'rtl';
6954
        // Change false to true in the following line to enable answer hinting
6955
        $debug_mark_answer = $show_answers; //api_is_allowed_to_edit() && false;
6956
        // Reads question information
6957
        if (!$objQuestionTmp) {
6958
            // Question not found
6959
            return false;
6960
        }
6961
6962
        $html = null;
6963
6964
        $questionId = $objQuestionTmp->id;
6965
6966
        if ($exercise_feedback != EXERCISE_FEEDBACK_TYPE_END) {
6967
            $show_comment = false;
6968
        }
6969
6970
        $answerType = $objQuestionTmp->selectType();
6971
        $pictureName = $objQuestionTmp->selectPicture();
6972
6973
        $s = null;
6974
        $form = new FormValidator('question');
6975
        $renderer = $form->defaultRenderer();
6976
        $form_template = '{content}';
6977
        $renderer->setFormTemplate($form_template);
6978
6979
        if ($answerType != HOT_SPOT && $answerType != HOT_SPOT_DELINEATION) {
6980
            // Question is not a hotspot
6981
            if (!$only_questions) {
6982
                $questionDescription = $objQuestionTmp->selectDescription();
6983
                if ($show_title) {
6984
                    $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...
6985
                    $html .= $categoryName;
6986
                    $html .= Display::div($current_item.'. '.$objQuestionTmp->selectTitle(), array('class' => 'question_title'));
6987
                    if (!empty($questionDescription)) {
6988
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
6989
                    }
6990 View Code Duplication
                } else {
6991
                    $html .= '<div class="media">';
6992
                    $html .= '<div class="pull-left">';
6993
                    $html .= '<div class="media-object">';
6994
                    $html .= Display::div($current_item, array('class' => 'question_no_title'));
6995
                    $html .= '</div>';
6996
                    $html .= '</div>';
6997
                    $html .= '<div class="media-body">';
6998
                    if (!empty($questionDescription)) {
6999
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
7000
                    }
7001
                    $html .= '</div>';
7002
                    $html .= '</div>';
7003
                }
7004
            }
7005
7006
            if (in_array($answerType, array(FREE_ANSWER, ORAL_EXPRESSION)) && $freeze) {
7007
                return null;
7008
            }
7009
7010
            $html .= '<div class="question_options">';
7011
            // construction of the Answer object (also gets all answers details)
7012
            $objAnswerTmp = new Answer($questionId, null, $this);
7013
7014
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
7015
            $course_id = api_get_course_int_id();
7016
            $sessionId = api_get_session_id();
7017
            $quiz_question_options = Question::readQuestionOption($questionId, $course_id);
7018
7019
            // For "matching" type here, we need something a little bit special
7020
            // because the match between the suggestions and the answers cannot be
7021
            // done easily (suggestions and answers are in the same table), so we
7022
            // have to go through answers first (elems with "correct" value to 0).
7023
            $select_items = array();
7024
            //This will contain the number of answers on the left side. We call them
7025
            // suggestions here, for the sake of comprehensions, while the ones
7026
            // on the right side are called answers
7027
            $num_suggestions = 0;
7028
7029
            if ($answerType == MATCHING || $answerType == DRAGGABLE) {
7030 View Code Duplication
                if ($answerType == DRAGGABLE) {
7031
                    $s .= '<div class="ui-widget ui-helper-clearfix">
7032
                            <ul class="drag_question ui-helper-reset ui-helper-clearfix">';
7033
                } else {
7034
                    $s .= '<div id="drag'.$questionId.'_question" class="drag_question">';
7035
                    $s .= '<table class="data_table">';
7036
                }
7037
7038
                $j = 1; //iterate through answers
7039
                $letter = 'A'; //mark letters for each answer
7040
                $answer_matching = array();
7041
                $capital_letter = array();
7042
                //for ($answerId=1; $answerId <= $nbrAnswers; $answerId++) {
7043
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7044
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7045
                    $answer = $objAnswerTmp->selectAnswer($answerId);
7046
                    if ($answerCorrect == 0) {
7047
                        // options (A, B, C, ...) that will be put into the list-box
7048
                        // have the "correct" field set to 0 because they are answer
7049
                        $capital_letter[$j] = $letter;
7050
                        //$answer_matching[$j]=$objAnswerTmp->selectAnswerByAutoId($numAnswer);
7051
                        $answer_matching[$j] = array('id' => $answerId, 'answer' => $answer);
7052
                        $j++;
7053
                        $letter++;
7054
                    }
7055
                }
7056
7057
                $i = 1;
7058
7059
                $select_items[0]['id'] = 0;
7060
                $select_items[0]['letter'] = '--';
7061
                $select_items[0]['answer'] = '';
7062
7063 View Code Duplication
                foreach ($answer_matching as $id => $value) {
7064
                    $select_items[$i]['id'] = $value['id'];
7065
                    $select_items[$i]['letter'] = $capital_letter[$id];
7066
                    $select_items[$i]['answer'] = $value['answer'];
7067
                    $i++;
7068
                }
7069
                $num_suggestions = ($nbrAnswers - $j) + 1;
7070
            } elseif ($answerType == FREE_ANSWER) {
7071
                $content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
7072
                $toolBar = 'TestFreeAnswer';
7073
                if ($modelType == EXERCISE_MODEL_TYPE_COMMITTEE) {
7074
                    $toolBar = 'TestFreeAnswerStrict';
7075
                }
7076
                $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => $toolBar));
7077
                $form->setDefaults(array("choice[".$questionId."]" => $content));
7078
                $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...
7079
            } elseif ($answerType == ORAL_EXPRESSION) {
7080
                // Add nanogong
7081 View Code Duplication
                if (api_get_setting('enable_record_audio') === 'true') {
7082
7083
                    //@todo pass this as a parameter
7084
                    global $exercise_stat_info, $exerciseId;
7085
7086
                    if (!empty($exercise_stat_info)) {
7087
                        $objQuestionTmp->initFile(
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Question as the method initFile() does only exist in the following sub-classes of Question: OralExpression. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
7088
                            api_get_session_id(),
7089
                            api_get_user_id(),
7090
                            $exercise_stat_info['exe_exo_id'],
7091
                            $exercise_stat_info['exe_id']
7092
                        );
7093
                    } else {
7094
                        $objQuestionTmp->initFile(
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Question as the method initFile() does only exist in the following sub-classes of Question: OralExpression. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
7095
                            api_get_session_id(),
7096
                            api_get_user_id(),
7097
                            $exerciseId,
7098
                            'temp_exe'
7099
                        );
7100
                    }
7101
7102
                    $s .= $objQuestionTmp->returnRecorder();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Question as the method returnRecorder() does only exist in the following sub-classes of Question: OralExpression. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
7103
                }
7104
7105
                $form->addElement(
7106
                    'html_editor',
7107
                    "choice[".$questionId."]",
7108
                    null,
7109
                    array('id' => "choice[".$questionId."]"),
7110
                    array('ToolbarSet' => 'TestFreeAnswer')
7111
                );
7112
                //$form->setDefaults(array("choice[".$questionId."]" => $content));
7113
                $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...
7114
            }
7115
7116
            // Now navigate through the possible answers, using the max number of
7117
            // answers for the question as a limiter
7118
            $lines_count = 1; // a counter for matching-type answers
7119
7120
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7121
                $header = Display::tag('th', get_lang('Options'));
7122
                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...
7123
                    $header .= Display::tag('th', $item);
7124
                }
7125
                if ($show_comment) {
7126
                    $header .= Display::tag('th', get_lang('Feedback'));
7127
                }
7128
                $s .= '<table class="data_table">';
7129
                $s .= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7130
            }
7131
7132 View Code Duplication
            if ($show_comment) {
7133
                if (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_COMBINATION, UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION, GLOBAL_MULTIPLE_ANSWER))) {
7134
                    $header = Display::tag('th', get_lang('Options'));
7135
                    if ($exercise_feedback == EXERCISE_FEEDBACK_TYPE_END) {
7136
                        $header .= Display::tag('th', get_lang('Feedback'));
7137
                    }
7138
                    $s .= '<table class="data_table">';
7139
                    $s.= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7140
                }
7141
            }
7142
7143
            $matching_correct_answer = 0;
7144
            $user_choice_array = array();
7145
            if (!empty($user_choice)) {
7146
                foreach ($user_choice as $item) {
7147
                    $user_choice_array[] = $item['answer'];
7148
                }
7149
            }
7150
7151
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7152
                $answer = $objAnswerTmp->selectAnswer($answerId);
7153
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7154
                $comment = $objAnswerTmp->selectComment($answerId);
7155
7156
                //$numAnswer       = $objAnswerTmp->selectAutoId($answerId);
7157
                $numAnswer = $answerId;
7158
7159
                $attributes = array();
7160
                // Unique answer
7161
                if (in_array($answerType, array(UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION))) {
7162
7163
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7164
                    if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
7165
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7166
                    } else {
7167
                        $attributes = array('id' => $input_id);
7168
                    }
7169
7170
                    if ($debug_mark_answer) {
7171
                        if ($answerCorrect) {
7172
                            $attributes['checked'] = 1;
7173
                            $attributes['selected'] = 1;
7174
                        }
7175
                    }
7176
7177
                    $answer = Security::remove_XSS($answer);
7178
                    $s .= Display::input('hidden', 'choice2['.$questionId.']', '0');
7179
7180
                    $answer_input = null;
7181
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7182
                        $attributes['style'] = 'display:none';
7183
                        $answer_input .= '<div id="answer'.$questionId.$numAnswer.'" style="float:left" class="highlight_image_default highlight_image">';
7184
                    }
7185
7186
                    $answer_input .= '<label class="radio">';
7187
                    $answer_input .= Display::input('radio', 'choice['.$questionId.']', $numAnswer, $attributes);
7188
                    $answer_input .= $answer;
7189
                    $answer_input .= '</label>';
7190
7191
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7192
                        $answer_input .= "</div>";
7193
                    }
7194
7195
                    if ($show_comment) {
7196
                        $s .= '<tr><td>';
7197
                        $s .= $answer_input;
7198
                        $s .= '</td>';
7199
                        $s .= '<td>';
7200
                        $s .= $comment;
7201
                        $s .= '</td>';
7202
                        $s .= '</tr>';
7203
                    } else {
7204
                        $s .= $answer_input;
7205
                    }
7206
7207
                } elseif (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_TRUE_FALSE, GLOBAL_MULTIPLE_ANSWER))) {
7208
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7209
                    $answer = Security::remove_XSS($answer);
7210
7211
                    if (in_array($numAnswer, $user_choice_array)) {
7212
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7213
                    } else {
7214
                        $attributes = array('id' => $input_id);
7215
                    }
7216
7217
                    if ($debug_mark_answer) {
7218
                        if ($answerCorrect) {
7219
                            $attributes['checked'] = 1;
7220
                            $attributes['selected'] = 1;
7221
                        }
7222
                    }
7223
7224 View Code Duplication
                    if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
7225
                        $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7226
7227
                        $answer_input = '<label class="checkbox">';
7228
                        $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', $numAnswer, $attributes);
7229
                        $answer_input .= $answer;
7230
                        $answer_input .= '</label>';
7231
7232
                        if ($show_comment) {
7233
                            $s .= '<tr><td>';
7234
                            $s .= $answer_input;
7235
                            $s .= '</td>';
7236
                            $s .= '<td>';
7237
                            $s .= $comment;
7238
                            $s .= '</td>';
7239
                            $s .='</tr>';
7240
                        } else {
7241
                            $s .= $answer_input;
7242
                        }
7243
                    } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
7244
7245
                        $my_choice = array();
7246
                        if (!empty($user_choice_array)) {
7247
                            foreach ($user_choice_array as $item) {
7248
                                $item = explode(':', $item);
7249
                                $my_choice[$item[0]] = $item[1];
7250
                            }
7251
                        }
7252
7253
                        $s .='<tr>';
7254
                        $s .= Display::tag('td', $answer);
7255
7256
                        if (!empty($quiz_question_options)) {
7257
                            foreach ($quiz_question_options as $id => $item) {
7258
                                $id = $item['iid'];
7259
                                if (isset($my_choice[$numAnswer]) && $id == $my_choice[$numAnswer]) {
7260
                                    $attributes = array('checked' => 1, 'selected' => 1);
7261
                                } else {
7262
                                    $attributes = array();
7263
                                }
7264
7265
                                if ($debug_mark_answer) {
7266
                                    if ($id == $answerCorrect) {
7267
                                        $attributes['checked'] = 1;
7268
                                        $attributes['selected'] = 1;
7269
                                    }
7270
                                }
7271
                                $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $id, $attributes), array('style' => ''));
7272
                            }
7273
                        }
7274
7275
                        if ($show_comment) {
7276
                            $s .= '<td>';
7277
                            $s .= $comment;
7278
                            $s .= '</td>';
7279
                        }
7280
                        $s.='</tr>';
7281
                    }
7282
7283
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
7284
7285
                    // multiple answers
7286
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7287
7288
                    if (in_array($numAnswer, $user_choice_array)) {
7289
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7290
                    } else {
7291
                        $attributes = array('id' => $input_id);
7292
                    }
7293
7294
                    if ($debug_mark_answer) {
7295
                        if ($answerCorrect) {
7296
                            $attributes['checked'] = 1;
7297
                            $attributes['selected'] = 1;
7298
                        }
7299
                    }
7300
7301
                    $answer = Security::remove_XSS($answer);
7302
                    $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7303
                    $answer_input .= '<label class="checkbox">';
7304
                    $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', 1, $attributes);
7305
                    $answer_input .= $answer;
7306
                    $answer_input .= '</label>';
7307
7308
                    if ($show_comment) {
7309
                        $s.= '<tr>';
7310
                        $s .= '<td>';
7311
                        $s.= $answer_input;
7312
                        $s .= '</td>';
7313
                        $s .= '<td>';
7314
                        $s .= $comment;
7315
                        $s .= '</td>';
7316
                        $s.= '</tr>';
7317
                    } else {
7318
                        $s.= $answer_input;
7319
                    }
7320
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7321
                    $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7322
7323
                    $my_choice = array();
7324
                    if (!empty($user_choice_array)) {
7325
                        foreach ($user_choice_array as $item) {
7326
                            $item = explode(':', $item);
7327
                            $my_choice[$item[0]] = $item[1];
7328
                        }
7329
                    }
7330
                    $answer = Security::remove_XSS($answer);
7331
                    $s .='<tr>';
7332
                    $s .= Display::tag('td', $answer);
7333
7334
                    foreach ($objQuestionTmp->options as $key => $item) {
7335
                        if (isset($my_choice[$numAnswer]) && $key == $my_choice[$numAnswer]) {
7336
                            $attributes = array('checked' => 1, 'selected' => 1);
7337
                        } else {
7338
                            $attributes = array();
7339
                        }
7340
7341
                        if ($debug_mark_answer) {
7342
                            if ($key == $answerCorrect) {
7343
                                $attributes['checked'] = 1;
7344
                                $attributes['selected'] = 1;
7345
                            }
7346
                        }
7347
                        $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $key, $attributes));
7348
                    }
7349
7350
                    if ($show_comment) {
7351
                        $s .= '<td>';
7352
                        $s .= $comment;
7353
                        $s .= '</td>';
7354
                    }
7355
                    $s.='</tr>';
7356
                } elseif ($answerType == FILL_IN_BLANKS) {
7357
                    list($answer) = explode('::', $answer);
7358
7359
                    //Correct answer
7360
                    api_preg_match_all('/\[[^]]+\]/', $answer, $correct_answer_list);
7361
7362
                    //Student's answezr
7363
                    if (isset($user_choice[0]['answer'])) {
7364
                        api_preg_match_all('/\[[^]]+\]/', $user_choice[0]['answer'], $student_answer_list);
7365
                        $student_answer_list = $student_answer_list[0];
7366
                    }
7367
7368
                    //If debug
7369
                    if ($debug_mark_answer) {
7370
                        $student_answer_list = $correct_answer_list[0];
7371
                    }
7372
7373
                    if (!empty($correct_answer_list) && !empty($student_answer_list)) {
7374
                        $correct_answer_list = $correct_answer_list[0];
7375
                        $i = 0;
7376
                        foreach ($correct_answer_list as $correct_item) {
7377
                            $value = null;
7378
                            if (isset($student_answer_list[$i]) && !empty($student_answer_list[$i])) {
7379
7380
                                //Cleaning student answer list
7381
                                $value = strip_tags($student_answer_list[$i]);
7382
                                $value = api_substr($value, 1, api_strlen($value) - 2);
7383
                                $value = explode('/', $value);
7384
7385
                                if (!empty($value[0])) {
7386
                                    $value = str_replace('&nbsp;', '', trim($value[0]));
7387
                                }
7388
                                $correct_item = preg_quote($correct_item);
7389
                                $correct_item = api_preg_replace('|/|', '\/', $correct_item);   // to prevent error if there is a / in the text to find
7390
                                $answer = api_preg_replace('/'.$correct_item.'/', Display::input('text', "choice[$questionId][]", $value), $answer, 1);
7391
                            }
7392
                            $i++;
7393
                        }
7394
                    } else {
7395
                        $answer = api_preg_replace('/\[[^]]+\]/', Display::input('text', "choice[$questionId][]", '', $attributes), $answer);
7396
                    }
7397
                    $s .= $answer;
7398
                } elseif ($answerType == MATCHING) {
7399
                    // matching type, showing suggestions and answers
7400
                    // TODO: replace $answerId by $numAnswer
7401
7402
                    if ($lines_count == 1) {
7403
                        $s .= $objAnswerTmp->getJs();
7404
                    }
7405
                    if ($answerCorrect != 0) {
7406
                        // only show elements to be answered (not the contents of
7407
                        // the select boxes, who are correct = 0)
7408
                        $s .= '<tr><td width="45%">';
7409
                        $parsed_answer = $answer;
7410
                        $windowId = $questionId.'_'.$lines_count;
7411
                        //left part questions
7412
                        $s .= ' <div id="window_'.$windowId.'" class="window window_left_question window'.$questionId.'_question">
7413
                                    <b>'.$lines_count.'</b>.&nbsp'.$parsed_answer.'
7414
                                </div>
7415
                                </td>';
7416
7417
                        // middle part (matches selects)
7418
7419
                        $s .= '<td width="10%" align="center">&nbsp;&nbsp;';
7420
                        $s .= '<div style="display:block">';
7421
7422
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']">';
7423
                        $selectedValue = 0;
7424
                        // fills the list-box
7425
                        $item = 0;
7426 View Code Duplication
                        foreach ($select_items as $val) {
7427
                            // set $debug_mark_answer to true at public static function start to
7428
                            // show the correct answer with a suffix '-x'
7429
                            $selected = '';
7430
                            if ($debug_mark_answer) {
7431
                                if ($val['id'] == $answerCorrect) {
7432
                                    $selected = 'selected="selected"';
7433
                                    $selectedValue = $val['id'];
7434
                                }
7435
                            }
7436
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7437
                                $selected = 'selected="selected"';
7438
                                $selectedValue = $val['id'];
7439
                            }
7440
                            //$s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
7441
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7442
                            $item++;
7443
                        }
7444
7445 View Code Duplication
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7446
                            $s.= '<script>
7447
                                jsPlumb.ready(function() {
7448
                                    jsPlumb.connect({
7449
                                        source: "window_'.$windowId.'",
7450
                                        target: "window_'.$questionId.'_'.$selectedValue.'_answer",
7451
                                        endpoint:["Blank", { radius:15 }],
7452
                                        anchor:["RightMiddle","LeftMiddle"],
7453
                                        paintStyle:{ strokeStyle:"#8a8888" , lineWidth:8 },
7454
                                        connector: [connectorType, { curviness: curvinessValue } ],
7455
                                    })
7456
                                });
7457
                                </script>';
7458
                        }
7459
                        $s .= '</select></div></td>';
7460
7461
                        $s.='<td width="45%" valign="top" >';
7462
7463 View Code Duplication
                        if (isset($select_items[$lines_count])) {
7464
                            $s.= '<div id="window_'.$windowId.'_answer" class="window window_right_question">
7465
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7466
                                  </div>';
7467
                        } else {
7468
                            $s.='&nbsp;';
7469
                        }
7470
7471
                        $s .= '</td>';
7472
                        $s .= '</tr>';
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
                        if (($lines_count - 1) == $num_suggestions) {
7477
                            // if it remains answers to shown at the right side
7478 View Code Duplication
                            while (isset($select_items[$lines_count])) {
7479
                                $s .= '<tr>
7480
                                      <td colspan="2"></td>
7481
                                      <td valign="top">';
7482
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7483
                                $s .= $select_items[$lines_count]['answer'];
7484
                                $s.="</td>
7485
                                </tr>";
7486
                                $lines_count++;
7487
                            } // end while()
7488
                        }  // end if()
7489
                        $matching_correct_answer++;
7490
                    }
7491
                } elseif ($answerType ==  DRAGGABLE) {
7492
                    // matching type, showing suggestions and answers
7493
                    // TODO: replace $answerId by $numAnswer
7494
7495
                    if ($answerCorrect != 0) {
7496
                        // only show elements to be answered (not the contents of
7497
                        // the select boxes, who are correct = 0)
7498
                        $s .= '<td>';
7499
                        $parsed_answer = $answer;
7500
                        $windowId = $questionId.'_'.$numAnswer; //67_293 - 67_294
7501
7502
                        //left part questions
7503
                        $s .= '<li class="ui-state-default" id="'.$windowId.'">';
7504
                        $s .= ' <div id="window_'.$windowId.'" class="window'.$questionId.'_question_draggable question_draggable">
7505
                                   '.$parsed_answer.'
7506
                                </div>';
7507
7508
                        $s .= '<div style="display:none">';
7509
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']" class="select_option">';
7510
                        $selectedValue = 0;
7511
                        // fills the list-box
7512
                        $item = 0;
7513 View Code Duplication
                        foreach ($select_items as $val) {
7514
                            // set $debug_mark_answer to true at function start to
7515
                            // show the correct answer with a suffix '-x'
7516
                            $selected = '';
7517
                            if ($debug_mark_answer) {
7518
                                if ($val['id'] == $answerCorrect) {
7519
                                    $selected = 'selected="selected"';
7520
                                    $selectedValue = $val['id'];
7521
                                }
7522
                            }
7523
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7524
                                $selected = 'selected="selected"';
7525
                                $selectedValue = $val['id'];
7526
                            }
7527
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7528
                            $item++;
7529
                        }
7530
                        $s .= '</select>';
7531
7532 View Code Duplication
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7533
                            $s.= '<script>
7534
                                $(function() {
7535
                                    deleteItem($("#'.$questionId.'_'.$selectedValue.'"), $("#drop_'.$windowId.'"));
7536
                                });
7537
                                </script>';
7538
                        }
7539
7540 View Code Duplication
                        if (isset($select_items[$lines_count])) {
7541
                            $s.= '<div id="window_'.$windowId.'_answer" class="">
7542
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7543
                                  </div>';
7544
                        } else {
7545
                            $s.='&nbsp;';
7546
                        }
7547
                        $lines_count++;
7548
                        //if the left side of the "matching" has been completely
7549
                        // shown but the right side still has values to show...
7550
7551 View Code Duplication
                        if (($lines_count - 1) == $num_suggestions) {
7552
                            // if it remains answers to shown at the right side
7553
                            while (isset($select_items[$lines_count])) {
7554
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7555
                                $s .= $select_items[$lines_count]['answer'];
7556
                                $lines_count++;
7557
                            }
7558
                        }
7559
                        $s .= '</div>';
7560
                        $matching_correct_answer++;
7561
                        $s .= '</li>';
7562
                    }
7563
                }
7564
            } // end for()
7565
7566
            if ($show_comment) {
7567
                $s .= '</table>';
7568
            } else {
7569
                if ($answerType == MATCHING || $answerType == UNIQUE_ANSWER_NO_OPTION || $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
7570
                    $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7571
                    $s .= '</table>';
7572
                }
7573
            }
7574
7575
            if ($answerType == DRAGGABLE) {
7576
                $s .= '</ul><div class="clear"></div>';
7577
7578
                $counterAnswer = 1;
7579
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7580
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7581
                    $windowId = $questionId.'_'.$counterAnswer;
7582
                    if ($answerCorrect == 0) {
7583
                        $s .= '<div id="drop_'.$windowId.'" class="droppable ui-state-default">'.$counterAnswer.'</div>';
7584
                        $counterAnswer++;
7585
                    }
7586
                }
7587
            }
7588
7589
            if ($answerType == MATCHING) {
7590
                $s .= '</div>';
7591
            }
7592
7593
            $s .= '</div>';
7594
7595
            // destruction of the Answer object
7596
            unset($objAnswerTmp);
7597
7598
            // destruction of the Question object
7599
            unset($objQuestionTmp);
7600
7601
            $html .= $s;
7602
            return $html;
7603
        } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
7604
            // Question is a HOT_SPOT
7605
            //checking document/images visibility
7606 View Code Duplication
            if (api_is_platform_admin() || api_is_course_admin()) {
7607
                $course = api_get_course_info();
7608
                $doc_id = DocumentManager::get_document_id($course, '/images/'.$pictureName);
7609
                if (is_numeric($doc_id)) {
7610
                    $images_folder_visibility = api_get_item_visibility($course, 'document', $doc_id, api_get_session_id());
7611
                    if (!$images_folder_visibility) {
7612
                        //This message is shown only to the course/platform admin if the image is set to visibility = false
7613
                        Display::display_warning_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'));
0 ignored issues
show
Deprecated Code introduced by
The method Display::display_warning_message() has been deprecated with message: use Display::addFlash with Display::return_message

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...
7614
                    }
7615
                }
7616
            }
7617
            $questionName = $objQuestionTmp->selectTitle();
7618
            $questionDescription = $objQuestionTmp->selectDescription();
7619
7620
            if ($freeze) {
7621
                $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...
7622
                $html .= $s;
7623
                return $html;
7624
            }
7625
7626
            // Get the answers, make a list
7627
            $objAnswerTmp = new Answer($questionId);
7628
7629
            // get answers of hotpost
7630
            $answers_hotspot = array();
7631
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7632
                //$answers = $objAnswerTmp->selectAnswerByAutoId($objAnswerTmp->selectAutoId($answerId));
7633
                $answers_hotspot[$answerId] = $objAnswerTmp->selectAnswer($answerId);
7634
            }
7635
7636
            // display answers of hotpost order by id
7637
            $answer_list = '<div style="padding: 10px; margin-left: 0px; border: 1px solid #A4A4A4; height: 408px; width: 200px;"><b>'.get_lang('HotspotZones').'</b><dl>';
7638
            if (!empty($answers_hotspot)) {
7639
                ksort($answers_hotspot);
7640
                foreach ($answers_hotspot as $key => $value) {
7641
                    $answer_list .= '<dt>'.$key.'.- '.$value.'</dt><br />';
7642
                }
7643
            }
7644
            $answer_list .= '</dl></div>';
7645
7646
            if ($answerType == HOT_SPOT_DELINEATION) {
7647
                $answer_list = '';
7648
                $swf_file = 'hotspot_delineation_user';
7649
                $swf_height = 405;
7650
            } else {
7651
                $swf_file = 'hotspot_user';
7652
                $swf_height = 436;
7653
            }
7654
7655
            if (!$only_questions) {
7656
                if ($show_title) {
7657
                    $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...
7658
                    $html .=  '<div class="question_title">'.$current_item.'. '.$questionName.'</div>';
7659
                    $html .=  $questionDescription;
7660 View Code Duplication
                } else {
7661
                    $html .= '<div class="media">';
7662
                    $html .= '<div class="pull-left">';
7663
                    $html .= '<div class="media-object">';
7664
                    $html .= Display::div($current_item.'. ', array('class' => 'question_no_title'));
7665
                    $html .= '</div>';
7666
                    $html .= '</div>';
7667
                    $html .= '<div class="media-body">';
7668
                    if (!empty($questionDescription)) {
7669
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
7670
                    }
7671
                    $html .= '</div>';
7672
                    $html .= '</div>';
7673
                }
7674
                //@todo I need to the get the feedback type
7675
                $html .=  '<input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />';
7676
                $html .=  '<table class="exercise_questions">
7677
                           <tr>
7678
                            <td valign="top" colspan="2">';
7679
                $html .=  '</td></tr>';
7680
            }
7681
7682
            $canClick = isset($_GET['editQuestion']) ? '0' : (isset($_GET['modifyAnswers']) ? '0' : '1');
7683
            $s .= api_get_js('js/hotspot/js/hotspot.js');
7684
            $s .= '<script>
7685
                    <!--
7686
                    // Globals
7687
                    // Major version of Flash required
7688
                    var requiredMajorVersion = 7;
7689
                    // Minor version of Flash required
7690
                    var requiredMinorVersion = 0;
7691
                    // Minor version of Flash required
7692
                    var requiredRevision = 0;
7693
                    // the version of javascript supported
7694
                    var jsVersion = 1.0;
7695
                    // -->
7696
                    </script>
7697
                    <script language="VBScript" type="text/vbscript">
7698
                    <!-- // Visual basic helper required to detect Flash Player ActiveX control version information
7699
                    Function VBGetSwfVer(i)
7700
                      on error resume next
7701
                      Dim swControl, swVersion
7702
                      swVersion = 0
7703
7704
                      set swControl = CreateObject("ShockwaveFlash.ShockwaveFlash." + CStr(i))
7705
                      if (IsObject(swControl)) then
7706
                        swVersion = swControl.GetVariable("$version")
7707
                      end if
7708
                      VBGetSwfVer = swVersion
7709
                    End Function
7710
                    // -->
7711
                    </script>
7712
7713
                    <script language="JavaScript1.1" type="text/javascript">
7714
                    <!-- // Detect Client Browser type
7715
                    var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
7716
                    var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
7717
                    var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
7718
                    jsVersion = 1.1;
7719
                    // JavaScript helper required to detect Flash Player PlugIn version information
7720
                    function JSGetSwfVer(i) {
7721
                        // NS/Opera version >= 3 check for Flash plugin in plugin array
7722
                        if (navigator.plugins != null && navigator.plugins.length > 0) {
7723
                            if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
7724
                                var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
7725
                                var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
7726
                                descArray = flashDescription.split(" ");
7727
                                tempArrayMajor = descArray[2].split(".");
7728
                                versionMajor = tempArrayMajor[0];
7729
                                versionMinor = tempArrayMajor[1];
7730
                                if ( descArray[3] != "" ) {
7731
                                    tempArrayMinor = descArray[3].split("r");
7732
                                } else {
7733
                                    tempArrayMinor = descArray[4].split("r");
7734
                                }
7735
                                versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;
7736
                                flashVer = versionMajor + "." + versionMinor + "." + versionRevision;
7737
                            } else {
7738
                                flashVer = -1;
7739
                            }
7740
                        }
7741
                        // MSN/WebTV 2.6 supports Flash 4
7742
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4;
7743
                        // WebTV 2.5 supports Flash 3
7744
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3;
7745
                        // older WebTV supports Flash 2
7746
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2;
7747
                        // Can\'t detect in all other cases
7748
                        else {
7749
                            flashVer = -1;
7750
                        }
7751
                        return flashVer;
7752
                    }
7753
                    // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available
7754
7755
                    function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) {
7756
                        reqVer = parseFloat(reqMajorVer + "." + reqRevision);
7757
                        // loop backwards through the versions until we find the newest version
7758
                        for (i=25;i>0;i--) {
7759
                            if (isIE && isWin && !isOpera) {
7760
                                versionStr = VBGetSwfVer(i);
7761
                            } else {
7762
                                versionStr = JSGetSwfVer(i);
7763
                            }
7764
                            if (versionStr == -1 ) {
7765
                                return false;
7766
                            } else if (versionStr != 0) {
7767
                                if(isIE && isWin && !isOpera) {
7768
                                    tempArray         = versionStr.split(" ");
7769
                                    tempString        = tempArray[1];
7770
                                    versionArray      = tempString .split(",");
7771
                                } else {
7772
                                    versionArray      = versionStr.split(".");
7773
                                }
7774
                                versionMajor      = versionArray[0];
7775
                                versionMinor      = versionArray[1];
7776
                                versionRevision   = versionArray[2];
7777
7778
                                versionString     = versionMajor + "." + versionRevision;   // 7.0r24 == 7.24
7779
                                versionNum        = parseFloat(versionString);
7780
                                // is the major.revision >= requested major.revision AND the minor version >= requested minor
7781
                                if ( (versionMajor > reqMajorVer) && (versionNum >= reqVer) ) {
7782
                                    return true;
7783
                                } else {
7784
                                    return ((versionNum >= reqVer && versionMinor >= reqMinorVer) ? true : false );
7785
                                }
7786
                            }
7787
                        }
7788
                    }
7789
                    // -->
7790
                    </script>';
7791
            $s .= '<tr><td valign="top" colspan="2" width="520"><table><tr><td width="520">
7792
                    <script>
7793
                        // Version check based upon the values entered above in "Globals"
7794
                        var hasReqestedVersion = DetectFlashVer(requiredMajorVersion, requiredMinorVersion, requiredRevision);
7795
7796
                        // Check to see if the version meets the requirements for playback
7797
                        if (hasReqestedVersion) {  // if we\'ve detected an acceptable version
7798
                            var oeTags = \'<object type="application/x-shockwave-flash" data="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" width="600" height="'.$swf_height.'">\'
7799
                                        + \'<param name="wmode" value="transparent">\'
7800
                                        + \'<param name="movie" value="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" />\'
7801
                                        + \'<\/object>\';
7802
                            document.write(oeTags);   // embed the Flash Content SWF when all tests are passed
7803
                        } else {  // flash is too old or we can\'t detect the plugin
7804
                            var alternateContent = "Error<br \/>"
7805
                                + "Hotspots requires Macromedia Flash 7.<br \/>"
7806
                                + "<a href=\"http://www.macromedia.com/go/getflash/\">Get Flash<\/a>";
7807
                            document.write(alternateContent);  // insert non-flash content
7808
                        }
7809
                    </script>
7810
                    </td>
7811
                    <td valign="top" align="left">'.$answer_list.'</td></tr>
7812
                    </table>
7813
            </td></tr>';
7814
            $html .= $s;
7815
            $html .= '</table>';
7816
            return $html;
7817
        }
7818
        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...
7819
    }
7820
7821
    /**
7822
     * @param int $exeId
7823
     * @return array
7824
     */
7825
    public function returnQuestionListByAttempt($exeId)
7826
    {
7827
        return $this->displayQuestionListByAttempt($exeId, false, true);
7828
    }
7829
7830
    /**
7831
     * Display the exercise results
7832
     * @param int  $exe_id
7833
     * @param bool $saveUserResult save users results (true) or just show the results (false)
7834
     * @param bool $returnExerciseResult return array with exercise result info
7835
     * @return mixed
7836
     */
7837
    public function displayQuestionListByAttempt($exe_id, $saveUserResult = false, $returnExerciseResult = false)
7838
    {
7839
        global $origin, $debug;
7840
7841
        //Getting attempt info
7842
        $exercise_stat_info = $this->getStatTrackExerciseInfoByExeId($exe_id);
7843
7844
        //Getting question list
7845
        $question_list = array();
7846
        if (!empty($exercise_stat_info['data_tracking'])) {
7847
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
7848
        } else {
7849
            //Try getting the question list only if save result is off
7850
            if ($saveUserResult == false) {
7851
                $question_list = $this->selectQuestionList();
7852
            }
7853
            error_log("Data tracking is empty! exe_id: $exe_id");
7854
        }
7855
7856
        $counter = 1;
7857
        $total_score = 0;
7858
        $total_weight = 0;
7859
7860
        $exercise_content = null;
7861
7862
        //Hide results
7863
        $show_results = false;
7864
        $show_only_score = false;
7865
7866
        if ($this->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
7867
            $show_results = true;
7868
        }
7869
7870
        $showScoreOptions = [
7871
            RESULT_DISABLE_SHOW_SCORE_ONLY,
7872
            RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
7873
            RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT
7874
        ];
7875
7876
        if (in_array($this->results_disabled, $showScoreOptions)) {
7877
            $show_only_score = true;
7878
        }
7879
7880 View Code Duplication
        if ($show_results || $show_only_score) {
7881
            $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
7882
            // Shows exercise header.
7883
            echo $this->show_exercise_result_header(
7884
                $user_info['complete_name'],
7885
                api_convert_and_format_date($exercise_stat_info['start_date'], DATE_TIME_FORMAT_LONG),
7886
                $exercise_stat_info['duration']
7887
            );
7888
        }
7889
7890
        // Display text when test is finished #4074 and for LP #4227
7891
        $end_of_message = $this->selectTextWhenFinished();
7892
        if (!empty($end_of_message)) {
7893
            Display::display_normal_message($end_of_message, false);
0 ignored issues
show
Deprecated Code introduced by
The method Display::display_normal_message() has been deprecated with message: use Display::addFlash with Display::return_message($message, 'normal');

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...
7894
            echo "<div class='clear'>&nbsp;</div>";
7895
        }
7896
7897
        $question_list_answers = array();
7898
        $media_list = array();
7899
        $category_list = array();
7900
        $tempParentId = null;
7901
        $mediaCounter = 0;
7902
7903
        $exerciseResultInfo = array();
7904
7905
        // Loop over all question to show results for each of them, one by one
7906
        if (!empty($question_list)) {
7907
            if ($debug) {
7908
                error_log('Looping question_list '.print_r($question_list, 1));
7909
            }
7910
7911
            foreach ($question_list as $questionId) {
7912
7913
                // Creates a temporary Question object
7914
                $objQuestionTmp = Question::read($questionId);
7915
7916
                // This variable commes from exercise_submit_modal.php
7917
                ob_start();
7918
                $hotspot_delineation_result = null;
7919
7920
                // We're inside *one* question. Go through each possible answer for this question
7921
                $result = $this->manageAnswers(
7922
                    $exercise_stat_info['exe_id'],
7923
                    $questionId,
7924
                    null,
7925
                    'exercise_result',
7926
                    array(),
7927
                    $saveUserResult,
7928
                    true,
7929
                    $show_results,
7930
                    $hotspot_delineation_result
7931
                );
7932
7933
                if (empty($result)) {
7934
                    continue;
7935
                }
7936
7937
                $total_score += $result['score'];
7938
                $total_weight += $result['weight'];
7939
7940
                $question_list_answers[] = array(
7941
                    'question' => $result['open_question'],
7942
                    'answer' => $result['open_answer'],
7943
                    'answer_type' => $result['answer_type']
7944
                );
7945
7946
                $my_total_score = $result['score'];
7947
                $my_total_weight = $result['weight'];
7948
7949
                // Category report
7950
                $category_was_added_for_this_test = false;
7951
                $categoryExerciseList = $this->getListOfCategoriesWithQuestionForTest();
7952
7953
                $category_list = array();
7954
                if (isset($categoryExerciseList) && !empty($categoryExerciseList)) {
7955
                    foreach ($categoryExerciseList as $category_id => $categoryInfo) {
7956
                        if (!isset($category_list[$category_id])) {
7957
                            $category_list[$category_id] = array();
7958
                            $category_list[$category_id]['score'] = 0;
7959
                            $category_list[$category_id]['total'] = 0;
7960
                        }
7961
                        $category_list[$category_id]['score'] += $my_total_score;
7962
                        $category_list[$category_id]['total'] += $my_total_weight;
7963
                        $category_was_added_for_this_test = true;
7964
                    }
7965
                }
7966
7967
                // No category for this question!
7968
                if ($category_was_added_for_this_test == false) {
7969
                    if (!isset($category_list['none'])) {
7970
                        $category_list['none'] = array();
7971
                        $category_list['none']['score'] = 0;
7972
                        $category_list['none']['total'] = 0;
7973
                    }
7974
7975
                    $category_list['none']['score'] += $my_total_score;
7976
                    $category_list['none']['total'] += $my_total_weight;
7977
                }
7978
7979
                if ($this->selectPropagateNeg() == 0 && $my_total_score < 0) {
7980
                    $my_total_score = 0;
7981
                }
7982
7983
                $comnt = null;
7984 View Code Duplication
                if ($show_results) {
7985
                    $comnt = get_comments($exe_id, $questionId);
7986
                    if (!empty($comnt)) {
7987
                        echo '<b>'.get_lang('Feedback').'</b>';
7988
                        echo '<div id="question_feedback">'.$comnt.'</div>';
7989
                    }
7990
                }
7991
7992
                $score = array();
7993
                $score['result'] = get_lang('Score')." : ".ExerciseLib::show_score($my_total_score, $my_total_weight, false, true);
7994
                $score['pass'] = $my_total_score >= $my_total_weight ? true : false;
7995
                $score['score'] = $my_total_score;
7996
                $score['weight'] = $my_total_weight;
7997
                $score['comments'] = $comnt;
7998
7999
                $exerciseResultInfo[$questionId]['score'] = $score;
8000
                $exerciseResultInfo[$questionId]['details'] = $result;
8001
8002
                // If no results we hide the results
8003
                if ($show_results == false) {
8004
                    $score = array();
8005
                }
8006
                $contents = ob_get_clean();
8007
8008
                $question_content = '<div class="question_row">';
8009
8010
                if ($show_results) {
8011
8012
                    $show_media = false;
8013
                    $counterToShow = $counter;
8014
                    if ($objQuestionTmp->parent_id != 0) {
8015
8016
                        if (!in_array($objQuestionTmp->parent_id, $media_list)) {
8017
                            $media_list[] = $objQuestionTmp->parent_id;
8018
                            $show_media = true;
8019
                        }
8020
                        if ($tempParentId == $objQuestionTmp->parent_id) {
8021
                            $mediaCounter++;
8022
                        } else {
8023
                            $mediaCounter = 0;
8024
                        }
8025
                        $counterToShow = chr(97 + $mediaCounter);
8026
                        $tempParentId = $objQuestionTmp->parent_id;
8027
                    }
8028
8029
                    // Shows question title an description.
8030
                    $question_content .= $objQuestionTmp->return_header(null, $counterToShow, $score, $show_media, $this->getHideQuestionTitle());
8031
8032
                    // display question category, if any
8033
                    $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...
8034
                }
8035
                $counter++;
8036
8037
                $question_content .= $contents;
8038
                $question_content .= '</div>';
8039
8040
                $exercise_content .= $question_content;
8041
            } // end foreach() block that loops over all questions
8042
        }
8043
8044
        $total_score_text = null;
8045
8046
        if ($returnExerciseResult) {
8047
            return $exerciseResultInfo;
8048
        }
8049
8050
        if ($origin != 'learnpath') {
8051
            if ($show_results || $show_only_score) {
8052
                $total_score_text .= $this->get_question_ribbon($total_score, $total_weight, true);
8053
            }
8054
        }
8055
8056 View Code Duplication
        if (!empty($category_list) && ($show_results || $show_only_score)) {
8057
            //Adding total
8058
            $category_list['total'] = array('score' => $total_score, 'total' => $total_weight);
8059
            echo TestCategory::get_stats_table_by_attempt($this->id, $category_list, $this->categoryMinusOne);
8060
        }
8061
8062
        echo $total_score_text;
8063
        echo $exercise_content;
8064
8065
        if (!$show_only_score) {
8066
            echo $total_score_text;
8067
        }
8068
8069
        if ($saveUserResult) {
8070
8071
            // Tracking of results
8072
            $learnpath_id = $exercise_stat_info['orig_lp_id'];
8073
            $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
8074
            $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
8075
8076 View Code Duplication
            if (api_is_allowed_to_session_edit()) {
8077
                update_event_exercise(
8078
                    $exercise_stat_info['exe_id'],
8079
                    $this->selectId(),
8080
                    $total_score,
8081
                    $total_weight,
8082
                    api_get_session_id(),
8083
                    $learnpath_id,
8084
                    $learnpath_item_id,
8085
                    $learnpath_item_view_id,
8086
                    $exercise_stat_info['exe_duration'],
8087
                    '',
8088
                    array()
8089
                );
8090
            }
8091
8092
            // Send notification.
8093
            if (!api_is_allowed_to_edit(null, true)) {
8094
                $isSuccess = ExerciseLib::is_success_exercise_result($total_score, $total_weight, $this->selectPassPercentage());
8095
                $this->sendCustomNotification($exe_id, $exerciseResultInfo, $isSuccess);
8096
                $this->sendNotificationForOpenQuestions($question_list_answers, $origin, $exe_id);
8097
                $this->sendNotificationForOralQuestions($question_list_answers, $origin, $exe_id);
8098
            }
8099
        }
8100
    }
8101
8102
    /**
8103
     * Returns an HTML ribbon to show on top of the exercise result, with
8104
     * colouring depending on the success or failure of the student
8105
     * @param integer $score
8106
     * @param integer $weight
8107
     * @param bool $check_pass_percentage
8108
     * @return string
8109
     */
8110
    public function get_question_ribbon($score, $weight, $check_pass_percentage = false)
8111
    {
8112
        $eventMessage = null;
8113
        $ribbon = '<div class="question_row">';
8114
        $ribbon .= '<div class="ribbon">';
8115
        if ($check_pass_percentage) {
8116
            $is_success = ExerciseLib::is_success_exercise_result($score, $weight, $this->selectPassPercentage());
8117
            // Color the final test score if pass_percentage activated
8118
            $ribbon_total_success_or_error = "";
8119
            if (ExerciseLib::is_pass_pourcentage_enabled($this->selectPassPercentage())) {
8120
                if ($is_success) {
8121
                    $eventMessage = $this->getOnSuccessMessage();
8122
                    $ribbon_total_success_or_error = ' ribbon-total-success';
8123
                } else {
8124
                    $eventMessage = $this->getOnFailedMessage();
8125
                    $ribbon_total_success_or_error = ' ribbon-total-error';
8126
                }
8127
            }
8128
            $ribbon .= '<div class="rib rib-total '.$ribbon_total_success_or_error.'">';
8129
        } else {
8130
            $ribbon .= '<div class="rib rib-total">';
8131
        }
8132
        $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
8133
        $ribbon .= ExerciseLib::show_score($score, $weight, false, true);
8134
        $ribbon .= '</h3>';
8135
        $ribbon .= '</div>';
8136
8137
        if ($check_pass_percentage) {
8138
            $ribbon .= ExerciseLib::show_success_message($score, $weight, $this->selectPassPercentage());
8139
        }
8140
        $ribbon .= '</div>';
8141
        $ribbon .= '</div>';
8142
8143
        $ribbon .= $eventMessage;
8144
8145
        return $ribbon;
8146
    }
8147
8148
    /**
8149
     * Returns an array of categories details for the questions of the current
8150
     * exercise.
8151
     * @return array
8152
     */
8153
    public function getQuestionWithCategories()
8154
    {
8155
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
8156
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
8157
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8158
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
8159
        $sql = "SELECT DISTINCT cat.*
8160
                FROM $TBL_EXERCICE_QUESTION e
8161
                INNER JOIN $TBL_QUESTIONS q
8162
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8163
                INNER JOIN $categoryRelTable catRel
8164
                ON (catRel.question_id = e.question_id)
8165
                INNER JOIN $categoryTable cat
8166
                ON (cat.id = catRel.category_id)
8167
                WHERE
8168
                  e.c_id = {$this->course_id} AND
8169
                  e.exercice_id	= ".intval($this->id);
8170
8171
        $result = Database::query($sql);
8172
        $categoriesInExercise = array();
8173
        if (Database::num_rows($result)) {
8174
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
8175
        }
8176
8177
        return $categoriesInExercise;
8178
    }
8179
8180
    /**
8181
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option
8182
     */
8183
    public function get_max_score()
8184
    {
8185
        $out_max_score = 0;
8186
        // list of question's id !!! the array key start at 1 !!!
8187
        $questionList = $this->selectQuestionList(true);
8188
8189
        // test is randomQuestions - see field random of test
8190
        if ($this->random > 0 && $this->randomByCat == 0) {
8191
            $numberRandomQuestions = $this->random;
8192
            $questionScoreList = array();
8193
            for ($i = 1; $i <= count($questionList); $i++) {
8194
                $tmpobj_question = Question::read($questionList[$i]);
8195
                $questionScoreList[] = $tmpobj_question->weighting;
8196
            }
8197
            rsort($questionScoreList);
8198
            // add the first $numberRandomQuestions value of score array to get max_score
8199
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
8200
                $out_max_score += $questionScoreList[$i];
8201
            }
8202
        } else if ($this->random > 0 && $this->randomByCat > 0) {
8203
            // test is random by category
8204
            // get the $numberRandomQuestions best score question of each category
8205
8206
            $numberRandomQuestions = $this->random;
8207
            $tab_categories_scores = array();
8208
            for ($i = 1; $i <= count($questionList); $i++) {
8209
                $question_category_id = TestCategory::getCategoryForQuestion($questionList[$i]);
8210
                if (!is_array($tab_categories_scores[$question_category_id])) {
8211
                    $tab_categories_scores[$question_category_id] = array();
8212
                }
8213
                $tmpobj_question = Question::read($questionList[$i]);
8214
                $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
8215
            }
8216
8217
            // here we've got an array with first key, the category_id, second key, score of question for this cat
8218
            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...
8219
                rsort($tab_scores);
8220
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
8221
                    $out_max_score += $tab_scores[$i];
8222
                }
8223
            }
8224
        } else {
8225
            // standard test, just add each question score
8226
            foreach ($questionList as $questionId) {
8227
                $question = Question::read($questionId, $this->course_id);
8228
                $out_max_score += $question->weighting;
8229
            }
8230
        }
8231
8232
        return $out_max_score;
8233
    }
8234
8235
    /**
8236
    * @return string
8237
    */
8238
    public function get_formated_title()
8239
    {
8240
        return api_html_entity_decode($this->selectTitle());
8241
    }
8242
8243
    /**
8244
     * @param $in_title
8245
     * @return string
8246
     */
8247
    public static function get_formated_title_variable($in_title)
8248
    {
8249
        return api_html_entity_decode($in_title);
8250
    }
8251
8252
    /**
8253
     * @return string
8254
     */
8255
    public function format_title()
8256
    {
8257
        return api_htmlentities($this->title);
8258
    }
8259
8260
    /**
8261
     * @param $in_title
8262
     * @return string
8263
     */
8264
    public static function format_title_variable($in_title)
8265
    {
8266
        return api_htmlentities($in_title);
8267
    }
8268
8269
    /**
8270
     * @param int $courseId
8271
     * @param int $sessionId
8272
     * @return array exercises
8273
     */
8274 View Code Duplication
    public function getExercisesByCouseSession($courseId, $sessionId)
8275
    {
8276
        $courseId = intval($courseId);
8277
        $sessionId = intval($sessionId);
8278
8279
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8280
        $sql = "SELECT * FROM $tbl_quiz cq
8281
                WHERE
8282
                    cq.c_id = %s AND
8283
                    (cq.session_id = %s OR cq.session_id = 0) AND
8284
                    cq.active = 0
8285
                ORDER BY cq.id";
8286
        $sql = sprintf($sql, $courseId, $sessionId);
8287
8288
        $result = Database::query($sql);
8289
8290
        $rows = array();
8291
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8292
            $rows[] = $row;
8293
        }
8294
8295
        return $rows;
8296
    }
8297
8298
    /**
8299
     *
8300
     * @param int $courseId
8301
     * @param int $sessionId
8302
     * @param array $quizId
8303
     * @return array exercises
8304
     */
8305
    public function getExerciseAndResult($courseId, $sessionId, $quizId = array())
8306
    {
8307
        if (empty($quizId)) {
8308
            return array();
8309
        }
8310
8311
        $sessionId = intval($sessionId);
8312
8313
        $ids = is_array($quizId) ? $quizId : array($quizId);
8314
        $ids = array_map('intval', $ids);
8315
        $ids = implode(',', $ids);
8316
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8317
        if ($sessionId != 0) {
8318
            $sql = "SELECT * FROM $track_exercises te
8319
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8320
              WHERE
8321
              te.id = %s AND
8322
              te.session_id = %s AND
8323
              cq.id IN (%s)
8324
              ORDER BY cq.id";
8325
8326
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
8327
        } else {
8328
            $sql = "SELECT * FROM $track_exercises te
8329
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8330
              WHERE
8331
              te.id = %s AND
8332
              cq.id IN (%s)
8333
              ORDER BY cq.id";
8334
            $sql = sprintf($sql, $courseId, $ids);
8335
        }
8336
        $result = Database::query($sql);
8337
        $rows = array();
8338
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8339
            $rows[] = $row;
8340
        }
8341
8342
        return $rows;
8343
    }
8344
8345
    /**
8346
     * @param $exeId
8347
     * @param $exercise_stat_info
8348
     * @param $remindList
8349
     * @param $currentQuestion
8350
     * @return int|null
8351
     */
8352
    public static function getNextQuestionId($exeId, $exercise_stat_info, $remindList, $currentQuestion)
8353
    {
8354
        $result = get_exercise_results_by_attempt($exeId, 'incomplete');
8355
8356
        if (isset($result[$exeId])) {
8357
            $result = $result[$exeId];
8358
        } else {
8359
            return null;
8360
        }
8361
8362
        $data_tracking  = $exercise_stat_info['data_tracking'];
8363
        $data_tracking  = explode(',', $data_tracking);
8364
8365
        // if this is the final question do nothing.
8366
        if ($currentQuestion == count($data_tracking)) {
8367
            return null;
8368
        }
8369
8370
        $currentQuestion = $currentQuestion - 1;
8371
8372
        if (!empty($result['question_list'])) {
8373
            $answeredQuestions = array();
8374
8375
            foreach ($result['question_list'] as $question) {
8376
                if (!empty($question['answer'])) {
8377
                    $answeredQuestions[] = $question['question_id'];
8378
                }
8379
            }
8380
8381
            // Checking answered questions
8382
8383
            $counterAnsweredQuestions = 0;
8384
            foreach ($data_tracking as $questionId) {
8385
                if (!in_array($questionId, $answeredQuestions)) {
8386
                    if ($currentQuestion != $counterAnsweredQuestions) {
8387
                        break;
8388
                    }
8389
                }
8390
                $counterAnsweredQuestions++;
8391
            }
8392
8393
            $counterRemindListQuestions = 0;
8394
            // Checking questions saved in the reminder list
8395
8396
            if (!empty($remindList)) {
8397
                foreach ($data_tracking as $questionId) {
8398
                    if (in_array($questionId, $remindList)) {
8399
                        // Skip the current question
8400
                        if ($currentQuestion != $counterRemindListQuestions) {
8401
                            break;
8402
                        }
8403
                    }
8404
                    $counterRemindListQuestions++;
8405
                }
8406
8407
                if ($counterRemindListQuestions < $currentQuestion) {
8408
                    return null;
8409
                }
8410
8411
                if (!empty($counterRemindListQuestions)) {
8412
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
8413
                        return $counterAnsweredQuestions;
8414
                    } else {
8415
                        return $counterRemindListQuestions;
8416
                    }
8417
                }
8418
            }
8419
8420
            return $counterAnsweredQuestions;
8421
        }
8422
    }
8423
8424
    /**
8425
     * Gets the position of a questionId in the question list
8426
     * @param $questionId
8427
     * @return int
8428
     */
8429
    public function getPositionInCompressedQuestionList($questionId)
8430
    {
8431
        $questionList = $this->getQuestionListWithMediasCompressed();
8432
        $mediaQuestions = $this->getMediaList();
8433
        $position = 1;
8434
        foreach ($questionList as $id) {
8435
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
8436
                $mediaQuestionList = $mediaQuestions[$id];
8437
                if (in_array($questionId, $mediaQuestionList)) {
8438
                    return $position;
8439
                } else {
8440
                    $position++;
8441
                }
8442
            } else {
8443
                if ($id == $questionId) {
8444
                    return $position;
8445
                } else {
8446
                    $position++;
8447
                }
8448
            }
8449
        }
8450
        return 1;
8451
    }
8452
8453
    /**
8454
     * Get the correct answers in all attempts
8455
     * @param int $learnPathId
8456
     * @param int $learnPathItemId
8457
     * @return array
8458
     */
8459
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
8460
    {
8461
        $attempts = Event::getExerciseResultsByUser(
8462
            api_get_user_id(),
8463
            $this->id,
8464
            api_get_course_int_id(),
8465
            api_get_session_id(),
8466
            $learnPathId,
8467
            $learnPathItemId,
8468
            'asc'
8469
        );
8470
8471
        $corrects = [];
8472
8473
        foreach ($attempts as $attempt) {
8474
            foreach ($attempt['question_list'] as $answers) {
8475
                foreach ($answers as $answer) {
8476
                    $objAnswer = new Answer($answer['question_id']);
8477
8478
                    switch ($objAnswer->getQuestionType()) {
8479
                        case FILL_IN_BLANKS:
8480
                            $isCorrect = FillBlanks::isCorrect($answer['answer']);
8481
                            break;
8482
                        default:
8483
                            $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
8484
                    }
8485
8486
                    if ($isCorrect) {
8487
                        $corrects[$answer['question_id']][] = $answer;
8488
                    }
8489
                }
8490
            }
8491
        }
8492
8493
        return $corrects;
8494
    }
8495
}
8496