Completed
Push — master ( 395485...bbad3a )
by Julito
43:12
created

Exercise::getCorrectAnswersInAllAttempts()   C

Complexity

Conditions 9
Paths 13

Size

Total Lines 47
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 31
nc 13
nop 2
dl 0
loc 47
rs 5.2941
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(true);
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.id 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
     * @param   bool    $from_db    Whether the results should be fetched in the database or just from memory
1086
     * @param   bool    $adminView  Whether we should return all questions (admin view) or just a list limited by the max number of random questions
1087
     * @author Olivier Brouckaert
1088
     * @return array - question ID list
1089
     */
1090
    public function selectQuestionList($from_db = false, $adminView = false)
1091
    {
1092
        if ($from_db && !empty($this->id)) {
1093
            $nbQuestions = $this->getQuestionCount();
1094
            $questionSelectionType = $this->getQuestionSelectionType();
1095
1096
            switch ($questionSelectionType) {
1097
                case EX_Q_SELECTION_ORDERED:
1098
                    $questionList = $this->getQuestionOrderedList();
1099
                    break;
1100
                case EX_Q_SELECTION_RANDOM:
1101
                    // Not a random exercise, or if there are not at least 2 questions
1102
                    if ($this->random == 0 || $nbQuestions < 2) {
1103
                        $questionList = $this->getQuestionOrderedList();
1104
                    } else {
1105
                        $questionList = $this->selectRandomList($adminView);
1106
                    }
1107
                    break;
1108
                default:
1109
                    $questionList = $this->getQuestionOrderedList();
1110
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1111
                        $questionList,
1112
                        $questionSelectionType
1113
                    );
1114
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1115
                    $questionList = $result['question_list'];
1116
                    break;
1117
            }
1118
1119
            return $questionList;
1120
        }
1121
1122
        return $this->questionList;
1123
    }
1124
1125
    /**
1126
     * returns the number of questions in this exercise
1127
     *
1128
     * @author Olivier Brouckaert
1129
     * @return integer - number of questions
1130
     */
1131
    public function selectNbrQuestions()
1132
    {
1133
        return sizeof($this->questionList);
1134
    }
1135
1136
    /**
1137
     * @return int
1138
     */
1139
    public function selectPropagateNeg()
1140
    {
1141
        return $this->propagate_neg;
1142
    }
1143
1144
    /**
1145
     * @return int
1146
     */
1147
    public function selectSaveCorrectAnswers()
1148
    {
1149
        return $this->saveCorrectAnswers;
1150
    }
1151
1152
    /**
1153
     * Selects questions randomly in the question list
1154
     *
1155
     * @author Olivier Brouckaert
1156
     * @author Hubert Borderiou 15 nov 2011
1157
     * @param    bool    $adminView  Whether we should return all questions (admin view) or just a list limited by the max number of random questions
1158
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1159
     *					 without randomizing, otherwise, returns the list with questions selected randomly
1160
     */
1161
    public function selectRandomList($adminView = false)
1162
    {
1163
        /*$nbQuestions	= $this->selectNbrQuestions();
1164
        $temp_list		= $this->questionList;
1165
1166
        //Not a random exercise, or if there are not at least 2 questions
1167
        if($this->random == 0 || $nbQuestions < 2) {
1168
            return $this->questionList;
1169
        }
1170
        if ($nbQuestions != 0) {
1171
            shuffle($temp_list);
1172
            $my_random_list = array_combine(range(1,$nbQuestions),$temp_list);
1173
            $my_question_list = array();
1174
            // $this->random == -1 if random with all questions
1175
            if ($this->random > 0) {
1176
                $i = 0;
1177
                foreach ($my_random_list as $item) {
1178
                    if ($i < $this->random) {
1179
                        $my_question_list[$i] = $item;
1180
                    } else {
1181
                        break;
1182
                    }
1183
                    $i++;
1184
                }
1185
            } else {
1186
                $my_question_list = $my_random_list;
1187
            }
1188
            return $my_question_list;
1189
        }*/
1190
1191
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1192
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1193
1194
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1195
1196
        $randomLimit = "ORDER BY RAND() LIMIT $random";
1197
        // Random all questions so no limit
1198
        if ($random == -1 or $adminView === true) {
1199
            // If viewing it as admin for edition, don't show it randomly, use title + id
1200
            $randomLimit = 'ORDER BY e.question_order';
1201
        }
1202
1203
        $sql = "SELECT e.question_id
1204
                FROM $TBL_EXERCISE_QUESTION e 
1205
                INNER JOIN $TBL_QUESTIONS q
1206
                ON (e.question_id= q.id AND e.c_id = q.c_id)
1207
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
1208
                $randomLimit ";
1209
        $result = Database::query($sql);
1210
        $questionList = array();
1211
        while ($row = Database::fetch_object($result)) {
1212
            $questionList[] = $row->question_id;
1213
        }
1214
1215
        return $questionList;
1216
    }
1217
1218
    /**
1219
     * returns 'true' if the question ID is in the question list
1220
     *
1221
     * @author Olivier Brouckaert
1222
     * @param integer $questionId - question ID
1223
     * @return boolean - true if in the list, otherwise false
1224
     */
1225
    public function isInList($questionId)
1226
    {
1227
        if (is_array($this->questionList)) {
1228
            return in_array($questionId, $this->questionList);
1229
        } else {
1230
            return false;
1231
        }
1232
    }
1233
1234
    /**
1235
     * changes the exercise title
1236
     *
1237
     * @author Olivier Brouckaert
1238
     * @param string $title - exercise title
1239
     */
1240
    public function updateTitle($title)
1241
    {
1242
        $this->exercise=$title;
1243
    }
1244
1245
    /**
1246
     * changes the exercise max attempts
1247
     *
1248
     * @param int $attempts - exercise max attempts
1249
     */
1250
    public function updateAttempts($attempts)
1251
    {
1252
        $this->attempts=$attempts;
1253
    }
1254
1255
    /**
1256
     * changes the exercise feedback type
1257
     *
1258
     * @param int $feedback_type
1259
     */
1260
    public function updateFeedbackType($feedback_type)
1261
    {
1262
        $this->feedback_type=$feedback_type;
1263
    }
1264
1265
    /**
1266
     * changes the exercise description
1267
     *
1268
     * @author Olivier Brouckaert
1269
     * @param string $description - exercise description
1270
     */
1271
    public function updateDescription($description)
1272
    {
1273
        $this->description=$description;
1274
    }
1275
1276
    /**
1277
     * changes the exercise expired_time
1278
     *
1279
     * @author Isaac flores
1280
     * @param int $expired_time The expired time of the quiz
1281
     */
1282
    public function updateExpiredTime($expired_time)
1283
    {
1284
        $this->expired_time = $expired_time;
1285
    }
1286
1287
    /**
1288
     * @param $value
1289
     */
1290
    public function updatePropagateNegative($value)
1291
    {
1292
        $this->propagate_neg = $value;
1293
    }
1294
1295
    /**
1296
     * @param $value int
1297
     */
1298
    public function updateSaveCorrectAnswers($value)
1299
    {
1300
        $this->saveCorrectAnswers = $value;
1301
    }
1302
1303
    /**
1304
     * @param $value
1305
     */
1306
    public function updateReviewAnswers($value)
1307
    {
1308
        $this->review_answers = isset($value) && $value ? true : false;
1309
    }
1310
1311
    /**
1312
     * @param $value
1313
     */
1314
    public function updatePassPercentage($value)
1315
    {
1316
        $this->pass_percentage = $value;
1317
    }
1318
1319
    /**
1320
     * @param string $text
1321
     */
1322
    public function updateEmailNotificationTemplate($text)
1323
    {
1324
        $this->emailNotificationTemplate = $text;
1325
    }
1326
1327
    /**
1328
     * @param string $text
1329
     */
1330
    public function updateEmailNotificationTemplateToUser($text)
1331
    {
1332
        $this->emailNotificationTemplateToUser = $text;
1333
    }
1334
1335
    /**
1336
     * @param string $value
1337
     */
1338
    public function setNotifyUserByEmail($value)
1339
    {
1340
        $this->notifyUserByEmail = $value;
1341
    }
1342
1343
    /**
1344
     * @param int $value
1345
     */
1346
    public function updateEndButton($value)
1347
    {
1348
        $this->endButton = intval($value);
1349
    }
1350
1351
    /**
1352
     * @param string $value
1353
     */
1354
    public function setOnSuccessMessage($value)
1355
    {
1356
        $this->onSuccessMessage = $value;
1357
    }
1358
1359
    /**
1360
     * @param string $value
1361
     */
1362
    public function setOnFailedMessage($value)
1363
    {
1364
        $this->onFailedMessage = $value;
1365
    }
1366
1367
    /**
1368
     * @param $value
1369
     */
1370
    public function setModelType($value)
1371
    {
1372
        $this->modelType = intval($value);
1373
    }
1374
1375
    /**
1376
     * @param int $value
1377
     */
1378
    public function setQuestionSelectionType($value)
1379
    {
1380
        $this->questionSelectionType = intval($value);
1381
    }
1382
1383
    /**
1384
     * @return int
1385
     */
1386
    public function getQuestionSelectionType()
1387
    {
1388
        return $this->questionSelectionType;
1389
    }
1390
1391
    /**
1392
     * @param array $categories
1393
     */
1394
    public function updateCategories($categories)
1395
    {
1396
        if (!empty($categories)) {
1397
            $categories = array_map('intval', $categories);
1398
            $this->categories = $categories;
1399
        }
1400
    }
1401
1402
    /**
1403
     * changes the exercise sound file
1404
     *
1405
     * @author Olivier Brouckaert
1406
     * @param string $sound - exercise sound file
1407
     * @param string $delete - ask to delete the file
1408
     */
1409
    public function updateSound($sound,$delete)
1410
    {
1411
        global $audioPath, $documentPath;
1412
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1413
1414
        if ($sound['size'] && (strstr($sound['type'],'audio') || strstr($sound['type'],'video'))) {
1415
            $this->sound=$sound['name'];
1416
1417
            if (@move_uploaded_file($sound['tmp_name'],$audioPath.'/'.$this->sound)) {
1418
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1419
                        WHERE c_id = ".$this->course_id." AND path='".str_replace($documentPath,'',$audioPath).'/'.$this->sound."'";
1420
                $result = Database::query($sql);
1421
1422
                if (!Database::num_rows($result)) {
1423
                    $id = add_document(
1424
                        $this->course,
1425
                        str_replace($documentPath,'',$audioPath).'/'.$this->sound,
1426
                        'file',
1427
                        $sound['size'],
1428
                        $sound['name']
1429
                    );
1430
                    api_item_property_update(
1431
                        $this->course,
1432
                        TOOL_DOCUMENT,
1433
                        $id,
1434
                        'DocumentAdded',
1435
                        api_get_user_id()
1436
                    );
1437
                    item_property_update_on_folder(
1438
                        $this->course,
1439
                        str_replace($documentPath, '', $audioPath),
1440
                        api_get_user_id()
1441
                    );
1442
                }
1443
            }
1444
        } elseif($delete && is_file($audioPath.'/'.$this->sound)) {
1445
            $this->sound='';
1446
        }
1447
    }
1448
1449
    /**
1450
     * changes the exercise type
1451
     *
1452
     * @author Olivier Brouckaert
1453
     * @param integer $type - exercise type
1454
     */
1455
    public function updateType($type)
1456
    {
1457
        $this->type = $type;
1458
    }
1459
1460
    /**
1461
     * sets to 0 if questions are not selected randomly
1462
     * if questions are selected randomly, sets the draws
1463
     *
1464
     * @author Olivier Brouckaert
1465
     * @param integer $random - 0 if not random, otherwise the draws
1466
     */
1467
    public function setRandom($random)
1468
    {
1469
        /*if ($random == 'all') {
1470
            $random = $this->selectNbrQuestions();
1471
        }*/
1472
        $this->random = $random;
1473
    }
1474
1475
    /**
1476
     * sets to 0 if answers are not selected randomly
1477
     * if answers are selected randomly
1478
     * @author Juan Carlos Rana
1479
     * @param integer $random_answers - random answers
1480
     */
1481
    public function updateRandomAnswers($random_answers)
1482
    {
1483
        $this->random_answers = $random_answers;
1484
    }
1485
1486
    /**
1487
     * enables the exercise
1488
     *
1489
     * @author Olivier Brouckaert
1490
     */
1491
    public function enable()
1492
    {
1493
        $this->active=1;
1494
    }
1495
1496
    /**
1497
     * disables the exercise
1498
     *
1499
     * @author Olivier Brouckaert
1500
     */
1501
    public function disable()
1502
    {
1503
        $this->active=0;
1504
    }
1505
1506
    /**
1507
     * Set disable results
1508
     */
1509
    public function disable_results()
1510
    {
1511
        $this->results_disabled = true;
1512
    }
1513
1514
    /**
1515
     * Enable results
1516
     */
1517
    public function enable_results()
1518
    {
1519
        $this->results_disabled = false;
1520
    }
1521
1522
    /**
1523
     * @param int $results_disabled
1524
     */
1525
    public function updateResultsDisabled($results_disabled)
1526
    {
1527
        $this->results_disabled = intval($results_disabled);
1528
    }
1529
1530
    /**
1531
     * updates the exercise in the data base
1532
     *
1533
     * @author Olivier Brouckaert
1534
     */
1535
    public function save($type_e = '')
1536
    {
1537
        $_course = $this->course;
1538
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1539
1540
        $id = $this->id;
1541
        $exercise = $this->exercise;
1542
        $description = $this->description;
1543
        $sound = $this->sound;
1544
        $type = $this->type;
1545
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1546
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1547
        $random = $this->random;
1548
        $random_answers = $this->random_answers;
1549
        $active = $this->active;
1550
        $propagate_neg = (int) $this->propagate_neg;
1551
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0;
1552
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1553
        $randomByCat = intval($this->randomByCat);
1554
        $text_when_finished = $this->text_when_finished;
1555
        $display_category_name = intval($this->display_category_name);
1556
        $pass_percentage = intval($this->pass_percentage);
1557
        $session_id = $this->sessionId;
1558
1559
        //If direct we do not show results
1560
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1561
            $results_disabled = 0;
1562
        } else {
1563
            $results_disabled = intval($this->results_disabled);
1564
        }
1565
1566
        $expired_time = intval($this->expired_time);
1567
1568
        // Exercise already exists
1569
        if ($id) {
1570
            // we prepare date in the database using the api_get_utc_datetime() function
1571
            if (!empty($this->start_time)) {
1572
                $start_time = $this->start_time;
1573
            } else {
1574
                $start_time = null;
1575
            }
1576
1577
            if (!empty($this->end_time)) {
1578
                $end_time = $this->end_time;
1579
            } else {
1580
                $end_time = null;
1581
            }
1582
1583
            $params = [
1584
                'title' => $exercise,
1585
                'description' => $description,
1586
            ];
1587
1588
            $paramsExtra = [];
1589
            if ($type_e != 'simple') {
1590
                $paramsExtra = [
1591
                    'sound' => $sound,
1592
                    'type' => $type,
1593
                    'random' => $random,
1594
                    'random_answers' => $random_answers,
1595
                    'active' => $active,
1596
                    'feedback_type' => $feedback_type,
1597
                    'start_time' => $start_time,
1598
                    'end_time' => $end_time,
1599
                    'max_attempt' => $attempts,
1600
                    'expired_time' => $expired_time,
1601
                    'propagate_neg' => $propagate_neg,
1602
                    'save_correct_answers' => $saveCorrectAnswers,
1603
                    'review_answers' => $review_answers,
1604
                    'random_by_category' => $randomByCat,
1605
                    'text_when_finished' => $text_when_finished,
1606
                    'display_category_name' => $display_category_name,
1607
                    'pass_percentage' => $pass_percentage,
1608
                    'results_disabled' => $results_disabled,
1609
                    'question_selection_type' => $this->getQuestionSelectionType(),
1610
                    'hide_question_title' => $this->getHideQuestionTitle()
1611
                ];
1612
            }
1613
1614
            $params = array_merge($params, $paramsExtra);
1615
1616
            Database::update(
1617
                $TBL_EXERCISES,
1618
                $params,
1619
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1620
            );
1621
1622
            // update into the item_property table
1623
            api_item_property_update(
1624
                $_course,
1625
                TOOL_QUIZ,
1626
                $id,
1627
                'QuizUpdated',
1628
                api_get_user_id()
1629
            );
1630
1631
            if (api_get_setting('search_enabled')=='true') {
1632
                $this->search_engine_edit();
1633
            }
1634
        } else {
1635
            // Creates a new exercise
1636
1637
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1638
            // for date because, bellow, we call function api_set_default_visibility()
1639
            // In this function, api_set_default_visibility,
1640
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1641
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1642
            if (!empty($this->start_time)) {
1643
                $start_time = $this->start_time;
1644
            } else {
1645
                $start_time = null;
1646
            }
1647
1648
            if (!empty($this->end_time)) {
1649
                $end_time = $this->end_time;
1650
            } else {
1651
                $end_time = null;
1652
            }
1653
1654
            $params = [
1655
                'c_id' => $this->course_id,
1656
                'start_time' => $start_time,
1657
                'end_time' => $end_time,
1658
                'title' => $exercise,
1659
                'description' => $description,
1660
                'sound' => $sound,
1661
                'type' => $type,
1662
                'random' => $random,
1663
                'random_answers' => $random_answers,
1664
                'active' => $active,
1665
                'results_disabled' => $results_disabled,
1666
                'max_attempt' => $attempts,
1667
                'feedback_type' => $feedback_type,
1668
                'expired_time' => $expired_time,
1669
                'session_id' => $session_id,
1670
                'review_answers' => $review_answers,
1671
                'random_by_category' => $randomByCat,
1672
                'text_when_finished' => $text_when_finished,
1673
                'display_category_name' => $display_category_name,
1674
                'pass_percentage' => $pass_percentage,
1675
                'save_correct_answers' => (int) $saveCorrectAnswers,
1676
                'propagate_neg' => $propagate_neg,
1677
                'hide_question_title' => $this->getHideQuestionTitle()
1678
            ];
1679
1680
            $this->id = Database::insert($TBL_EXERCISES, $params);
1681
1682
            if ($this->id) {
1683
1684
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1685
                Database::query($sql);
1686
1687
                $sql = "UPDATE $TBL_EXERCISES
1688
                        SET question_selection_type= ".intval($this->getQuestionSelectionType())."
1689
                        WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1690
                Database::query($sql);
1691
1692
                // insert into the item_property table
1693
                api_item_property_update(
1694
                    $this->course,
1695
                    TOOL_QUIZ,
1696
                    $this->id,
1697
                    'QuizAdded',
1698
                    api_get_user_id()
1699
                );
1700
1701
                // This function save the quiz again, carefull about start_time
1702
                // and end_time if you remove this line (see above)
1703
                api_set_default_visibility(
1704
                    $this->id,
1705
                    TOOL_QUIZ,
1706
                    null,
1707
                    $this->course
1708
                );
1709
1710
                if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1711
                    $this->search_engine_save();
1712
                }
1713
            }
1714
        }
1715
1716
        $this->save_categories_in_exercise($this->categories);
1717
1718
        // Updates the question position
1719
        $this->update_question_positions();
1720
    }
1721
1722
    /**
1723
     * Updates question position
1724
     */
1725
    public function update_question_positions()
1726
    {
1727
        $quiz_question_table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1728
        //Fixes #3483 when updating order
1729
        $question_list = $this->selectQuestionList(true);
1730
        if (!empty($question_list)) {
1731
            foreach ($question_list as $position => $questionId) {
1732
                $sql = "UPDATE $quiz_question_table SET
1733
                        question_order ='".intval($position)."'
1734
                        WHERE
1735
                            c_id = ".$this->course_id." AND
1736
                            question_id = ".intval($questionId)." AND
1737
                            exercice_id=".intval($this->id);
1738
                Database::query($sql);
1739
            }
1740
        }
1741
    }
1742
1743
    /**
1744
     * Adds a question into the question list
1745
     *
1746
     * @author Olivier Brouckaert
1747
     * @param integer $questionId - question ID
1748
     * @return boolean - true if the question has been added, otherwise false
1749
     */
1750
    public function addToList($questionId)
1751
    {
1752
        // checks if the question ID is not in the list
1753
        if (!$this->isInList($questionId)) {
1754
            // selects the max position
1755
            if (!$this->selectNbrQuestions()) {
1756
                $pos = 1;
1757
            } else {
1758
                if (is_array($this->questionList)) {
1759
                    $pos = max(array_keys($this->questionList)) + 1;
1760
                }
1761
            }
1762
            $this->questionList[$pos] = $questionId;
1763
1764
            return true;
1765
        }
1766
1767
        return false;
1768
    }
1769
1770
    /**
1771
     * removes a question from the question list
1772
     *
1773
     * @author Olivier Brouckaert
1774
     * @param integer $questionId - question ID
1775
     * @return boolean - true if the question has been removed, otherwise false
1776
     */
1777
    public function removeFromList($questionId)
1778
    {
1779
        // searches the position of the question ID in the list
1780
        $pos = array_search($questionId,$this->questionList);
1781
1782
        // question not found
1783
        if ($pos === false) {
1784
            return false;
1785
        } else {
1786
            // dont reduce the number of random question if we use random by category option, or if
1787
            // random all questions
1788
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1789
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1790
                    $this->random -= 1;
1791
                    $this->save();
1792
                }
1793
            }
1794
            // deletes the position from the array containing the wanted question ID
1795
            unset($this->questionList[$pos]);
1796
1797
            return true;
1798
        }
1799
    }
1800
1801
    /**
1802
     * deletes the exercise from the database
1803
     * Notice : leaves the question in the data base
1804
     *
1805
     * @author Olivier Brouckaert
1806
     */
1807
    public function delete()
1808
    {
1809
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1810
        $sql = "UPDATE $TBL_EXERCISES SET active='-1'
1811
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
1812
        Database::query($sql);
1813
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'QuizDeleted', api_get_user_id());
1814
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'delete', api_get_user_id());
1815
1816
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian') ) {
1817
            $this->search_engine_delete();
1818
        }
1819
    }
1820
1821
    /**
1822
     * Creates the form to create / edit an exercise
1823
     * @param FormValidator $form
1824
     */
1825
    public function createForm($form, $type='full')
1826
    {
1827
        if (empty($type)) {
1828
            $type = 'full';
1829
        }
1830
1831
        // form title
1832
        if (!empty($_GET['exerciseId'])) {
1833
            $form_title = get_lang('ModifyExercise');
1834
        } else {
1835
            $form_title = get_lang('NewEx');
1836
        }
1837
1838
        $form->addElement('header', $form_title);
1839
1840
        // Title.
1841
        $form->addElement(
1842
            'text',
1843
            'exerciseTitle',
1844
            get_lang('ExerciseName'),
1845
            array('id' => 'exercise_title')
1846
        );
1847
1848
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1849
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1850
1851
        $editor_config = array(
1852
            'ToolbarSet' => 'TestQuestionDescription',
1853
            'Width' => '100%',
1854
            'Height' => '150',
1855
        );
1856
        if (is_array($type)){
1857
            $editor_config = array_merge($editor_config, $type);
1858
        }
1859
1860
        $form->addHtmlEditor(
1861
            'exerciseDescription',
1862
            get_lang('ExerciseDescription'),
1863
            false,
1864
            false,
1865
            $editor_config
1866
        );
1867
1868
        if ($type == 'full') {
1869
            //Can't modify a DirectFeedback question
1870
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1871
                // feedback type
1872
                $radios_feedback = array();
1873
                $radios_feedback[] = $form->createElement(
1874
                    'radio',
1875
                    'exerciseFeedbackType',
1876
                    null,
1877
                    get_lang('ExerciseAtTheEndOfTheTest'),
1878
                    '0',
1879
                    array(
1880
                        'id' => 'exerciseType_0',
1881
                        'onclick' => 'check_feedback()',
1882
                    )
1883
                );
1884
1885 View Code Duplication
                if (api_get_setting('enable_quiz_scenario') == 'true') {
1886
                    //Can't convert a question from one feedback to another if there is more than 1 question already added
1887
                    if ($this->selectNbrQuestions() == 0) {
1888
                        $radios_feedback[] = $form->createElement(
1889
                            'radio',
1890
                            'exerciseFeedbackType',
1891
                            null,
1892
                            get_lang('DirectFeedback'),
1893
                            '1',
1894
                            array(
1895
                                'id' => 'exerciseType_1',
1896
                                'onclick' => 'check_direct_feedback()',
1897
                            )
1898
                        );
1899
                    }
1900
                }
1901
1902
                $radios_feedback[] = $form->createElement(
1903
                    'radio',
1904
                    'exerciseFeedbackType',
1905
                    null,
1906
                    get_lang('NoFeedback'),
1907
                    '2',
1908
                    array('id' => 'exerciseType_2')
1909
                );
1910
                $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
1911
1912
                // Type of results display on the final page
1913
                $radios_results_disabled = array();
1914
                $radios_results_disabled[] = $form->createElement(
1915
                    'radio',
1916
                    'results_disabled',
1917
                    null,
1918
                    get_lang('ShowScoreAndRightAnswer'),
1919
                    '0',
1920
                    array('id' => 'result_disabled_0')
1921
                );
1922
                $radios_results_disabled[] = $form->createElement(
1923
                    'radio',
1924
                    'results_disabled',
1925
                    null,
1926
                    get_lang('DoNotShowScoreNorRightAnswer'),
1927
                    '1',
1928
                    array('id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()')
1929
                );
1930
                $radios_results_disabled[] = $form->createElement(
1931
                    'radio',
1932
                    'results_disabled',
1933
                    null,
1934
                    get_lang('OnlyShowScore'),
1935
                    '2',
1936
                    array('id' => 'result_disabled_2')
1937
                );
1938
1939
1940
                $radios_results_disabled[] = $form->createElement(
1941
                    'radio',
1942
                    'results_disabled',
1943
                    null,
1944
                    get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
1945
                    '4',
1946
                    array('id' => 'result_disabled_4')
1947
                );
1948
1949
                $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'));
1950
1951
                // Type of questions disposition on page
1952
                $radios = array();
1953
1954
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1955
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1956
1957
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
1958
1959
            } else {
1960
                // if is Directfeedback but has not questions we can allow to modify the question type
1961
                if ($this->selectNbrQuestions() == 0) {
1962
1963
                    // feedback type
1964
                    $radios_feedback = array();
1965
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('ExerciseAtTheEndOfTheTest'),'0',array('id' =>'exerciseType_0', 'onclick' => 'check_feedback()'));
1966
1967 View Code Duplication
                    if (api_get_setting('enable_quiz_scenario') == 'true') {
1968
                        $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('DirectFeedback'), '1', array('id' =>'exerciseType_1' , 'onclick' => 'check_direct_feedback()'));
1969
                    }
1970
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('NoFeedback'),'2',array('id' =>'exerciseType_2'));
1971
                    $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
1972
1973
                    //$form->addElement('select', 'exerciseFeedbackType',get_lang('FeedbackType'),$feedback_option,'onchange="javascript:feedbackselection()"');
1974
                    $radios_results_disabled = array();
1975
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1976
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
1977
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
1978
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
1979
1980
                    // Type of questions disposition on page
1981
                    $radios = array();
1982
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1');
1983
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2');
1984
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
1985
1986
                } else {
1987
                    //Show options freeze
1988
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1989
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
1990
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
1991
                    $result_disable_group = $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'));
1992
                    $result_disable_group->freeze();
1993
1994
                    //we force the options to the DirectFeedback exercisetype
1995
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
1996
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
1997
1998
                    // Type of questions disposition on page
1999
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
2000
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
2001
2002
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
2003
                    $type_group->freeze();
2004
                }
2005
            }
2006
2007
            if (true) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
2008
                $option = array(
2009
                    EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2010
                    //  defined by user
2011
                    EX_Q_SELECTION_RANDOM => get_lang('Random'),
2012
                    // 1-10, All
2013
                    'per_categories' => '--------'.get_lang(
2014
                            'UsingCategories'
2015
                        ).'----------',
2016
2017
                    // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2018
                    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang(
2019
                        'OrderedCategoriesAlphabeticallyWithQuestionsOrdered'
2020
                    ),
2021
                    // A 123 B 456 C 78 (0, 1, all)
2022
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang(
2023
                        'RandomCategoriesWithQuestionsOrdered'
2024
                    ),
2025
                    // C 78 B 456 A 123
2026
2027
                    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang(
2028
                        'OrderedCategoriesAlphabeticallyWithRandomQuestions'
2029
                    ),
2030
                    // A 321 B 654 C 87
2031
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang(
2032
                        'RandomCategoriesWithRandomQuestions'
2033
                    ),
2034
                    //C 87 B 654 A 321
2035
2036
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2037
                    /*    B 456 C 78 A 123
2038
                            456 78 123
2039
                            123 456 78
2040
                    */
2041
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2042
                    /*
2043
                        A 123 B 456 C 78
2044
                        B 456 C 78 A 123
2045
                        B 654 C 87 A 321
2046
                        654 87 321
2047
                        165 842 73
2048
                    */
2049
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2050
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2051
                );
2052
2053
                $form->addElement(
2054
                    'select',
2055
                    'question_selection_type',
2056
                    array(get_lang('QuestionSelection')),
2057
                    $option,
2058
                    array(
2059
                        'id' => 'questionSelection',
2060
                        'onchange' => 'checkQuestionSelection()'
2061
                    )
2062
                );
2063
2064
                $displayMatrix = 'none';
2065
                $displayRandom = 'none';
2066
                $selectionType = $this->getQuestionSelectionType();
2067
                switch ($selectionType) {
2068
                    case EX_Q_SELECTION_RANDOM:
2069
                        $displayRandom = 'block';
2070
                        break;
2071
                    case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2072
                        $displayMatrix = 'block';
2073
                        break;
2074
                }
2075
2076
                $form->addElement(
2077
                    'html',
2078
                    '<div id="hidden_random" style="display:'.$displayRandom.'">'
2079
                );
2080
                // Number of random question.
2081
                $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
2082
                $option = range(0, $max);
2083
                $option[0] = get_lang('No');
2084
                $option[-1] = get_lang('AllQuestionsShort');
2085
                $form->addElement(
2086
                    'select',
2087
                    'randomQuestions',
2088
                    array(
2089
                        get_lang('RandomQuestions'),
2090
                        get_lang('RandomQuestionsHelp')
2091
                    ),
2092
                    $option,
2093
                    array('id' => 'randomQuestions')
2094
                );
2095
                $form->addElement('html', '</div>');
2096
2097
                $form->addElement(
2098
                    'html',
2099
                    '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2100
                );
2101
2102
                // Category selection.
2103
                $cat = new TestCategory();
2104
                $cat_form = $cat->returnCategoryForm($this);
2105
                if (empty($cat_form)) {
2106
                    $cat_form = '<span class="label label-warning">' . get_lang('NoCategoriesDefined') . '</span>';
2107
                }
2108
                $form->addElement('label', null, $cat_form);
2109
                $form->addElement('html', '</div>');
2110
2111
                // Category name.
2112
                $radio_display_cat_name = array(
2113
                    $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2114
                    $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0')
2115
                );
2116
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'));
2117
2118
                // Random answers.
2119
                $radios_random_answers = array(
2120
                    $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2121
                    $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0')
2122
                );
2123
                $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'));
2124
2125
                // Hide question title.
2126
                $group = array(
2127
                    $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2128
                    $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0')
2129
                );
2130
                $form->addGroup($group, null, get_lang('HideQuestionTitle'));
2131
            } 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...
2132
2133
                // number of random question
2134
                /*
2135
                $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10 ;
2136
                $option = range(0, $max);
2137
                $option[0] = get_lang('No');
2138
                $option[-1] = get_lang('AllQuestionsShort');
2139
                $form->addElement('select', 'randomQuestions',array(get_lang('RandomQuestions'), get_lang('RandomQuestionsHelp')), $option, array('id'=>'randomQuestions'));
2140
2141
                // Random answers
2142
                $radios_random_answers = array();
2143
                $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'),'1');
2144
                $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('No'),'0');
2145
                $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'), '');
2146
2147
                // Random by category
2148
                $form->addElement('html','<div class="clear">&nbsp;</div>');
2149
                $radiocat = array();
2150
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesShuffled'),'1');
2151
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesSorted'),'2');
2152
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('No'),'0');
2153
                $radioCatGroup = $form->addGroup($radiocat, null, get_lang('RandomQuestionByCategory'), '');
2154
                $form->addElement('html','<div class="clear">&nbsp;</div>');
2155
2156
                // add the radio display the category name for student
2157
                $radio_display_cat_name = array();
2158
                $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1');
2159
                $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0');
2160
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'), '');*/
2161
            }
2162
            // Attempts
2163
            $attempt_option = range(0, 10);
2164
            $attempt_option[0] = get_lang('Infinite');
2165
2166
            $form->addElement(
2167
                'select',
2168
                'exerciseAttempts',
2169
                get_lang('ExerciseAttempts'),
2170
                $attempt_option,
2171
                ['id' => 'exerciseAttempts']
2172
            );
2173
2174
            // Exercise time limit
2175
            $form->addElement('checkbox', 'activate_start_date_check',null, get_lang('EnableStartTime'), array('onclick' => 'activate_start_date()'));
2176
2177
            $var = Exercise::selectTimeLimit();
2178
2179
            if (!empty($this->start_time)) {
2180
                $form->addElement('html', '<div id="start_date_div" style="display:block;">');
2181
            } else {
2182
                $form->addElement('html', '<div id="start_date_div" style="display:none;">');
2183
            }
2184
2185
            $form->addElement('date_time_picker', 'start_time');
2186
2187
            $form->addElement('html','</div>');
2188
2189
            $form->addElement('checkbox', 'activate_end_date_check', null , get_lang('EnableEndTime'), array('onclick' => 'activate_end_date()'));
2190
2191
            if (!empty($this->end_time)) {
2192
                $form->addElement('html', '<div id="end_date_div" style="display:block;">');
2193
            } else {
2194
                $form->addElement('html', '<div id="end_date_div" style="display:none;">');
2195
            }
2196
2197
            $form->addElement('date_time_picker', 'end_time');
2198
            $form->addElement('html','</div>');
2199
2200
            //$check_option=$this->selectType();
2201
            $diplay = 'block';
2202
            $form->addElement('checkbox', 'propagate_neg', null, get_lang('PropagateNegativeResults'));
2203
            $form->addCheckBox(
2204
                'save_correct_answers',
2205
                null,
2206
                get_lang('SaveTheCorrectAnswersForTheNextAttempt')
2207
            );
2208
            $form->addElement('html','<div class="clear">&nbsp;</div>');
2209
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2210
2211
            $form->addElement('html','<div id="divtimecontrol"  style="display:'.$diplay.';">');
2212
2213
            //Timer control
2214
            //$time_hours_option = range(0,12);
2215
            //$time_minutes_option = range(0,59);
2216
            $form->addElement(
2217
                'checkbox',
2218
                'enabletimercontrol',
2219
                null,
2220
                get_lang('EnableTimerControl'),
2221
                array(
2222
                    'onclick' => 'option_time_expired()',
2223
                    'id' => 'enabletimercontrol',
2224
                    'onload' => 'check_load_time()',
2225
                )
2226
            );
2227
2228
            $expired_date = (int)$this->selectExpiredTime();
2229
2230
            if (($expired_date!='0')) {
2231
                $form->addElement('html','<div id="timercontrol" style="display:block;">');
2232
            } else {
2233
                $form->addElement('html','<div id="timercontrol" style="display:none;">');
2234
            }
2235
            $form->addText(
2236
                'enabletimercontroltotalminutes',
2237
                get_lang('ExerciseTotalDurationInMinutes'),
2238
                false,
2239
                [
2240
                    'id' => 'enabletimercontroltotalminutes',
2241
                    'cols-size' => [2, 2, 8]
2242
                ]
2243
            );
2244
            $form->addElement('html','</div>');
2245
2246
            $form->addElement(
2247
                'text',
2248
                'pass_percentage',
2249
                array(get_lang('PassPercentage'), null, '%'),
2250
                array('id' => 'pass_percentage')
2251
            );
2252
2253
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2254
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2255
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2256
2257
            // add the text_when_finished textbox
2258
            $form->addHtmlEditor(
2259
                'text_when_finished',
2260
                get_lang('TextWhenFinished'),
2261
                false,
2262
                false,
2263
                $editor_config
2264
            );
2265
2266
            $defaults = array();
2267
2268
            if (api_get_setting('search_enabled') === 'true') {
2269
                require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2270
2271
                $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument'));
2272
                $form->addElement('select_language', 'language', get_lang('SearchFeatureDocumentLanguage'));
2273
2274
                $specific_fields = get_specific_field_list();
2275
2276
                foreach ($specific_fields as $specific_field) {
2277
                    $form->addElement ('text', $specific_field['code'], $specific_field['name']);
2278
                    $filter = array(
2279
                        'c_id' => api_get_course_int_id(),
2280
                        'field_id' => $specific_field['id'],
2281
                        'ref_id' => $this->id,
2282
                        'tool_id' => "'" . TOOL_QUIZ . "'"
2283
                    );
2284
                    $values = get_specific_field_values_list($filter, array('value'));
2285
                    if ( !empty($values) ) {
2286
                        $arr_str_values = array();
2287
                        foreach ($values as $value) {
2288
                            $arr_str_values[] = $value['value'];
2289
                        }
2290
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2291
                    }
2292
                }
2293
            }
2294
2295
            $form->addElement('html','</div>');  //End advanced setting
2296
            $form->addElement('html','</div>');
2297
        }
2298
2299
        // submit
2300
        if (isset($_GET['exerciseId'])) {
2301
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2302
        } else {
2303
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2304
        }
2305
2306
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2307
2308
        if ($type == 'full') {
2309
            // rules
2310
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2311
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2312
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2313
        }
2314
2315
        // defaults
2316
        if ($type=='full') {
2317
            if ($this->id > 0) {
2318
                if ($this->random > $this->selectNbrQuestions()) {
2319
                    $defaults['randomQuestions'] =  $this->selectNbrQuestions();
2320
                } else {
2321
                    $defaults['randomQuestions'] = $this->random;
2322
                }
2323
2324
                $defaults['randomAnswers'] = $this->selectRandomAnswers();
2325
                $defaults['exerciseType'] = $this->selectType();
2326
                $defaults['exerciseTitle'] = $this->get_formated_title();
2327
                $defaults['exerciseDescription'] = $this->selectDescription();
2328
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2329
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2330
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2331
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2332
                $defaults['save_correct_answers'] = $this->selectSaveCorrectAnswers();
2333
                $defaults['review_answers'] = $this->review_answers;
2334
                $defaults['randomByCat'] = $this->selectRandomByCat();
2335
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2336
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2337
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2338
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2339
                $defaults['hide_question_title'] = $this->getHideQuestionTitle();
2340
2341
                if (!empty($this->start_time)) {
2342
                    $defaults['activate_start_date_check'] = 1;
2343
                }
2344
                if (!empty($this->end_time)) {
2345
                    $defaults['activate_end_date_check'] = 1;
2346
                }
2347
2348
                $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2349
                $defaults['end_time'] = empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time()+84600);
2350
2351
                // Get expired time
2352
                if ($this->expired_time != '0') {
2353
                    $defaults['enabletimercontrol'] = 1;
2354
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2355
                } else {
2356
                    $defaults['enabletimercontroltotalminutes'] = 0;
2357
                }
2358
            } else {
2359
                $defaults['exerciseType'] = 2;
2360
                $defaults['exerciseAttempts'] = 0;
2361
                $defaults['randomQuestions'] = 0;
2362
                $defaults['randomAnswers'] = 0;
2363
                $defaults['exerciseDescription'] = '';
2364
                $defaults['exerciseFeedbackType'] = 0;
2365
                $defaults['results_disabled'] = 0;
2366
                $defaults['randomByCat'] = 0;
2367
                $defaults['text_when_finished'] = '';
2368
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2369
                $defaults['display_category_name'] = 1;
2370
                $defaults['end_time']   = date('Y-m-d 12:00:00', time()+84600);
2371
                $defaults['pass_percentage'] = '';
2372
                $defaults['end_button'] = $this->selectEndButton();
2373
                $defaults['question_selection_type'] = 1;
2374
                $defaults['hide_question_title'] = 0;
2375
                $defaults['on_success_message'] = null;
2376
                $defaults['on_failed_message'] = null;
2377
            }
2378
        } else {
2379
            $defaults['exerciseTitle'] = $this->selectTitle();
2380
            $defaults['exerciseDescription'] = $this->selectDescription();
2381
        }
2382
        if (api_get_setting('search_enabled') === 'true') {
2383
            $defaults['index_document'] = 'checked="checked"';
2384
        }
2385
        $form->setDefaults($defaults);
2386
2387
        // Freeze some elements.
2388
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2389
            $elementsToFreeze = array(
2390
                'randomQuestions',
2391
                //'randomByCat',
2392
                'exerciseAttempts',
2393
                'propagate_neg',
2394
                'enabletimercontrol',
2395
                'review_answers'
2396
            );
2397
2398
            foreach ($elementsToFreeze as $elementName) {
2399
                /** @var HTML_QuickForm_element $element */
2400
                $element = $form->getElement($elementName);
2401
                $element->freeze();
2402
            }
2403
2404
            //$radioCatGroup->freeze();
2405
        }
2406
    }
2407
2408
    /**
2409
     * function which process the creation of exercises
2410
     * @param FormValidator $form
2411
     * @param string
2412
     */
2413
    public function processCreation($form, $type = '')
2414
    {
2415
        $this->updateTitle(Exercise::format_title_variable($form->getSubmitValue('exerciseTitle')));
2416
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2417
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2418
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2419
        $this->updateType($form->getSubmitValue('exerciseType'));
2420
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2421
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2422
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2423
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2424
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2425
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2426
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2427
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2428
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2429
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2430
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2431
        $this->updateCategories($form->getSubmitValue('category'));
2432
        $this->updateEndButton($form->getSubmitValue('end_button'));
2433
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2434
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2435
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2436
        $this->updateEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2437
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2438
        $this->setModelType($form->getSubmitValue('model_type'));
2439
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2440
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2441
        $this->sessionId = api_get_session_id();
2442
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2443
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2444
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2445
2446
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2447
            $start_time = $form->getSubmitValue('start_time');
2448
            $this->start_time = api_get_utc_datetime($start_time);
2449
        } else {
2450
            $this->start_time = null;
2451
        }
2452
2453
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2454
            $end_time = $form->getSubmitValue('end_time');
2455
            $this->end_time = api_get_utc_datetime($end_time);
2456
        } else {
2457
            $this->end_time = null;
2458
        }
2459
2460
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2461
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2462
            if ($this->expired_time == 0) {
2463
                $this->expired_time = $expired_total_time;
2464
            }
2465
        } else {
2466
            $this->expired_time = 0;
2467
        }
2468
2469
        if ($form->getSubmitValue('randomAnswers') == 1) {
2470
            $this->random_answers=1;
2471
        } else {
2472
            $this->random_answers=0;
2473
        }
2474
        $this->save($type);
2475
    }
2476
2477
    function search_engine_save()
2478
    {
2479
        if ($_POST['index_document'] != 1) {
2480
            return;
2481
        }
2482
        $course_id = api_get_course_id();
2483
2484
        require_once api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php';
2485
        require_once api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php';
2486
        require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2487
2488
        $specific_fields = get_specific_field_list();
2489
        $ic_slide = new IndexableChunk();
2490
2491
        $all_specific_terms = '';
2492 View Code Duplication
        foreach ($specific_fields as $specific_field) {
2493
            if (isset($_REQUEST[$specific_field['code']])) {
2494
                $sterms = trim($_REQUEST[$specific_field['code']]);
2495
                if (!empty($sterms)) {
2496
                    $all_specific_terms .= ' '. $sterms;
2497
                    $sterms = explode(',', $sterms);
2498
                    foreach ($sterms as $sterm) {
2499
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2500
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2501
                    }
2502
                }
2503
            }
2504
        }
2505
2506
        // build the chunk to index
2507
        $ic_slide->addValue("title", $this->exercise);
2508
        $ic_slide->addCourseId($course_id);
2509
        $ic_slide->addToolId(TOOL_QUIZ);
2510
        $xapian_data = array(
2511
            SE_COURSE_ID => $course_id,
2512
            SE_TOOL_ID => TOOL_QUIZ,
2513
            SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2514
            SE_USER => (int)api_get_user_id(),
2515
        );
2516
        $ic_slide->xapian_data = serialize($xapian_data);
2517
        $exercise_description = $all_specific_terms .' '. $this->description;
2518
        $ic_slide->addValue("content", $exercise_description);
2519
2520
        $di = new ChamiloIndexer();
2521
        isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2522
        $di->connectDb(NULL, NULL, $lang);
2523
        $di->addChunk($ic_slide);
2524
2525
        //index and return search engine document id
2526
        $did = $di->index();
2527
        if ($did) {
2528
            // save it to db
2529
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2530
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2531
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2532
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2533
            Database::query($sql);
2534
        }
2535
    }
2536
2537
    function search_engine_edit()
2538
    {
2539
        // update search enchine and its values table if enabled
2540
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian')) {
2541
            $course_id = api_get_course_id();
2542
2543
            // actually, it consists on delete terms from db,
2544
            // insert new ones, create a new search engine document, and remove the old one
2545
            // get search_did
2546
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2547
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2548
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2549
            $res = Database::query($sql);
2550
2551
            if (Database::num_rows($res) > 0) {
2552
                require_once(api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php');
2553
                require_once(api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php');
2554
                require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
2555
2556
                $se_ref = Database::fetch_array($res);
2557
                $specific_fields = get_specific_field_list();
2558
                $ic_slide = new IndexableChunk();
2559
2560
                $all_specific_terms = '';
2561
                foreach ($specific_fields as $specific_field) {
2562
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2563
                    if (isset($_REQUEST[$specific_field['code']])) {
2564
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2565
                        $all_specific_terms .= ' '. $sterms;
2566
                        $sterms = explode(',', $sterms);
2567
                        foreach ($sterms as $sterm) {
2568
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2569
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2570
                        }
2571
                    }
2572
                }
2573
2574
                // build the chunk to index
2575
                $ic_slide->addValue("title", $this->exercise);
2576
                $ic_slide->addCourseId($course_id);
2577
                $ic_slide->addToolId(TOOL_QUIZ);
2578
                $xapian_data = array(
2579
                    SE_COURSE_ID => $course_id,
2580
                    SE_TOOL_ID => TOOL_QUIZ,
2581
                    SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2582
                    SE_USER => (int)api_get_user_id(),
2583
                );
2584
                $ic_slide->xapian_data = serialize($xapian_data);
2585
                $exercise_description = $all_specific_terms .' '. $this->description;
2586
                $ic_slide->addValue("content", $exercise_description);
2587
2588
                $di = new ChamiloIndexer();
2589
                isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2590
                $di->connectDb(NULL, NULL, $lang);
2591
                $di->remove_document((int)$se_ref['search_did']);
2592
                $di->addChunk($ic_slide);
2593
2594
                //index and return search engine document id
2595
                $did = $di->index();
2596
                if ($did) {
2597
                    // save it to db
2598
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2599
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2600
                    Database::query($sql);
2601
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2602
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2603
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2604
                    Database::query($sql);
2605
                }
2606
            } else {
2607
                $this->search_engine_save();
2608
            }
2609
        }
2610
2611
    }
2612
2613
    function search_engine_delete()
2614
    {
2615
        // remove from search engine if enabled
2616
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian') ) {
2617
            $course_id = api_get_course_id();
2618
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2619
            $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';
2620
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2621
            $res = Database::query($sql);
2622
            if (Database::num_rows($res) > 0) {
2623
                $row = Database::fetch_array($res);
2624
                require_once(api_get_path(LIBRARY_PATH) .'search/ChamiloIndexer.class.php');
2625
                $di = new ChamiloIndexer();
2626
                $di->remove_document((int)$row['search_did']);
2627
                unset($di);
2628
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2629
                foreach ( $this->questionList as $question_i) {
2630
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2631
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2632
                    $qres = Database::query($sql);
2633
                    if (Database::num_rows($qres) > 0) {
2634
                        $qrow = Database::fetch_array($qres);
2635
                        $objQuestion = Question::getInstance($qrow['type']);
2636
                        $objQuestion = Question::read((int)$question_i);
2637
                        $objQuestion->search_engine_edit($this->id, FALSE, TRUE);
2638
                        unset($objQuestion);
2639
                    }
2640
                }
2641
            }
2642
            $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';
2643
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2644
            Database::query($sql);
2645
2646
            // remove terms from db
2647
            require_once api_get_path(LIBRARY_PATH) .'specific_fields_manager.lib.php';
2648
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2649
        }
2650
    }
2651
2652
    public function selectExpiredTime()
2653
    {
2654
        return $this->expired_time;
2655
    }
2656
2657
    /**
2658
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2659
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2660
     * Works with exercises in sessions
2661
     * @param bool $cleanLpTests
2662
     * @param string $cleanResultBeforeDate
2663
     *
2664
     * @return int quantity of user's exercises deleted
2665
     */
2666
    public function clean_results($cleanLpTests = false, $cleanResultBeforeDate = null)
2667
    {
2668
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2669
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2670
2671
        $sql_where = '  AND
2672
                        orig_lp_id = 0 AND
2673
                        orig_lp_item_id = 0';
2674
2675
        // if we want to delete results from LP too
2676
        if ($cleanLpTests) {
2677
            $sql_where = "";
2678
        }
2679
2680
        // if we want to delete attempts before date $cleanResultBeforeDate
2681
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2682
2683
        if (!empty($cleanResultBeforeDate)) {
2684
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2685
            if (api_is_valid_date($cleanResultBeforeDate)) {
2686
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2687
            } else {
2688
                return 0;
2689
            }
2690
        }
2691
2692
        $sql = "SELECT exe_id
2693
                FROM $table_track_e_exercises
2694
                WHERE
2695
                    c_id = ".api_get_course_int_id()." AND
2696
                    exe_exo_id = ".$this->id." AND
2697
                    session_id = ".api_get_session_id()." ".
2698
                    $sql_where;
2699
2700
        $result   = Database::query($sql);
2701
        $exe_list = Database::store_result($result);
2702
2703
        // deleting TRACK_E_ATTEMPT table
2704
        // check if exe in learning path or not
2705
        $i = 0;
2706
        if (is_array($exe_list) && count($exe_list) > 0) {
2707
            foreach ($exe_list as $item) {
2708
                $sql = "DELETE FROM $table_track_e_attempt
2709
                        WHERE exe_id = '".$item['exe_id']."'";
2710
                Database::query($sql);
2711
                $i++;
2712
            }
2713
        }
2714
2715
        $session_id = api_get_session_id();
2716
        // delete TRACK_E_EXERCISES table
2717
        $sql = "DELETE FROM $table_track_e_exercises
2718
                WHERE c_id = ".api_get_course_int_id()."
2719
                AND exe_exo_id = ".$this->id."
2720
                $sql_where
2721
                AND session_id = ".$session_id."";
2722
        Database::query($sql);
2723
2724
        Event::addEvent(
2725
            LOG_EXERCISE_RESULT_DELETE,
2726
            LOG_EXERCISE_ID,
2727
            $this->id,
2728
            null,
2729
            null,
2730
            api_get_course_int_id(),
2731
            $session_id
2732
        );
2733
2734
        return $i;
2735
    }
2736
2737
    /**
2738
     * Copies an exercise (duplicate all questions and answers)
2739
     */
2740
    public function copy_exercise()
2741
    {
2742
        $exercise_obj = $this;
2743
2744
        // force the creation of a new exercise
2745
        $exercise_obj->updateTitle($exercise_obj->selectTitle().' - '.get_lang('Copy'));
2746
        //Hides the new exercise
2747
        $exercise_obj->updateStatus(false);
2748
        $exercise_obj->updateId(0);
2749
        $exercise_obj->save();
2750
2751
        $new_exercise_id = $exercise_obj->selectId();
2752
        $question_list 	 = $exercise_obj->selectQuestionList();
2753
2754
        if (!empty($question_list)) {
2755
            //Question creation
2756
2757
            foreach ($question_list as $old_question_id) {
2758
                $old_question_obj = Question::read($old_question_id);
2759
                $new_id = $old_question_obj->duplicate();
2760
                if ($new_id) {
2761
                    $new_question_obj = Question::read($new_id);
2762
2763
                    if (isset($new_question_obj) && $new_question_obj) {
2764
                        $new_question_obj->addToList($new_exercise_id);
2765
                        // This should be moved to the duplicate function
2766
                        $new_answer_obj = new Answer($old_question_id);
2767
                        $new_answer_obj->read();
2768
                        $new_answer_obj->duplicate($new_id);
2769
                    }
2770
                }
2771
            }
2772
        }
2773
    }
2774
2775
    /**
2776
     * Changes the exercise id
2777
     *
2778
     * @param int $id - exercise id
2779
     */
2780
    private function updateId($id)
2781
    {
2782
        $this->id = $id;
2783
    }
2784
2785
    /**
2786
     * Changes the exercise status
2787
     *
2788
     * @param string $status - exercise status
2789
     */
2790
    function updateStatus($status)
2791
    {
2792
        $this->active = $status;
2793
    }
2794
2795
    /**
2796
     * @param int $lp_id
2797
     * @param int $lp_item_id
2798
     * @param int $lp_item_view_id
2799
     * @param string $status
2800
     * @return array
2801
     */
2802
    public function get_stat_track_exercise_info(
2803
        $lp_id = 0,
2804
        $lp_item_id = 0,
2805
        $lp_item_view_id = 0,
2806
        $status = 'incomplete'
2807
    ) {
2808
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2809
        if (empty($lp_id)) {
2810
            $lp_id = 0;
2811
        }
2812
        if (empty($lp_item_id)) {
2813
            $lp_item_id   = 0;
2814
        }
2815
        if (empty($lp_item_view_id)) {
2816
            $lp_item_view_id = 0;
2817
        }
2818
        $condition = ' WHERE exe_exo_id 	= ' . "'" . $this->id . "'" .' AND
2819
					   exe_user_id 			= ' . "'" . api_get_user_id() . "'" . ' AND
2820
					   c_id                 = ' . api_get_course_int_id() . ' AND
2821
					   status 				= ' . "'" . Database::escape_string($status). "'" . ' AND
2822
					   orig_lp_id 			= ' . "'" . $lp_id . "'" . ' AND
2823
					   orig_lp_item_id 		= ' . "'" . $lp_item_id . "'" . ' AND
2824
                       orig_lp_item_view_id = ' . "'" . $lp_item_view_id . "'" . ' AND
2825
					   session_id 			= ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case
2826
2827
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2828
2829
        $result = Database::query($sql_track);
2830
        $new_array = array();
2831
        if (Database::num_rows($result) > 0 ) {
2832
            $new_array = Database::fetch_array($result, 'ASSOC');
2833
            $new_array['num_exe'] = Database::num_rows($result);
2834
        }
2835
2836
        return $new_array;
2837
    }
2838
2839
    /**
2840
     * Saves a test attempt
2841
     *
2842
     * @param int  clock_expired_time
2843
     * @param int  int lp id
2844
     * @param int  int lp item id
2845
     * @param int  int lp item_view id
2846
     * @param float $weight
2847
     * @param array question list
2848
     */
2849
    public function save_stat_track_exercise_info(
2850
        $clock_expired_time = 0,
2851
        $safe_lp_id = 0,
2852
        $safe_lp_item_id = 0,
2853
        $safe_lp_item_view_id = 0,
2854
        $questionList = array(),
2855
        $weight = 0
2856
    ) {
2857
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2858
        $safe_lp_id = intval($safe_lp_id);
2859
        $safe_lp_item_id = intval($safe_lp_item_id);
2860
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
2861
2862
        if (empty($safe_lp_id)) {
2863
            $safe_lp_id = 0;
2864
        }
2865
        if (empty($safe_lp_item_id)) {
2866
            $safe_lp_item_id = 0;
2867
        }
2868
        if (empty($clock_expired_time)) {
2869
            $clock_expired_time = null;
2870
        }
2871
2872
        $questionList = array_map('intval', $questionList);
2873
2874
        $params = array(
2875
            'exe_exo_id' => $this->id ,
2876
            'exe_user_id' => api_get_user_id(),
2877
            'c_id' => api_get_course_int_id(),
2878
            'status' =>  'incomplete',
2879
            'session_id'  => api_get_session_id(),
2880
            'data_tracking'  => implode(',', $questionList) ,
2881
            'start_date' => api_get_utc_datetime(),
2882
            'orig_lp_id' => $safe_lp_id,
2883
            'orig_lp_item_id'  => $safe_lp_item_id,
2884
            'orig_lp_item_view_id'  => $safe_lp_item_view_id,
2885
            'exe_weighting'=> $weight,
2886
            'user_ip' => api_get_real_ip(),
2887
            'exe_date' => api_get_utc_datetime(),
2888
            'exe_result' => 0,
2889
            'steps_counter' => 0,
2890
            'exe_duration' => 0,
2891
            'expired_time_control' => $clock_expired_time,
2892
            'questions_to_check' => ''
2893
        );
2894
2895
        $id = Database::insert($track_exercises, $params);
2896
2897
        return $id;
2898
    }
2899
2900
    /**
2901
     * @param int $question_id
2902
     * @param int $questionNum
2903
     * @param array $questions_in_media
2904
     * @param string $currentAnswer
2905
     * @return string
2906
     */
2907
    public function show_button($question_id, $questionNum, $questions_in_media = array(), $currentAnswer = '')
2908
    {
2909
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
2910
2911
        $nbrQuestions = $this->get_count_question_list();
2912
2913
        $all_button = $html = $label = '';
2914
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']):null;
2915
2916
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
2917
            $urlTitle = get_lang('ContinueTest');
2918
2919
            if ($questionNum == count($this->questionList)) {
2920
                $urlTitle = get_lang('EndTest');
2921
            }
2922
2923
            $html .= Display::url(
2924
                $urlTitle,
2925
                'exercise_submit_modal.php?' . http_build_query([
2926
                    'learnpath_id' => $safe_lp_id,
2927
                    'learnpath_item_id' => $safe_lp_item_id,
2928
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
2929
                    'origin' => $origin,
2930
                    'hotspot' => $hotspot_get,
2931
                    'nbrQuestions' => $nbrQuestions,
2932
                    'num' => $questionNum,
2933
                    'exerciseType' => $this->type,
2934
                    'exerciseId' => $this->id
2935
                ]),
2936
                [
2937
                    'class' => 'ajax btn btn-default',
2938
                    'data-title' => $urlTitle,
2939
                    'data-size' => 'md'
2940
                ]
2941
            );
2942
            $html .='<br />';
2943
        } else {
2944
            // User
2945
            if (api_is_allowed_to_session_edit()) {
2946
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum) {
2947 View Code Duplication
                    if ($this->review_answers) {
2948
                        $label = get_lang('ReviewQuestions');
2949
                        $class = 'btn btn-success';
2950
                    } else {
2951
                        $label = get_lang('EndTest');
2952
                        $class = 'btn btn-warning';
2953
                    }
2954
                } else {
2955
                    $label = get_lang('NextQuestion');
2956
                    $class = 'btn btn-primary';
2957
                }
2958
				$class .= ' question-validate-btn'; // used to select it with jquery
2959
                if ($this->type == ONE_PER_PAGE) {
2960
                    if ($questionNum != 1) {
2961
                        $prev_question = $questionNum - 2;
2962
                        $all_button .= '<a href="javascript://" class="btn btn-default" onclick="previous_question_and_save('.$prev_question.', '.$question_id.' ); ">'.get_lang('PreviousQuestion').'</a>';
2963
                    }
2964
2965
                    //Next question
2966
                    if (!empty($questions_in_media)) {
2967
                        $questions_in_media = "['".implode("','",$questions_in_media)."']";
2968
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_question_list('.$questions_in_media.'); ">'.$label.'</a>';
2969
                    } else {
2970
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_now('.$question_id.', \'\', \''.$currentAnswer.'\'); ">'.$label.'</a>';
2971
                    }
2972
                    $all_button .= '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
2973
2974
                    $html .= $all_button;
2975
                } else {
2976 View Code Duplication
                    if ($this->review_answers) {
2977
                        $all_label = get_lang('ReviewQuestions');
2978
                        $class = 'btn btn-success';
2979
                    } else {
2980
                        $all_label = get_lang('EndTest');
2981
                        $class = 'btn btn-warning';
2982
                    }
2983
					$class .= ' question-validate-btn'; // used to select it with jquery
2984
                    $all_button = '&nbsp;<a href="javascript://" class="'.$class.'" onclick="validate_all(); ">'.$all_label.'</a>';
2985
                    $all_button .= '&nbsp;' . Display::span(null, ['id' => 'save_all_reponse']);
2986
                    $html .= $all_button;
2987
                }
2988
            }
2989
        }
2990
2991
        return $html;
2992
    }
2993
2994
    /**
2995
     * So the time control will work
2996
     *
2997
     * @param string $time_left
2998
     * @return string
2999
     */
3000
    public function show_time_control_js($time_left)
3001
    {
3002
        $time_left = intval($time_left);
3003
        return "<script>
3004
3005
            function get_expired_date_string(expired_time) {
3006
                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
3007
                var day, month, year, hours, minutes, seconds, date_string;
3008
                var obj_date = new Date(expired_time);
3009
                day     = obj_date.getDate();
3010
                if (day < 10) day = '0' + day;
3011
                    month   = obj_date.getMonth();
3012
                    year    = obj_date.getFullYear();
3013
                    hours   = obj_date.getHours();
3014
                if (hours < 10) hours = '0' + hours;
3015
                minutes = obj_date.getMinutes();
3016
                if (minutes < 10) minutes = '0' + minutes;
3017
                seconds = obj_date.getSeconds();
3018
                if (seconds < 10) seconds = '0' + seconds;
3019
                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
3020
                return date_string;
3021
            }
3022
3023
            function open_clock_warning() {
3024
                $('#clock_warning').dialog({
3025
                    modal:true,
3026
                    height:250,
3027
                    closeOnEscape: false,
3028
                    resizable: false,
3029
                    buttons: {
3030
                        '".addslashes(get_lang("EndTest"))."': function() {
3031
                            $('#clock_warning').dialog('close');
3032
                        }
3033
                    },
3034
                    close: function() {
3035
                        send_form();
3036
                    }
3037
                });
3038
                $('#clock_warning').dialog('open');
3039
3040
                $('#counter_to_redirect').epiclock({
3041
                    mode: $.epiclock.modes.countdown,
3042
                    offset: {seconds: 5},
3043
                    format: 's'
3044
                }).bind('timer', function () {
3045
                    send_form();
3046
                });
3047
3048
            }
3049
3050
            function send_form() {
3051
                if ($('#exercise_form').length) {
3052
                    $('#exercise_form').submit();
3053
                } else {
3054
                    //In reminder
3055
                    final_submit();
3056
                }
3057
            }
3058
3059
            function onExpiredTimeExercise() {
3060
                $('#wrapper-clock').hide();
3061
                $('#exercise_form').hide();
3062
                $('#expired-message-id').show();
3063
3064
                //Fixes bug #5263
3065
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3066
                open_clock_warning();
3067
            }
3068
3069
			$(document).ready(function() {
3070
3071
				var current_time = new Date().getTime();
3072
                var time_left    = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
3073
				var expired_time = current_time + (time_left*1000);
3074
				var expired_date = get_expired_date_string(expired_time);
3075
3076
                $('#exercise_clock_warning').epiclock({
3077
                    mode: $.epiclock.modes.countdown,
3078
                    offset: {seconds: time_left},
3079
                    format: 'x:i:s',
3080
                    renderer: 'minute'
3081
                }).bind('timer', function () {
3082
                    onExpiredTimeExercise();
3083
                });
3084
	       		$('#submit_save').click(function () {});
3085
	    });
3086
	    </script>";
3087
    }
3088
3089
    /**
3090
     * Lp javascript for hotspots
3091
     */
3092
    public function show_lp_javascript()
3093
    {
3094
        return '';
3095
    }
3096
3097
    /**
3098
     * This function was originally found in the exercise_show.php
3099
     * @param int       $exeId
3100
     * @param int       $questionId
3101
     * @param int       $choice the user selected
3102
     * @param string    $from  function is called from 'exercise_show' or 'exercise_result'
3103
     * @param array     $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates
3104
     * @param bool      $saved_results save results in the DB or just show the reponse
3105
     * @param bool      $from_database gets information from DB or from the current selection
3106
     * @param bool      $show_result show results or not
3107
     * @param int       $propagate_neg
3108
     * @param array     $hotspot_delineation_result
3109
     * @param boolean $showTotalScoreAndUserChoicesInLastAttempt
3110
     * @todo    reduce parameters of this function
3111
     * @return  string  html code
3112
     */
3113
    public function manage_answer(
3114
        $exeId,
3115
        $questionId,
3116
        $choice,
3117
        $from = 'exercise_show',
3118
        $exerciseResultCoordinates = array(),
3119
        $saved_results = true,
3120
        $from_database = false,
3121
        $show_result = true,
3122
        $propagate_neg = 0,
3123
        $hotspot_delineation_result = array(),
3124
        $showTotalScoreAndUserChoicesInLastAttempt = true
3125
    ) {
3126
        global $debug;
3127
        //needed in order to use in the exercise_attempt() for the time
3128
        global $learnpath_id, $learnpath_item_id;
3129
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3130
3131
        $em = Database::getManager();
3132
3133
        $feedback_type = $this->selectFeedbackType();
3134
        $results_disabled = $this->selectResultsDisabled();
3135
3136
        if ($debug) {
3137
            error_log("<------ manage_answer ------> ");
3138
            error_log('exe_id: '.$exeId);
3139
            error_log('$from:  '.$from);
3140
            error_log('$saved_results: '.intval($saved_results));
3141
            error_log('$from_database: '.intval($from_database));
3142
            error_log('$show_result: '.$show_result);
3143
            error_log('$propagate_neg: '.$propagate_neg);
3144
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3145
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3146
            error_log('$learnpath_id: '.$learnpath_id);
3147
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3148
            error_log('$choice: '.print_r($choice, 1));
3149
        }
3150
3151
        $extra_data = array();
3152
        $final_overlap = 0;
3153
        $final_missing = 0;
3154
        $final_excess = 0;
3155
        $overlap_color = 0;
3156
        $missing_color = 0;
3157
        $excess_color = 0;
3158
        $threadhold1 = 0;
3159
        $threadhold2 = 0;
3160
        $threadhold3 = 0;
3161
3162
        $arrques = null;
3163
        $arrans  = null;
3164
3165
        $questionId = intval($questionId);
3166
        $exeId = intval($exeId);
3167
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3168
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3169
3170
        // Creates a temporary Question object
3171
        $course_id = $this->course_id;
3172
        $objQuestionTmp = Question::read($questionId, $course_id);
3173
3174
        if ($objQuestionTmp === false) {
3175
            return false;
3176
        }
3177
3178
        $questionName = $objQuestionTmp->selectTitle();
3179
        $questionWeighting = $objQuestionTmp->selectWeighting();
3180
        $answerType = $objQuestionTmp->selectType();
3181
        $quesId = $objQuestionTmp->selectId();
3182
        $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...
3183
3184
        $next = 1; //not for now
3185
3186
        // Extra information of the question
3187
        if (!empty($extra)) {
3188
            $extra = explode(':', $extra);
3189
            if ($debug) {
3190
                error_log(print_r($extra, 1));
3191
            }
3192
            // Fixes problems with negatives values using intval
3193
            $true_score = floatval(trim($extra[0]));
3194
            $false_score = floatval(trim($extra[1]));
3195
            $doubt_score = floatval(trim($extra[2]));
3196
        }
3197
3198
        $totalWeighting = 0;
3199
        $totalScore = 0;
3200
3201
        // Construction of the Answer object
3202
        $objAnswerTmp = new Answer($questionId);
3203
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3204
3205
        if ($debug) {
3206
            error_log('Count of answers: '.$nbrAnswers);
3207
            error_log('$answerType: '.$answerType);
3208
        }
3209
3210
        if ($answerType == FREE_ANSWER ||
3211
            $answerType == ORAL_EXPRESSION ||
3212
            $answerType == CALCULATED_ANSWER
3213
        ) {
3214
            $nbrAnswers = 1;
3215
        }
3216
3217
        if ($answerType == ORAL_EXPRESSION) {
3218
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3219
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3220
3221
            $objQuestionTmp->initFile(
3222
                api_get_session_id(),
3223
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3224
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3225
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3226
            );
3227
3228
            //probably this attempt came in an exercise all question by page
3229
            if ($feedback_type == 0) {
3230
                $objQuestionTmp->replaceWithRealExe($exeId);
3231
            }
3232
        }
3233
3234
        $user_answer = '';
3235
3236
        // Get answer list for matching
3237
        $sql = "SELECT id_auto, id, answer
3238
                FROM $table_ans
3239
                WHERE c_id = $course_id AND question_id = $questionId";
3240
        $res_answer = Database::query($sql);
3241
3242
        $answerMatching = array();
3243
        while ($real_answer = Database::fetch_array($res_answer)) {
3244
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3245
        }
3246
3247
        $real_answers = array();
3248
        $quiz_question_options = Question::readQuestionOption(
3249
            $questionId,
3250
            $course_id
3251
        );
3252
3253
        $organs_at_risk_hit = 0;
3254
        $questionScore = 0;
3255
        $answer_correct_array = array();
3256
        $orderedHotspots = [];
3257
3258
        if ($answerType == HOT_SPOT) {
3259
            $orderedHotspots = $em
3260
                ->getRepository('ChamiloCoreBundle:TrackEHotspot')
3261
                ->findBy([
3262
                        'hotspotQuestionId' => $questionId,
3263
                        'cId' => $course_id,
3264
                        'hotspotExeId' => $exeId
3265
                    ],
3266
                    ['hotspotId' => 'ASC']
3267
                );
3268
        }
3269
3270
        if ($debug) error_log('Start answer loop ');
3271
3272
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3273
            $answer = $objAnswerTmp->selectAnswer($answerId);
3274
            $answerComment = $objAnswerTmp->selectComment($answerId);
3275
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3276
            $answerWeighting = (float)$objAnswerTmp->selectWeighting($answerId);
3277
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3278
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? $objAnswerTmp->iid[$answerId] : '';
3279
3280
            $answer_correct_array[$answerId] = (bool)$answerCorrect;
3281
3282
            if ($debug) {
3283
                error_log("answer auto id: $answerAutoId ");
3284
                error_log("answer correct: $answerCorrect ");
3285
            }
3286
3287
            // Delineation
3288
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3289
            $answer_delineation_destination=$objAnswerTmp->selectDestination(1);
3290
3291
            switch ($answerType) {
3292
                // for unique answer
3293
                case UNIQUE_ANSWER:
3294
                case UNIQUE_ANSWER_IMAGE:
3295
                case UNIQUE_ANSWER_NO_OPTION:
3296
                    if ($from_database) {
3297
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3298
                                WHERE
3299
                                    exe_id = '".$exeId."' AND
3300
                                    question_id= '".$questionId."'";
3301
                        $result = Database::query($sql);
3302
                        $choice = Database::result($result,0,"answer");
3303
3304
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3305
                        if ($studentChoice) {
3306
                            $questionScore += $answerWeighting;
3307
                            $totalScore += $answerWeighting;
3308
                        }
3309
                    } else {
3310
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3311
                        if ($studentChoice) {
3312
                            $questionScore += $answerWeighting;
3313
                            $totalScore += $answerWeighting;
3314
                        }
3315
                    }
3316
                    break;
3317
                // for multiple answers
3318
                case MULTIPLE_ANSWER_TRUE_FALSE:
3319
                    if ($from_database) {
3320
                        $choice = array();
3321
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3322
                                WHERE
3323
                                    exe_id = $exeId AND
3324
                                    question_id = ".$questionId;
3325
3326
                        $result = Database::query($sql);
3327 View Code Duplication
                        while ($row = Database::fetch_array($result)) {
3328
                            $ind = $row['answer'];
3329
                            $values = explode(':', $ind);
3330
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3331
                            $option = isset($values[1]) ? $values[1] : '';
3332
                            $choice[$my_answer_id] = $option;
3333
                        }
3334
                    }
3335
3336
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3337
3338
                    if (!empty($studentChoice)) {
3339
                        if ($studentChoice == $answerCorrect) {
3340
                            $questionScore += $true_score;
3341
                        } else {
3342
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3343
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3344
                            ) {
3345
                                $questionScore += $doubt_score;
3346
                            } else {
3347
                                $questionScore += $false_score;
3348
                            }
3349
                        }
3350
                    } else {
3351
                        // If no result then the user just hit don't know
3352
                        $studentChoice = 3;
3353
                        $questionScore  +=  $doubt_score;
3354
                    }
3355
                    $totalScore = $questionScore;
3356
                    break;
3357 View Code Duplication
                case MULTIPLE_ANSWER: //2
3358
                    if ($from_database) {
3359
                        $choice = array();
3360
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3361
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3362
                        $resultans = Database::query($sql);
3363
                        while ($row = Database::fetch_array($resultans)) {
3364
                            $ind = $row['answer'];
3365
                            $choice[$ind] = 1;
3366
                        }
3367
3368
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3369
                        $real_answers[$answerId] = (bool)$studentChoice;
3370
3371
                        if ($studentChoice) {
3372
                            $questionScore  +=$answerWeighting;
3373
                        }
3374
                    } else {
3375
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3376
                        $real_answers[$answerId] = (bool)$studentChoice;
3377
3378
                        if (isset($studentChoice)) {
3379
                            $questionScore  += $answerWeighting;
3380
                        }
3381
                    }
3382
                    $totalScore += $answerWeighting;
3383
3384
                    if ($debug) error_log("studentChoice: $studentChoice");
3385
                    break;
3386 View Code Duplication
                case GLOBAL_MULTIPLE_ANSWER:
3387
                    if ($from_database) {
3388
                        $choice = array();
3389
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3390
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3391
                        $resultans = Database::query($sql);
3392
                        while ($row = Database::fetch_array($resultans)) {
3393
                            $ind = $row['answer'];
3394
                            $choice[$ind] = 1;
3395
                        }
3396
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3397
                        $real_answers[$answerId] = (bool)$studentChoice;
3398
                        if ($studentChoice) {
3399
                            $questionScore +=$answerWeighting;
3400
                        }
3401
                    } else {
3402
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3403
                        if (isset($studentChoice)) {
3404
                            $questionScore += $answerWeighting;
3405
                        }
3406
                        $real_answers[$answerId] = (bool)$studentChoice;
3407
                    }
3408
                    $totalScore += $answerWeighting;
3409
                    if ($debug) error_log("studentChoice: $studentChoice");
3410
                    break;
3411
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3412
                    if ($from_database) {
3413
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3414
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3415
                        $resultans = Database::query($sql);
3416 View Code Duplication
                        while ($row = Database::fetch_array($resultans)) {
3417
                            $ind = $row['answer'];
3418
                            $result = explode(':',$ind);
3419
                            if (isset($result[0])) {
3420
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3421
                                $option = isset($result[1]) ? $result[1] : '';
3422
                                $choice[$my_answer_id] = $option;
3423
                            }
3424
                        }
3425
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3426
3427
                        if ($answerCorrect == $studentChoice) {
3428
                            //$answerCorrect = 1;
3429
                            $real_answers[$answerId] = true;
3430
                        } else {
3431
                            //$answerCorrect = 0;
3432
                            $real_answers[$answerId] = false;
3433
                        }
3434
                    } else {
3435
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3436
                        if ($answerCorrect == $studentChoice) {
3437
                            //$answerCorrect = 1;
3438
                            $real_answers[$answerId] = true;
3439
                        } else {
3440
                            //$answerCorrect = 0;
3441
                            $real_answers[$answerId] = false;
3442
                        }
3443
                    }
3444
                    break;
3445
                case MULTIPLE_ANSWER_COMBINATION:
3446
                    if ($from_database) {
3447
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3448
                                WHERE exe_id = $exeId AND question_id= $questionId";
3449
                        $resultans = Database::query($sql);
3450
                        while ($row = Database::fetch_array($resultans)) {
3451
                            $ind = $row['answer'];
3452
                            $choice[$ind] = 1;
3453
                        }
3454
3455
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3456
3457 View Code Duplication
                        if ($answerCorrect == 1) {
3458
                            if ($studentChoice) {
3459
                                $real_answers[$answerId] = true;
3460
                            } else {
3461
                                $real_answers[$answerId] = false;
3462
                            }
3463
                        } else {
3464
                            if ($studentChoice) {
3465
                                $real_answers[$answerId] = false;
3466
                            } else {
3467
                                $real_answers[$answerId] = true;
3468
                            }
3469
                        }
3470
                    } else {
3471
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3472
3473 View Code Duplication
                        if ($answerCorrect == 1) {
3474
                            if ($studentChoice) {
3475
                                $real_answers[$answerId] = true;
3476
                            } else {
3477
                                $real_answers[$answerId] = false;
3478
                            }
3479
                        } else {
3480
                            if ($studentChoice) {
3481
                                $real_answers[$answerId] = false;
3482
                            } else {
3483
                                $real_answers[$answerId] = true;
3484
                            }
3485
                        }
3486
                    }
3487
                    break;
3488
                case FILL_IN_BLANKS:
3489
                    $str = '';
3490
                    if ($from_database) {
3491
                        $sql = "SELECT answer
3492
                                    FROM $TBL_TRACK_ATTEMPT
3493
                                    WHERE
3494
                                        exe_id = $exeId AND
3495
                                        question_id= ".intval($questionId);
3496
                        $result = Database::query($sql);
3497
                        $str = Database::result($result, 0, 'answer');
3498
                    }
3499
3500
                    if ($saved_results == false && strpos($str, 'font color') !== false) {
3501
                        // the question is encoded like this
3502
                        // [A] B [C] D [E] F::10,10,10@1
3503
                        // number 1 before the "@" means that is a switchable fill in blank question
3504
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3505
                        // means that is a normal fill blank question
3506
                        // first we explode the "::"
3507
                        $pre_array = explode('::', $answer);
3508
3509
                        // is switchable fill blank or not
3510
                        $last = count($pre_array) - 1;
3511
                        $is_set_switchable = explode('@', $pre_array[$last]);
3512
                        $switchable_answer_set = false;
3513
                        if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3514
                            $switchable_answer_set = true;
3515
                        }
3516
                        $answer = '';
3517
                        for ($k = 0; $k < $last; $k++) {
3518
                            $answer .= $pre_array[$k];
3519
                        }
3520
                        // splits weightings that are joined with a comma
3521
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3522
                        // we save the answer because it will be modified
3523
                        $temp = $answer;
3524
                        $answer = '';
3525
                        $j = 0;
3526
                        //initialise answer tags
3527
                        $user_tags = $correct_tags = $real_text = array();
3528
                        // the loop will stop at the end of the text
3529
                        while (1) {
3530
                            // quits the loop if there are no more blanks (detect '[')
3531
                            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...
3532
                                // adds the end of the text
3533
                                $answer = $temp;
3534
                                $real_text[] = $answer;
3535
                                break; //no more "blanks", quit the loop
3536
                            }
3537
                            // adds the piece of text that is before the blank
3538
                            //and ends with '[' into a general storage array
3539
                            $real_text[] = api_substr($temp, 0, $pos +1);
3540
                            $answer .= api_substr($temp, 0, $pos +1);
3541
                            //take the string remaining (after the last "[" we found)
3542
                            $temp = api_substr($temp, $pos +1);
3543
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3544
                            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 3542 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...
3545
                                // adds the end of the text
3546
                                $answer .= $temp;
3547
                                break;
3548
                            }
3549
                            if ($from_database) {
3550
                                $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3551
                                          WHERE
3552
                                            exe_id = '".$exeId."' AND
3553
                                            question_id= ".intval($questionId)."";
3554
                                $resfill = Database::query($queryfill);
3555
                                $str = Database::result($resfill, 0, 'answer');
3556
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3557
                                $str = str_replace('\r\n', '', $str);
3558
3559
                                $choice = $arr[1];
3560
                                if (isset($choice[$j])) {
3561
                                    $tmp = api_strrpos($choice[$j], ' / ');
3562
                                    $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 3561 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...
3563
                                    $choice[$j] = trim($choice[$j]);
3564
                                    // Needed to let characters ' and " to work as part of an answer
3565
                                    $choice[$j] = stripslashes($choice[$j]);
3566
                                } else {
3567
                                    $choice[$j] = null;
3568
                                }
3569
                            } else {
3570
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3571
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3572
                            }
3573
3574
                            $user_tags[] = $choice[$j];
3575
                            //put the contents of the [] answer tag into correct_tags[]
3576
                            $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 3542 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...
3577
                            $j++;
3578
                            $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 3578 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...
3579
                        }
3580
                        $answer = '';
3581
                        $real_correct_tags = $correct_tags;
3582
                        $chosen_list = array();
3583
3584
                        for ($i = 0; $i < count($real_correct_tags); $i++) {
3585
                            if ($i == 0) {
3586
                                $answer .= $real_text[0];
3587
                            }
3588
                            if (!$switchable_answer_set) {
3589
                                // Needed to parse ' and " characters
3590
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3591 View Code Duplication
                                if ($correct_tags[$i] == $user_tags[$i]) {
3592
                                    // gives the related weighting to the student
3593
                                    $questionScore += $answerWeighting[$i];
3594
                                    // increments total score
3595
                                    $totalScore += $answerWeighting[$i];
3596
                                    // adds the word in green at the end of the string
3597
                                    $answer .= $correct_tags[$i];
3598
                                } elseif (!empty($user_tags[$i])) {
3599
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3600
                                    // adds the word in red at the end of the string, and strikes it
3601
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3602
                                } else {
3603
                                    // adds a tabulation if no word has been typed by the student
3604
                                    $answer .= ''; // remove &nbsp; that causes issue
3605
                                }
3606
                            } else {
3607
                                // switchable fill in the blanks
3608
                                if (in_array($user_tags[$i], $correct_tags)) {
3609
                                    $chosen_list[] = $user_tags[$i];
3610
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3611
                                    // gives the related weighting to the student
3612
                                    $questionScore += $answerWeighting[$i];
3613
                                    // increments total score
3614
                                    $totalScore += $answerWeighting[$i];
3615
                                    // adds the word in green at the end of the string
3616
                                    $answer .= $user_tags[$i];
3617
                                } elseif (!empty ($user_tags[$i])) {
3618
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3619
                                    // adds the word in red at the end of the string, and strikes it
3620
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3621
                                } else {
3622
                                    // adds a tabulation if no word has been typed by the student
3623
                                    $answer .= '';  // remove &nbsp; that causes issue
3624
                                }
3625
                            }
3626
3627
                            // adds the correct word, followed by ] to close the blank
3628
                            $answer .= ' / <font color="green"><b>' . $real_correct_tags[$i] . '</b></font>]';
3629
                            if (isset($real_text[$i +1])) {
3630
                                $answer .= $real_text[$i +1];
3631
                            }
3632
                        }
3633
                    } else {
3634
                        // insert the student result in the track_e_attempt table, field answer
3635
                        // $answer is the answer like in the c_quiz_answer table for the question
3636
                        // student data are choice[]
3637
                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
3638
                            $answer
3639
                        );
3640
3641
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
3642
                        $answerWeighting = $listCorrectAnswers['tabweighting'];
3643
                        // user choices is an array $choice
3644
3645
                        // get existing user data in n the BDD
3646
                        if ($from_database) {
3647
                            $sql = "SELECT answer
3648
                                    FROM $TBL_TRACK_ATTEMPT
3649
                                    WHERE
3650
                                        exe_id = $exeId AND
3651
                                        question_id= ".intval($questionId);
3652
                            $result = Database::query($sql);
3653
                            $str = Database::result($result, 0, 'answer');
3654
                            $listStudentResults = FillBlanks::getAnswerInfo(
3655
                                $str,
3656
                                true
3657
                            );
3658
                            $choice = $listStudentResults['studentanswer'];
3659
                        }
3660
3661
                        // loop other all blanks words
3662
                        if (!$switchableAnswerSet) {
3663
                            // not switchable answer, must be in the same place than teacher order
3664
                            for ($i = 0; $i < count($listCorrectAnswers['tabwords']); $i++) {
3665
                                $studentAnswer = isset($choice[$i]) ? trim($choice[$i]) : '';
3666
                                $correctAnswer = $listCorrectAnswers['tabwords'][$i];
3667
3668
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3669
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3670
                                if (!$from_database) {
3671
                                    $studentAnswer = htmlentities(
3672
                                        api_utf8_encode($studentAnswer)
3673
                                    );
3674
                                }
3675
3676
                                $isAnswerCorrect = 0;
3677 View Code Duplication
                                if (FillBlanks::isGoodStudentAnswer($studentAnswer, $correctAnswer)) {
3678
                                    // gives the related weighting to the student
3679
                                    $questionScore += $answerWeighting[$i];
3680
                                    // increments total score
3681
                                    $totalScore += $answerWeighting[$i];
3682
                                    $isAnswerCorrect = 1;
3683
                                }
3684
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3685
                                $listCorrectAnswers['studentscore'][$i] = $isAnswerCorrect;
3686
                            }
3687
                        } else {
3688
                            // switchable answer
3689
                            $listStudentAnswerTemp = $choice;
3690
                            $listTeacherAnswerTemp = $listCorrectAnswers['tabwords'];
3691
                            // for every teacher answer, check if there is a student answer
3692
                            for ($i = 0; $i < count($listStudentAnswerTemp); $i++) {
3693
                                $studentAnswer = trim(
3694
                                    $listStudentAnswerTemp[$i]
3695
                                );
3696
                                $found = false;
3697
                                for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
3698
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3699
                                    if (!$found) {
3700 View Code Duplication
                                        if (FillBlanks::isGoodStudentAnswer(
3701
                                            $studentAnswer,
3702
                                            $correctAnswer
3703
                                        )
3704
                                        ) {
3705
                                            $questionScore += $answerWeighting[$i];
3706
                                            $totalScore += $answerWeighting[$i];
3707
                                            $listTeacherAnswerTemp[$j] = "";
3708
                                            $found = true;
3709
                                        }
3710
                                    }
3711
                                }
3712
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3713
                                if (!$found) {
3714
                                    $listCorrectAnswers['studentscore'][$i] = 0;
3715
                                } else {
3716
                                    $listCorrectAnswers['studentscore'][$i] = 1;
3717
                                }
3718
                            }
3719
                        }
3720
                        $answer = FillBlanks::getAnswerInStudentAttempt(
3721
                            $listCorrectAnswers
3722
                        );
3723
                    }
3724
                    break;
3725
                case CALCULATED_ANSWER:
3726
                    $answer = $objAnswerTmp->selectAnswer($_SESSION['calculatedAnswerId'][$questionId]);
3727
                    $preArray = explode('@@', $answer);
3728
                    $last = count($preArray) - 1;
3729
                    $answer = '';
3730
                    for ($k = 0; $k < $last; $k++) {
3731
                        $answer .= $preArray[$k];
3732
                    }
3733
                    $answerWeighting = array($answerWeighting);
3734
                    // we save the answer because it will be modified
3735
                    $temp = $answer;
3736
                    $answer = '';
3737
                    $j = 0;
3738
                    //initialise answer tags
3739
                    $userTags = $correctTags = $realText = array();
3740
                    // the loop will stop at the end of the text
3741
                    while (1) {
3742
                        // quits the loop if there are no more blanks (detect '[')
3743
                        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...
3744
                            // adds the end of the text
3745
                            $answer = $temp;
3746
                            $realText[] = $answer;
3747
                            break; //no more "blanks", quit the loop
3748
                        }
3749
                        // adds the piece of text that is before the blank
3750
                        //and ends with '[' into a general storage array
3751
                        $realText[] = api_substr($temp, 0, $pos +1);
3752
                        $answer .= api_substr($temp, 0, $pos +1);
3753
                        //take the string remaining (after the last "[" we found)
3754
                        $temp = api_substr($temp, $pos +1);
3755
                        // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3756
                        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 3754 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...
3757
                            // adds the end of the text
3758
                            $answer .= $temp;
3759
                            break;
3760
                        }
3761
                        if ($from_database) {
3762
                            $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3763
                                          WHERE
3764
                                            exe_id = '".$exeId."' AND
3765
                                            question_id= ".intval($questionId);
3766
                            $resfill = Database::query($queryfill);
3767
                            $str = Database::result($resfill, 0, 'answer');
3768
                            api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3769
                            $str = str_replace('\r\n', '', $str);
3770
                            $choice = $arr[1];
3771
                            if (isset($choice[$j])) {
3772
                                $tmp = api_strrpos($choice[$j], ' / ');
3773
3774
                                if ($tmp) {
3775
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3776
                                } else {
3777
                                    $tmp = ltrim($tmp, '[');
3778
                                    $tmp = rtrim($tmp, ']');
3779
                                }
3780
3781
                                $choice[$j] = trim($choice[$j]);
3782
                                // Needed to let characters ' and " to work as part of an answer
3783
                                $choice[$j] = stripslashes($choice[$j]);
3784
                            } else {
3785
                                $choice[$j] = null;
3786
                            }
3787
                        } else {
3788
                            // This value is the user input, not escaped while correct answer is escaped by fckeditor
3789
                            $choice[$j] = api_htmlentities(trim($choice[$j]));
3790
                        }
3791
                        $userTags[] = $choice[$j];
3792
                        //put the contents of the [] answer tag into correct_tags[]
3793
                        $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 3754 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...
3794
                        $j++;
3795
                        $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 3795 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...
3796
                    }
3797
                    $answer = '';
3798
                    $realCorrectTags = $correctTags;
3799
                    for ($i = 0; $i < count($realCorrectTags); $i++) {
3800
                        if ($i == 0) {
3801
                            $answer .= $realText[0];
3802
                        }
3803
                        // Needed to parse ' and " characters
3804
                        $userTags[$i] = stripslashes($userTags[$i]);
3805 View Code Duplication
                        if ($correctTags[$i] == $userTags[$i]) {
3806
                            // gives the related weighting to the student
3807
                            $questionScore += $answerWeighting[$i];
3808
                            // increments total score
3809
                            $totalScore += $answerWeighting[$i];
3810
                            // adds the word in green at the end of the string
3811
                            $answer .= $correctTags[$i];
3812
                        } elseif (!empty($userTags[$i])) {
3813
                            // else if the word entered by the student IS NOT the same as the one defined by the professor
3814
                            // adds the word in red at the end of the string, and strikes it
3815
                            $answer .= '<font color="red"><s>' . $userTags[$i] . '</s></font>';
3816
                        } else {
3817
                            // adds a tabulation if no word has been typed by the student
3818
                            $answer .= ''; // remove &nbsp; that causes issue
3819
                        }
3820
                        // adds the correct word, followed by ] to close the blank
3821
3822
                        if (
3823
                            $this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM
3824
                        ) {
3825
                            $answer .= ' / <font color="green"><b>' . $realCorrectTags[$i] . '</b></font>';
3826
                        }
3827
3828
                        $answer .= ']';
3829
3830
                        if (isset($realText[$i +1])) {
3831
                            $answer .= $realText[$i +1];
3832
                        }
3833
                    }
3834
                    break;
3835
                case FREE_ANSWER:
3836
                    if ($from_database) {
3837
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3838
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3839
                        $resq = Database::query($query);
3840
                        $data = Database::fetch_array($resq);
3841
3842
                        $choice = $data['answer'];
3843
                        $choice = str_replace('\r\n', '', $choice);
3844
                        $choice = stripslashes($choice);
3845
                        $questionScore = $data['marks'];
3846
3847
                        if ($questionScore == -1) {
3848
                            $totalScore+= 0;
3849
                        } else {
3850
                            $totalScore+= $questionScore;
3851
                        }
3852
                        if ($questionScore == '') {
3853
                            $questionScore = 0;
3854
                        }
3855
                        $arrques = $questionName;
3856
                        $arrans  = $choice;
3857
                    } else {
3858
                        $studentChoice = $choice;
3859
                        if ($studentChoice) {
3860
                            //Fixing negative puntation see #2193
3861
                            $questionScore = 0;
3862
                            $totalScore += 0;
3863
                        }
3864
                    }
3865
                    break;
3866
                case ORAL_EXPRESSION:
3867
                    if ($from_database) {
3868
                        $query = "SELECT answer, marks 
3869
                                  FROM $TBL_TRACK_ATTEMPT
3870
                                  WHERE 
3871
                                        exe_id = $exeId AND 
3872
                                        question_id = $questionId
3873
                                 ";
3874
                        $resq = Database::query($query);
3875
                        $row = Database::fetch_assoc($resq);
3876
                        $choice = $row['answer'];
3877
                        $choice = str_replace('\r\n', '', $choice);
3878
                        $choice = stripslashes($choice);
3879
                        $questionScore = $row['marks'];
3880
                        if ($questionScore == -1) {
3881
                            $totalScore += 0;
3882
                        } else {
3883
                            $totalScore += $questionScore;
3884
                        }
3885
                        $arrques = $questionName;
3886
                        $arrans  = $choice;
3887
                    } else {
3888
                        $studentChoice = $choice;
3889
                        if ($studentChoice) {
3890
                            //Fixing negative puntation see #2193
3891
                            $questionScore = 0;
3892
                            $totalScore += 0;
3893
                        }
3894
                    }
3895
                    break;
3896
                case DRAGGABLE:
3897
                    //no break
3898
                case MATCHING_DRAGGABLE:
3899
                    //no break
3900
                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...
3901
                    if ($from_database) {
3902
                        $sql = "SELECT id, answer, id_auto
3903
                                FROM $table_ans
3904
                                WHERE
3905
                                    c_id = $course_id AND
3906
                                    question_id = $questionId AND
3907
                                    correct = 0
3908
                                ";
3909
                        $res_answer = Database::query($sql);
3910
                        // Getting the real answer
3911
                        $real_list = array();
3912
                        while ($real_answer = Database::fetch_array($res_answer)) {
3913
                            $real_list[$real_answer['id_auto']] = $real_answer['answer'];
3914
                        }
3915
3916
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
3917
                                FROM $table_ans
3918
                                WHERE
3919
                                    c_id = $course_id AND
3920
                                    question_id = $questionId AND
3921
                                    correct <> 0
3922
                                ORDER BY id_auto";
3923
                        $res_answers = Database::query($sql);
3924
3925
                        $questionScore = 0;
3926
3927
                        while ($a_answers = Database::fetch_array($res_answers)) {
3928
                            $i_answer_id = $a_answers['id']; //3
3929
                            $s_answer_label = $a_answers['answer'];  // your daddy - your mother
3930
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
3931
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
3932
3933
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3934
                                    WHERE
3935
                                        exe_id = '$exeId' AND
3936
                                        question_id = '$questionId' AND
3937
                                        position = '$i_answer_id_auto'";
3938
3939
                            $res_user_answer = Database::query($sql);
3940
3941
                            if (Database::num_rows($res_user_answer) > 0) {
3942
                                //  rich - good looking
3943
                                $s_user_answer = Database::result($res_user_answer, 0, 0);
3944
                            } else {
3945
                                $s_user_answer = 0;
3946
                            }
3947
3948
                            $i_answerWeighting = $a_answers['ponderation'];
3949
3950
                            $user_answer = '';
3951
                            if (!empty($s_user_answer)) {
3952
                                if ($answerType == DRAGGABLE) {
3953
                                    if ($s_user_answer == $i_answer_correct_answer) {
3954
                                        $questionScore += $i_answerWeighting;
3955
                                        $totalScore += $i_answerWeighting;
3956
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
3957
                                    } else {
3958
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3959
                                    }
3960
                                } else {
3961
                                    if ($s_user_answer == $i_answer_correct_answer) {
3962
                                        $questionScore += $i_answerWeighting;
3963
                                        $totalScore += $i_answerWeighting;
3964
3965
                                        // Try with id
3966
                                        if (isset($real_list[$i_answer_id])) {
3967
                                            $user_answer = Display::span($real_list[$i_answer_id]);
3968
                                        }
3969
3970
                                        // Try with $i_answer_id_auto
3971
                                        if (empty($user_answer)) {
3972
                                            if (isset($real_list[$i_answer_id_auto])) {
3973
                                                $user_answer = Display::span(
3974
                                                    $real_list[$i_answer_id_auto]
3975
                                                );
3976
                                            }
3977
                                        }
3978
                                    } else {
3979
                                        $user_answer = Display::span(
3980
                                            $real_list[$s_user_answer],
3981
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
3982
                                        );
3983
                                    }
3984
                                }
3985
                            } elseif ($answerType == DRAGGABLE) {
3986
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3987
                            } else {
3988
                                $user_answer = Display::span(
3989
                                    get_lang('Incorrect').' &nbsp;',
3990
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
3991
                                );
3992
                            }
3993
3994
                            if ($show_result) {
3995
                                if ($showTotalScoreAndUserChoicesInLastAttempt === false) {
3996
                                    $user_answer = '';
3997
                                }
3998
                                echo '<tr>';
3999
                                echo '<td>' . $s_answer_label . '</td>';
4000
                                echo '<td>' . $user_answer;
4001
4002
                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4003
                                    if (isset($real_list[$i_answer_correct_answer]) &&
4004
                                        $showTotalScoreAndUserChoicesInLastAttempt === true
4005
                                    ) {
4006
                                        echo Display::span(
4007
                                            $real_list[$i_answer_correct_answer],
4008
                                            ['style' => 'color: #008000; font-weight: bold;']
4009
                                        );
4010
                                    }
4011
                                }
4012
                                echo '</td>';
4013
                                echo '</tr>';
4014
                            }
4015
                        }
4016
                        break(2); // break the switch and the "for" condition
4017
                    } else {
4018
                        if ($answerCorrect) {
4019
                            if (isset($choice[$answerAutoId]) &&
4020
                                $answerCorrect == $choice[$answerAutoId]
4021
                            ) {
4022
                                $questionScore += $answerWeighting;
4023
                                $totalScore += $answerWeighting;
4024
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4025
                            } else {
4026
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4027
                                    $user_answer = Display::span(
4028
                                        $answerMatching[$choice[$answerAutoId]],
4029
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4030
                                    );
4031
                                }
4032
                            }
4033
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4034
                        }
4035
                        break;
4036
                    }
4037
                case HOT_SPOT:
4038
                    if ($from_database) {
4039
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4040
                        // Check auto id
4041
                        $sql = "SELECT hotspot_correct
4042
                                FROM $TBL_TRACK_HOTSPOT
4043
                                WHERE
4044
                                    hotspot_exe_id = $exeId AND
4045
                                    hotspot_question_id= $questionId AND
4046
                                    hotspot_answer_id = ".intval($answerAutoId);
4047
                        $result = Database::query($sql);
4048
                        if (Database::num_rows($result)) {
4049
                            $studentChoice = Database::result(
4050
                                $result,
4051
                                0,
4052
                                'hotspot_correct'
4053
                            );
4054
4055
                            if ($studentChoice) {
4056
                                $questionScore += $answerWeighting;
4057
                                $totalScore += $answerWeighting;
4058
                            }
4059
                        } else {
4060
                            // If answer.id is different:
4061
                            $sql = "SELECT hotspot_correct
4062
                                FROM $TBL_TRACK_HOTSPOT
4063
                                WHERE
4064
                                    hotspot_exe_id = $exeId AND
4065
                                    hotspot_question_id= $questionId AND
4066
                                    hotspot_answer_id = ".intval($answerId);
4067
                            $result = Database::query($sql);
4068
4069
                            if (Database::num_rows($result)) {
4070
                                $studentChoice = Database::result(
4071
                                    $result,
4072
                                    0,
4073
                                    'hotspot_correct'
4074
                                );
4075
4076
                                if ($studentChoice) {
4077
                                    $questionScore += $answerWeighting;
4078
                                    $totalScore += $answerWeighting;
4079
                                }
4080
                            } else {
4081
                                // check answer.iid
4082
                                if (!empty($answerIid)) {
4083
                                    $sql = "SELECT hotspot_correct
4084
                                            FROM $TBL_TRACK_HOTSPOT
4085
                                            WHERE
4086
                                                hotspot_exe_id = $exeId AND
4087
                                                hotspot_question_id= $questionId AND
4088
                                                hotspot_answer_id = ".intval($answerIid);
4089
                                    $result = Database::query($sql);
4090
4091
                                    $studentChoice = Database::result(
4092
                                        $result,
4093
                                        0,
4094
                                        'hotspot_correct'
4095
                                    );
4096
4097
                                    if ($studentChoice) {
4098
                                        $questionScore += $answerWeighting;
4099
                                        $totalScore += $answerWeighting;
4100
                                    }
4101
                                }
4102
                            }
4103
                        }
4104
                    } else {
4105
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4106
                            $choice[$answerAutoId] = 0;
4107
                            $choice[$answerIid] = 0;
4108
                        } else {
4109
                            $studentChoice = $choice[$answerAutoId];
4110
                            if (empty($studentChoice)) {
4111
                                $studentChoice = $choice[$answerIid];
4112
                            }
4113
                            $choiceIsValid = false;
4114
                            if (!empty($studentChoice)) {
4115
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4116
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4117
                                $choicePoint = Geometry::decodePoint($studentChoice);
4118
4119
                                switch ($hotspotType) {
4120
                                    case 'square':
4121
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4122
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4123
                                        break;
4124
                                    case 'circle':
4125
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4126
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4127
                                        break;
4128
                                    case 'poly':
4129
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4130
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4131
                                        break;
4132
                                }
4133
                            }
4134
4135
                            $choice[$answerAutoId] = 0;
4136
                            if ($choiceIsValid) {
4137
                                $questionScore += $answerWeighting;
4138
                                $totalScore += $answerWeighting;
4139
                                $choice[$answerAutoId] = 1;
4140
                                $choice[$answerIid] = 1;
4141
                            }
4142
                        }
4143
                    }
4144
                    break;
4145
                // @todo never added to chamilo
4146
                //for hotspot with fixed order
4147
                case HOT_SPOT_ORDER:
4148
                    $studentChoice = $choice['order'][$answerId];
4149
                    if ($studentChoice == $answerId) {
4150
                        $questionScore  += $answerWeighting;
4151
                        $totalScore     += $answerWeighting;
4152
                        $studentChoice = true;
4153
                    } else {
4154
                        $studentChoice = false;
4155
                    }
4156
                    break;
4157
                // for hotspot with delineation
4158
                case HOT_SPOT_DELINEATION:
4159
                    if ($from_database) {
4160
                        // getting the user answer
4161
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4162
                        $query   = "SELECT hotspot_correct, hotspot_coordinate
4163
                                    FROM $TBL_TRACK_HOTSPOT
4164
                                    WHERE
4165
                                        hotspot_exe_id = '".$exeId."' AND
4166
                                        hotspot_question_id= '".$questionId."' AND
4167
                                        hotspot_answer_id='1'";
4168
                        //by default we take 1 because it's a delineation
4169
                        $resq = Database::query($query);
4170
                        $row = Database::fetch_array($resq,'ASSOC');
4171
4172
                        $choice = $row['hotspot_correct'];
4173
                        $user_answer = $row['hotspot_coordinate'];
4174
4175
                        // THIS is very important otherwise the poly_compile will throw an error!!
4176
                        // round-up the coordinates
4177
                        $coords = explode('/',$user_answer);
4178
                        $user_array = '';
4179 View Code Duplication
                        foreach ($coords as $coord) {
4180
                            list($x,$y) = explode(';',$coord);
4181
                            $user_array .= round($x).';'.round($y).'/';
4182
                        }
4183
                        $user_array = substr($user_array,0,-1);
4184
                    } else {
4185
                        if (!empty($studentChoice)) {
4186
                            $newquestionList[] = $questionId;
4187
                        }
4188
4189
                        if ($answerId === 1) {
4190
                            $studentChoice = $choice[$answerId];
4191
                            $questionScore += $answerWeighting;
4192
4193
                            if ($hotspot_delineation_result[1]==1) {
4194
                                $totalScore += $answerWeighting; //adding the total
4195
                            }
4196
                        }
4197
                    }
4198
                    $_SESSION['hotspot_coord'][1]	= $delineation_cord;
4199
                    $_SESSION['hotspot_dest'][1]	= $answer_delineation_destination;
4200
                    break;
4201
            } // end switch Answertype
4202
4203
            if ($show_result) {
4204
                if ($debug) error_log('Showing questions $from '.$from);
4205
                if ($from == 'exercise_result') {
4206
                    //display answers (if not matching type, or if the answer is correct)
4207
                    if (
4208
                        !in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4209
                        $answerCorrect
4210
                    ) {
4211
                        if (
4212
                            in_array(
4213
                                $answerType,
4214
                                array(
4215
                                    UNIQUE_ANSWER,
4216
                                    UNIQUE_ANSWER_IMAGE,
4217
                                    UNIQUE_ANSWER_NO_OPTION,
4218
                                    MULTIPLE_ANSWER,
4219
                                    MULTIPLE_ANSWER_COMBINATION,
4220
                                    GLOBAL_MULTIPLE_ANSWER
4221
                                )
4222
                            )
4223
                        ) {
4224
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4225
                                $feedback_type,
4226
                                $answerType,
4227
                                $studentChoice,
4228
                                $answer,
4229
                                $answerComment,
4230
                                $answerCorrect,
4231
                                0,
4232
                                0,
4233
                                0,
4234
                                $results_disabled,
4235
                                $showTotalScoreAndUserChoicesInLastAttempt
4236
                            );
4237
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4238
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4239
                                $feedback_type,
4240
                                $answerType,
4241
                                $studentChoice,
4242
                                $answer,
4243
                                $answerComment,
4244
                                $answerCorrect,
4245
                                0,
4246
                                $questionId,
4247
                                0,
4248
                                $results_disabled,
4249
                                $showTotalScoreAndUserChoicesInLastAttempt
4250
                            );
4251
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ) {
4252
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4253
                                $feedback_type,
4254
                                $answerType,
4255
                                $studentChoice,
4256
                                $answer,
4257
                                $answerComment,
4258
                                $answerCorrect,
4259
                                0,
4260
                                0,
4261
                                0,
4262
                                $results_disabled,
4263
                                $showTotalScoreAndUserChoicesInLastAttempt
4264
                            );
4265
                        } elseif ($answerType == FILL_IN_BLANKS) {
4266
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4267
                                $feedback_type,
4268
                                $answer,
4269
                                0,
4270
                                0,
4271
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3134 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...
4272
                                '',
4273
                                $showTotalScoreAndUserChoicesInLastAttempt
4274
                            );
4275
                        } elseif ($answerType == CALCULATED_ANSWER) {
4276
                            ExerciseShowFunctions::display_calculated_answer(
4277
                                $feedback_type,
4278
                                $answer,
4279
                                0,
4280
                                0,
4281
                                $results_disabled,
4282
                                $showTotalScoreAndUserChoicesInLastAttempt
4283
                            );
4284
                        } elseif ($answerType == FREE_ANSWER) {
4285
                            ExerciseShowFunctions::display_free_answer(
4286
                                $feedback_type,
4287
                                $choice,
4288
                                $exeId,
4289
                                $questionId,
4290
                                $questionScore,
4291
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3134 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...
4292
                            );
4293
                        } elseif ($answerType == ORAL_EXPRESSION) {
4294
                            // to store the details of open questions in an array to be used in mail
4295
                            /** @var OralExpression $objQuestionTmp */
4296
                            ExerciseShowFunctions::display_oral_expression_answer(
4297
                                $feedback_type,
4298
                                $choice,
4299
                                0,
4300
                                0,
4301
                                $objQuestionTmp->getFileUrl(true),
4302
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3134 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...
4303
                            );
4304
                        } elseif ($answerType == HOT_SPOT) {
4305
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4306
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4307
                                    break;
4308
                                }
4309
                            }
4310
4311
                            ExerciseShowFunctions::display_hotspot_answer(
4312
                                $feedback_type,
4313
                                $answerId,
4314
                                $answer,
4315
                                $studentChoice,
4316
                                $answerComment,
4317
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3134 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...
4318
                                $answerId,
4319
                                $showTotalScoreAndUserChoicesInLastAttempt
4320
                            );
4321
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4322
                            ExerciseShowFunctions::display_hotspot_order_answer(
4323
                                $feedback_type,
4324
                                $answerId,
4325
                                $answer,
4326
                                $studentChoice,
4327
                                $answerComment
4328
                            );
4329
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4330
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4331
4332
                            //round-up the coordinates
4333
                            $coords = explode('/',$user_answer);
4334
                            $user_array = '';
4335 View Code Duplication
                            foreach ($coords as $coord) {
4336
                                list($x,$y) = explode(';',$coord);
4337
                                $user_array .= round($x).';'.round($y).'/';
4338
                            }
4339
                            $user_array = substr($user_array,0,-1);
4340
4341 View Code Duplication
                            if ($next) {
4342
4343
                                $user_answer = $user_array;
4344
4345
                                // we compare only the delineation not the other points
4346
                                $answer_question = $_SESSION['hotspot_coord'][1];
4347
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4348
4349
                                //calculating the area
4350
                                $poly_user = convert_coordinates($user_answer, '/');
4351
                                $poly_answer = convert_coordinates($answer_question, '|');
4352
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4353
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4354
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4355
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4356
4357
                                $overlap = $poly_results['both'];
4358
                                $poly_answer_area = $poly_results['s1'];
4359
                                $poly_user_area = $poly_results['s2'];
4360
                                $missing = $poly_results['s1Only'];
4361
                                $excess = $poly_results['s2Only'];
4362
4363
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4364
                                // //this is an area in pixels
4365
                                if ($debug > 0) {
4366
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4367
                                }
4368
4369
                                if ($overlap < 1) {
4370
                                    //shortcut to avoid complicated calculations
4371
                                    $final_overlap = 0;
4372
                                    $final_missing = 100;
4373
                                    $final_excess = 100;
4374
                                } else {
4375
                                    // the final overlap is the percentage of the initial polygon
4376
                                    // that is overlapped by the user's polygon
4377
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4378
                                    if ($debug > 1) {
4379
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4380
                                    }
4381
                                    // the final missing area is the percentage of the initial polygon
4382
                                    // that is not overlapped by the user's polygon
4383
                                    $final_missing = 100 - $final_overlap;
4384
                                    if ($debug > 1) {
4385
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4386
                                    }
4387
                                    // the final excess area is the percentage of the initial polygon's size
4388
                                    // that is covered by the user's polygon outside of the initial polygon
4389
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4390
                                    if ($debug > 1) {
4391
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4392
                                    }
4393
                                }
4394
4395
                                //checking the destination parameters parsing the "@@"
4396
                                $destination_items= explode('@@', $answerDestination);
4397
                                $threadhold_total = $destination_items[0];
4398
                                $threadhold_items=explode(';',$threadhold_total);
4399
                                $threadhold1 = $threadhold_items[0]; // overlap
4400
                                $threadhold2 = $threadhold_items[1]; // excess
4401
                                $threadhold3 = $threadhold_items[2];	 //missing
4402
4403
                                // if is delineation
4404
                                if ($answerId===1) {
4405
                                    //setting colors
4406
                                    if ($final_overlap>=$threadhold1) {
4407
                                        $overlap_color=true; //echo 'a';
4408
                                    }
4409
                                    //echo $excess.'-'.$threadhold2;
4410
                                    if ($final_excess<=$threadhold2) {
4411
                                        $excess_color=true; //echo 'b';
4412
                                    }
4413
                                    //echo '--------'.$missing.'-'.$threadhold3;
4414
                                    if ($final_missing<=$threadhold3) {
4415
                                        $missing_color=true; //echo 'c';
4416
                                    }
4417
4418
                                    // if pass
4419
                                    if (
4420
                                        $final_overlap >= $threadhold1 &&
4421
                                        $final_missing <= $threadhold3 &&
4422
                                        $final_excess <= $threadhold2
4423
                                    ) {
4424
                                        $next=1; //go to the oars
4425
                                        $result_comment=get_lang('Acceptable');
4426
                                        $final_answer = 1;	// do not update with  update_exercise_attempt
4427
                                    } else {
4428
                                        $next=0;
4429
                                        $result_comment=get_lang('Unacceptable');
4430
                                        $comment=$answerDestination=$objAnswerTmp->selectComment(1);
4431
                                        $answerDestination=$objAnswerTmp->selectDestination(1);
4432
                                        //checking the destination parameters parsing the "@@"
4433
                                        $destination_items= explode('@@', $answerDestination);
4434
                                    }
4435
                                } elseif($answerId>1) {
4436
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4437
                                        if ($debug>0) {
4438
                                            error_log(__LINE__.' - answerId is of type noerror',0);
4439
                                        }
4440
                                        //type no error shouldn't be treated
4441
                                        $next = 1;
4442
                                        continue;
4443
                                    }
4444
                                    if ($debug>0) {
4445
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR',0);
4446
                                    }
4447
                                    //check the intersection between the oar and the user
4448
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4449
                                    //echo 'official';print_r($x_list);print_r($y_list);
4450
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4451
                                    $inter= $result['success'];
4452
4453
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4454
                                    $delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4455
4456
                                    $poly_answer = convert_coordinates($delineation_cord,'|');
4457
                                    $max_coord = poly_get_max($poly_user,$poly_answer);
4458
                                    $poly_answer_compiled = poly_compile($poly_answer,$max_coord);
4459
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4460
4461
                                    if ($overlap == false) {
4462
                                        //all good, no overlap
4463
                                        $next = 1;
4464
                                        continue;
4465
                                    } else {
4466
                                        if ($debug>0) {
4467
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit',0);
4468
                                        }
4469
                                        $organs_at_risk_hit++;
4470
                                        //show the feedback
4471
                                        $next=0;
4472
                                        $comment=$answerDestination=$objAnswerTmp->selectComment($answerId);
4473
                                        $answerDestination=$objAnswerTmp->selectDestination($answerId);
4474
4475
                                        $destination_items= explode('@@', $answerDestination);
4476
                                        $try_hotspot=$destination_items[1];
4477
                                        $lp_hotspot=$destination_items[2];
4478
                                        $select_question_hotspot=$destination_items[3];
4479
                                        $url_hotspot=$destination_items[4];
4480
                                    }
4481
                                }
4482
                            } else {	// the first delineation feedback
4483
                                if ($debug>0) {
4484
                                    error_log(__LINE__.' first',0);
4485
                                }
4486
                            }
4487
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4488
                            echo '<tr>';
4489
                            echo Display::tag('td', $answerMatching[$answerId]);
4490
                            echo Display::tag(
4491
                                'td',
4492
                                "$user_answer / " . Display::tag(
4493
                                    'strong',
4494
                                    $answerMatching[$answerCorrect],
4495
                                    ['style' => 'color: #008000; font-weight: bold;']
4496
                                )
4497
                            );
4498
                            echo '</tr>';
4499
                        }
4500
                    }
4501
                } else {
4502
                    if ($debug) error_log('Showing questions $from '.$from);
4503
4504
                    switch ($answerType) {
4505
                        case UNIQUE_ANSWER:
4506
                        case UNIQUE_ANSWER_IMAGE:
4507
                        case UNIQUE_ANSWER_NO_OPTION:
4508
                        case MULTIPLE_ANSWER:
4509
                        case GLOBAL_MULTIPLE_ANSWER :
4510 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION:
4511
                            if ($answerId == 1) {
4512
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4513
                                    $feedback_type,
4514
                                    $answerType,
4515
                                    $studentChoice,
4516
                                    $answer,
4517
                                    $answerComment,
4518
                                    $answerCorrect,
4519
                                    $exeId,
4520
                                    $questionId,
4521
                                    $answerId,
4522
                                    $results_disabled,
4523
                                    $showTotalScoreAndUserChoicesInLastAttempt
4524
                                );
4525
                            } else {
4526
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4527
                                    $feedback_type,
4528
                                    $answerType,
4529
                                    $studentChoice,
4530
                                    $answer,
4531
                                    $answerComment,
4532
                                    $answerCorrect,
4533
                                    $exeId,
4534
                                    $questionId,
4535
                                    '',
4536
                                    $results_disabled,
4537
                                    $showTotalScoreAndUserChoicesInLastAttempt
4538
                                );
4539
                            }
4540
                            break;
4541 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4542
                            if ($answerId == 1) {
4543
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4544
                                    $feedback_type,
4545
                                    $answerType,
4546
                                    $studentChoice,
4547
                                    $answer,
4548
                                    $answerComment,
4549
                                    $answerCorrect,
4550
                                    $exeId,
4551
                                    $questionId,
4552
                                    $answerId,
4553
                                    $results_disabled,
4554
                                    $showTotalScoreAndUserChoicesInLastAttempt
4555
                                );
4556
                            } else {
4557
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4558
                                    $feedback_type,
4559
                                    $answerType,
4560
                                    $studentChoice,
4561
                                    $answer,
4562
                                    $answerComment,
4563
                                    $answerCorrect,
4564
                                    $exeId,
4565
                                    $questionId,
4566
                                    '',
4567
                                    $results_disabled,
4568
                                    $showTotalScoreAndUserChoicesInLastAttempt
4569
                                );
4570
                            }
4571
                            break;
4572 View Code Duplication
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4573
                            if ($answerId == 1) {
4574
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4575
                                    $feedback_type,
4576
                                    $answerType,
4577
                                    $studentChoice,
4578
                                    $answer,
4579
                                    $answerComment,
4580
                                    $answerCorrect,
4581
                                    $exeId,
4582
                                    $questionId,
4583
                                    $answerId,
4584
                                    $results_disabled,
4585
                                    $showTotalScoreAndUserChoicesInLastAttempt
4586
                                );
4587
                            } else {
4588
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4589
                                    $feedback_type,
4590
                                    $answerType,
4591
                                    $studentChoice,
4592
                                    $answer,
4593
                                    $answerComment,
4594
                                    $answerCorrect,
4595
                                    $exeId,
4596
                                    $questionId,
4597
                                    '',
4598
                                    $results_disabled,
4599
                                    $showTotalScoreAndUserChoicesInLastAttempt
4600
                                );
4601
                            }
4602
                            break;
4603
                        case FILL_IN_BLANKS:
4604
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4605
                                $feedback_type,
4606
                                $answer,
4607
                                $exeId,
4608
                                $questionId,
4609
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3134 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...
4610
                                $str,
4611
                                $showTotalScoreAndUserChoicesInLastAttempt
4612
                            );
4613
                            break;
4614
                        case CALCULATED_ANSWER:
4615
                            ExerciseShowFunctions::display_calculated_answer(
4616
                                $feedback_type,
4617
                                $answer,
4618
                                $exeId,
4619
                                $questionId,
4620
                                $results_disabled,
4621
                                '',
4622
                                $showTotalScoreAndUserChoicesInLastAttempt
4623
                            );
4624
                            break;
4625
                        case FREE_ANSWER:
4626
                            echo ExerciseShowFunctions::display_free_answer(
4627
                                $feedback_type,
4628
                                $choice,
4629
                                $exeId,
4630
                                $questionId,
4631
                                $questionScore,
4632
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3134 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...
4633
                            );
4634
                            break;
4635
                        case ORAL_EXPRESSION:
4636
                            echo '<tr>
4637
                                <td valign="top">' . ExerciseShowFunctions::display_oral_expression_answer(
4638
                                    $feedback_type,
4639
                                    $choice,
4640
                                    $exeId,
4641
                                    $questionId,
4642
                                    $objQuestionTmp->getFileUrl(),
4643
                                    $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3134 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...
4644
                                ) . '</td>
4645
                                </tr>
4646
                                </table>';
4647
                            break;
4648
                        case HOT_SPOT:
4649
                            ExerciseShowFunctions::display_hotspot_answer(
4650
                                $feedback_type,
4651
                                $answerId,
4652
                                $answer,
4653
                                $studentChoice,
4654
                                $answerComment,
4655
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3134 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...
4656
                                $answerId,
4657
                                $showTotalScoreAndUserChoicesInLastAttempt
4658
                            );
4659
                            break;
4660
                        case HOT_SPOT_DELINEATION:
4661
                            $user_answer = $user_array;
4662 View Code Duplication
                            if ($next) {
4663
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4664
                                // Save into db
4665
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
4666
                                 * hotspot_user_id,
4667
                                 *  hotspot_course_code,
4668
                                 *  hotspot_exe_id,
4669
                                 *  hotspot_question_id,
4670
                                 *  hotspot_answer_id,
4671
                                 *  hotspot_correct,
4672
                                 *  hotspot_coordinate
4673
                                 *  )
4674
                                VALUES (
4675
                                 * '".Database::escape_string($_user['user_id'])."',
4676
                                 *  '".Database::escape_string($_course['id'])."',
4677
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
4678
                                 *  '".Database::escape_string($answerId)."',
4679
                                 *  '".Database::escape_string($studentChoice)."',
4680
                                 *  '".Database::escape_string($user_array)."')";
4681
                                $result = Database::query($sql,__FILE__,__LINE__);
4682
                                 */
4683
                                $user_answer = $user_array;
4684
4685
                                // we compare only the delineation not the other points
4686
                                $answer_question = $_SESSION['hotspot_coord'][1];
4687
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4688
4689
                                //calculating the area
4690
                                $poly_user = convert_coordinates($user_answer, '/');
4691
                                $poly_answer = convert_coordinates($answer_question, '|');
4692
4693
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4694
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4695
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4696
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4697
4698
                                $overlap = $poly_results['both'];
4699
                                $poly_answer_area = $poly_results['s1'];
4700
                                $poly_user_area = $poly_results['s2'];
4701
                                $missing = $poly_results['s1Only'];
4702
                                $excess = $poly_results['s2Only'];
4703
4704
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user)); //this is an area in pixels
4705
                                if ($debug > 0) {
4706
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4707
                                }
4708
                                if ($overlap < 1) {
4709
                                    //shortcut to avoid complicated calculations
4710
                                    $final_overlap = 0;
4711
                                    $final_missing = 100;
4712
                                    $final_excess = 100;
4713
                                } else {
4714
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
4715
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4716
                                    if ($debug > 1) {
4717
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4718
                                    }
4719
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
4720
                                    $final_missing = 100 - $final_overlap;
4721
                                    if ($debug > 1) {
4722
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4723
                                    }
4724
                                    // 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
4725
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4726
                                    if ($debug > 1) {
4727
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4728
                                    }
4729
                                }
4730
4731
                                //checking the destination parameters parsing the "@@"
4732
                                $destination_items = explode('@@', $answerDestination);
4733
                                $threadhold_total = $destination_items[0];
4734
                                $threadhold_items = explode(';', $threadhold_total);
4735
                                $threadhold1 = $threadhold_items[0]; // overlap
4736
                                $threadhold2 = $threadhold_items[1]; // excess
4737
                                $threadhold3 = $threadhold_items[2];  //missing
4738
                                // if is delineation
4739
                                if ($answerId === 1) {
4740
                                    //setting colors
4741
                                    if ($final_overlap >= $threadhold1) {
4742
                                        $overlap_color = true; //echo 'a';
4743
                                    }
4744
                                    //echo $excess.'-'.$threadhold2;
4745
                                    if ($final_excess <= $threadhold2) {
4746
                                        $excess_color = true; //echo 'b';
4747
                                    }
4748
                                    //echo '--------'.$missing.'-'.$threadhold3;
4749
                                    if ($final_missing <= $threadhold3) {
4750
                                        $missing_color = true; //echo 'c';
4751
                                    }
4752
4753
                                    // if pass
4754
                                    if ($final_overlap >= $threadhold1 && $final_missing <= $threadhold3 && $final_excess <= $threadhold2) {
4755
                                        $next = 1; //go to the oars
4756
                                        $result_comment = get_lang('Acceptable');
4757
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4758
                                    } else {
4759
                                        $next = 0;
4760
                                        $result_comment = get_lang('Unacceptable');
4761
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4762
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4763
                                        //checking the destination parameters parsing the "@@"
4764
                                        $destination_items = explode('@@', $answerDestination);
4765
                                    }
4766
                                } elseif ($answerId > 1) {
4767
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4768
                                        if ($debug > 0) {
4769
                                            error_log(__LINE__ . ' - answerId is of type noerror', 0);
4770
                                        }
4771
                                        //type no error shouldn't be treated
4772
                                        $next = 1;
4773
                                        continue;
4774
                                    }
4775
                                    if ($debug > 0) {
4776
                                        error_log(__LINE__ . ' - answerId is >1 so we\'re probably in OAR', 0);
4777
                                    }
4778
                                    //check the intersection between the oar and the user
4779
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4780
                                    //echo 'official';print_r($x_list);print_r($y_list);
4781
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4782
                                    $inter = $result['success'];
4783
4784
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4785
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4786
4787
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4788
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4789
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4790
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4791
4792
                                    if ($overlap == false) {
4793
                                        //all good, no overlap
4794
                                        $next = 1;
4795
                                        continue;
4796
                                    } else {
4797
                                        if ($debug > 0) {
4798
                                            error_log(__LINE__ . ' - Overlap is ' . $overlap . ': OAR hit', 0);
4799
                                        }
4800
                                        $organs_at_risk_hit++;
4801
                                        //show the feedback
4802
                                        $next = 0;
4803
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4804
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4805
4806
                                        $destination_items = explode('@@', $answerDestination);
4807
                                        $try_hotspot = $destination_items[1];
4808
                                        $lp_hotspot = $destination_items[2];
4809
                                        $select_question_hotspot = $destination_items[3];
4810
                                        $url_hotspot=$destination_items[4];
4811
                                    }
4812
                                }
4813
                            } else {	// the first delineation feedback
4814
                                if ($debug > 0) {
4815
                                    error_log(__LINE__ . ' first', 0);
4816
                                }
4817
                            }
4818
                            break;
4819
                        case HOT_SPOT_ORDER:
4820
                            ExerciseShowFunctions::display_hotspot_order_answer(
4821
                                $feedback_type,
4822
                                $answerId,
4823
                                $answer,
4824
                                $studentChoice,
4825
                                $answerComment
4826
                            );
4827
                            break;
4828
                        case DRAGGABLE:
4829
                            //no break
4830
                        case MATCHING_DRAGGABLE:
4831
                            //no break
4832
                        case MATCHING:
4833
                            echo '<tr>';
4834
                            echo Display::tag('td', $answerMatching[$answerId]);
4835
                            echo Display::tag(
4836
                                'td',
4837
                                "$user_answer / " . Display::tag(
4838
                                    'strong',
4839
                                    $answerMatching[$answerCorrect],
4840
                                    ['style' => 'color: #008000; font-weight: bold;']
4841
                                )
4842
                            );
4843
                            echo '</tr>';
4844
4845
                            break;
4846
                    }
4847
                }
4848
            }
4849
            if ($debug) error_log(' ------ ');
4850
        } // end for that loops over all answers of the current question
4851
4852
        if ($debug) error_log('-- end answer loop --');
4853
4854
        $final_answer = true;
4855
4856
        foreach ($real_answers as $my_answer) {
4857
            if (!$my_answer) {
4858
                $final_answer = false;
4859
            }
4860
        }
4861
4862
        //we add the total score after dealing with the answers
4863
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
4864
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
4865
        ) {
4866
            if ($final_answer) {
4867
                //getting only the first score where we save the weight of all the question
4868
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
4869
                $questionScore += $answerWeighting;
4870
                $totalScore += $answerWeighting;
4871
            }
4872
        }
4873
4874
        //Fixes multiple answer question in order to be exact
4875
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
4876
       /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
4877
            $diff = @array_diff($answer_correct_array, $real_answers);
4878
4879
            // All good answers or nothing works like exact
4880
4881
            $counter = 1;
4882
            $correct_answer = true;
4883
            foreach ($real_answers as $my_answer) {
4884
                if ($debug)
4885
                    error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
4886
                if ($my_answer != $answer_correct_array[$counter]) {
4887
                    $correct_answer = false;
4888
                    break;
4889
                }
4890
                $counter++;
4891
            }
4892
4893
            if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
4894
            if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
4895
            if ($debug) error_log(" correct_answer: ".$correct_answer);
4896
4897
            if ($correct_answer == false) {
4898
                $questionScore = 0;
4899
            }
4900
4901
            // This makes the result non exact
4902
            if (!empty($diff)) {
4903
                $questionScore = 0;
4904
            }
4905
        }*/
4906
4907
        $extra_data = array(
4908
            'final_overlap' => $final_overlap,
4909
            'final_missing'=>$final_missing,
4910
            'final_excess'=> $final_excess,
4911
            'overlap_color' => $overlap_color,
4912
            'missing_color'=>$missing_color,
4913
            'excess_color'=> $excess_color,
4914
            'threadhold1'   => $threadhold1,
4915
            'threadhold2'=>$threadhold2,
4916
            'threadhold3'=> $threadhold3,
4917
        );
4918
        if ($from == 'exercise_result') {
4919
            // if answer is hotspot. To the difference of exercise_show.php,
4920
            //  we use the results from the session (from_db=0)
4921
            // TODO Change this, because it is wrong to show the user
4922
            //  some results that haven't been stored in the database yet
4923
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION ) {
4924
4925
                if ($debug) error_log('$from AND this is a hotspot kind of question ');
4926
4927
                $my_exe_id = 0;
4928
                $from_database = 0;
4929
                if ($answerType == HOT_SPOT_DELINEATION) {
4930
                    if (0) {
4931
                        if ($overlap_color) {
4932
                            $overlap_color='green';
4933
                        } else {
4934
                            $overlap_color='red';
4935
                        }
4936
                        if ($missing_color) {
4937
                            $missing_color='green';
4938
                        } else {
4939
                            $missing_color='red';
4940
                        }
4941
                        if ($excess_color) {
4942
                            $excess_color='green';
4943
                        } else {
4944
                            $excess_color='red';
4945
                        }
4946
                        if (!is_numeric($final_overlap)) {
4947
                            $final_overlap = 0;
4948
                        }
4949
                        if (!is_numeric($final_missing)) {
4950
                            $final_missing = 0;
4951
                        }
4952
                        if (!is_numeric($final_excess)) {
4953
                            $final_excess = 0;
4954
                        }
4955
4956
                        if ($final_overlap>100) {
4957
                            $final_overlap = 100;
4958
                        }
4959
4960
                        $table_resume='<table class="data_table">
4961
                                <tr class="row_odd" >
4962
                                    <td></td>
4963
                                    <td ><b>' . get_lang('Requirements') . '</b></td>
4964
                                    <td><b>' . get_lang('YourAnswer') . '</b></td>
4965
                                </tr>
4966
                                <tr class="row_even">
4967
                                    <td><b>' . get_lang('Overlap') . '</b></td>
4968
                                    <td>' . get_lang('Min') . ' ' . $threadhold1 . '</td>
4969
                                    <td><div style="color:' . $overlap_color . '">'
4970
                                        . (($final_overlap < 0) ? 0 : intval($final_overlap)) . '</div></td>
4971
                                </tr>
4972
                                <tr>
4973
                                    <td><b>' . get_lang('Excess') . '</b></td>
4974
                                    <td>' . get_lang('Max') . ' ' . $threadhold2 . '</td>
4975
                                    <td><div style="color:' . $excess_color . '">'
4976
                                        . (($final_excess < 0) ? 0 : intval($final_excess)) . '</div></td>
4977
                                </tr>
4978
                                <tr class="row_even">
4979
                                    <td><b>' . get_lang('Missing') . '</b></td>
4980
                                    <td>' . get_lang('Max') . ' ' . $threadhold3 . '</td>
4981
                                    <td><div style="color:' . $missing_color . '">'
4982
                                        . (($final_missing < 0) ? 0 : intval($final_missing)) . '</div></td>
4983
                                </tr>
4984
                            </table>';
4985 View Code Duplication
                        if ($next == 0) {
4986
                            $try = $try_hotspot;
4987
                            $lp = $lp_hotspot;
4988
                            $destinationid = $select_question_hotspot;
4989
                            $url = $url_hotspot;
4990
                        } else {
4991
                            //show if no error
4992
                            //echo 'no error';
4993
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
4994
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
4995
                        }
4996
4997
                        echo '<h1><div style="color:#333;">' . get_lang('Feedback') . '</div></h1>
4998
                            <p style="text-align:center">';
4999
5000
                        $message = '<p>' . get_lang('YourDelineation') . '</p>';
5001
                        $message .= $table_resume;
5002
                        $message .= '<br />' . get_lang('ResultIs') . ' ' . $result_comment . '<br />';
5003
                        if ($organs_at_risk_hit > 0) {
5004
                            $message .= '<p><b>' . get_lang('OARHit') . '</b></p>';
5005
                        }
5006
                        $message .='<p>' . $comment . '</p>';
5007
                        echo $message;
5008
                    } else {
5009
                        echo $hotspot_delineation_result[0]; //prints message
5010
                        $from_database = 1;  // the hotspot_solution.swf needs this variable
5011
                    }
5012
5013
                    //save the score attempts
5014
5015
                    if (1) {
5016
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5017
                        $final_answer = $hotspot_delineation_result[1];
5018
                        if ($final_answer == 0) {
5019
                            $questionScore = 0;
5020
                        }
5021
                        // we always insert the answer_id 1 = delineation
5022
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5023
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5024
                        $hotspotValue = (int) $hotspot_delineation_result[1] === 1 ? 1 : 0;
5025
                        Event::saveExerciseAttemptHotspot(
5026
                            $exeId,
5027
                            $quesId,
5028
                            1,
5029
                            $hotspotValue,
5030
                            $exerciseResultCoordinates[$quesId]
5031
                        );
5032
                    } else {
5033
                        if ($final_answer==0) {
5034
                            $questionScore = 0;
5035
                            $answer=0;
5036
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5037
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5038
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
5039
                                    Event::saveExerciseAttemptHotspot(
5040
                                        $exeId,
5041
                                        $quesId,
5042
                                        $idx,
5043
                                        0,
5044
                                        $val
5045
                                    );
5046
                                }
5047
                            }
5048
                        } else {
5049
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5050
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5051
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
5052
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5053
                                    Event::saveExerciseAttemptHotspot(
5054
                                        $exeId,
5055
                                        $quesId,
5056
                                        $idx,
5057
                                        $hotspotValue,
5058
                                        $val
5059
                                    );
5060
                                }
5061
                            }
5062
                        }
5063
                    }
5064
                    $my_exe_id = $exeId;
5065
                }
5066
            }
5067
5068
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5069
                // We made an extra table for the answers
5070
5071
                if ($show_result) {
5072
                    $relPath = api_get_path(WEB_CODE_PATH);
5073
                    //	if ($origin != 'learnpath') {
5074
                    echo '</table></td></tr>';
5075
                    echo "
5076
                        <tr>
5077
                            <td colspan=\"2\">
5078
                                <p><em>" . get_lang('HotSpot') . "</em></p>
5079
                                <div id=\"hotspot-solution-$questionId\"></div>
5080
                                <script>
5081
                                    $(document).on('ready', function () {
5082
                                        new HotspotQuestion({
5083
                                            questionId: $questionId,
5084
                                            exerciseId: $exeId,
5085
                                            selector: '#hotspot-solution-$questionId',
5086
                                            for: 'solution',
5087
                                            relPath: '$relPath'
5088
                                        });
5089
                                    });
5090
                                </script>
5091
                            </td>
5092
                        </tr>
5093
                    ";
5094
                    //	}
5095
                }
5096
            }
5097
5098
            //if ($origin != 'learnpath') {
5099
            if ($show_result) {
5100
                echo '</table>';
5101
            }
5102
            //	}
5103
        }
5104
        unset ($objAnswerTmp);
5105
5106
        $totalWeighting += $questionWeighting;
5107
        // Store results directly in the database
5108
        // For all in one page exercises, the results will be
5109
        // stored by exercise_results.php (using the session)
5110
5111
        if ($saved_results) {
5112
            if ($debug) error_log("Save question results $saved_results");
5113
            if ($debug) error_log(print_r($choice ,1 ));
5114
5115
            if (empty($choice)) {
5116
                $choice = 0;
5117
            }
5118
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5119
                if ($choice != 0) {
5120
                    $reply = array_keys($choice);
5121
                    for ($i = 0; $i < sizeof($reply); $i++) {
5122
                        $ans = $reply[$i];
5123
                        Event::saveQuestionAttempt(
5124
                            $questionScore,
5125
                            $ans . ':' . $choice[$ans],
5126
                            $quesId,
5127
                            $exeId,
5128
                            $i,
5129
                            $this->id
5130
                        );
5131
                        if ($debug) {
5132
                            error_log('result =>' . $questionScore . ' ' . $ans . ':' . $choice[$ans]);
5133
                        }
5134
                    }
5135
                } else {
5136
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5137
                }
5138
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5139
                if ($choice != 0) {
5140
                    $reply = array_keys($choice);
5141
5142
                    if ($debug) {
5143
                        error_log("reply " . print_r($reply, 1) . "");
5144
                    }
5145 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5146
                        $ans = $reply[$i];
5147
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5148
                    }
5149
                } else {
5150
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5151
                }
5152
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5153
                if ($choice != 0) {
5154
                    $reply = array_keys($choice);
5155 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5156
                        $ans = $reply[$i];
5157
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5158
                    }
5159
                } else {
5160
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5161
                }
5162
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5163
                if (isset($matching)) {
5164
                    foreach ($matching as $j => $val) {
5165
                        Event::saveQuestionAttempt($questionScore, $val, $quesId, $exeId, $j, $this->id);
5166
                    }
5167
                }
5168
            } elseif ($answerType == FREE_ANSWER) {
5169
                $answer = $choice;
5170
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5171
            } elseif ($answerType == ORAL_EXPRESSION) {
5172
                $answer = $choice;
5173
                Event::saveQuestionAttempt(
5174
                    $questionScore,
5175
                    $answer,
5176
                    $quesId,
5177
                    $exeId,
5178
                    0,
5179
                    $this->id,
5180
                    false,
5181
                    $objQuestionTmp->getAbsoluteFilePath()
5182
                );
5183
            } elseif (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION])) {
5184
                $answer = $choice;
5185
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5186
                //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
5187
            } elseif ($answerType == HOT_SPOT) {
5188
                $answer = [];
5189
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5190
                    Database::delete(
5191
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5192
                        [
5193
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5194
                                $exeId,
5195
                                $questionId,
5196
                                api_get_course_int_id()
5197
                            ]
5198
                        ]
5199
                    );
5200
5201
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5202
                        $answer[] = $val;
5203
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5204
                        Event::saveExerciseAttemptHotspot(
5205
                            $exeId,
5206
                            $quesId,
5207
                            $idx,
5208
                            $hotspotValue,
5209
                            $val,
5210
                            false,
5211
                            $this->id
5212
                        );
5213
                    }
5214
                }
5215
5216
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5217
            } else {
5218
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0,$this->id);
5219
            }
5220
        }
5221
5222
        if ($propagate_neg == 0 && $questionScore < 0) {
5223
            $questionScore = 0;
5224
        }
5225
5226
        if ($saved_results) {
5227
            $stat_table = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5228
            $sql = 'UPDATE ' . $stat_table . ' SET
5229
                        exe_result = exe_result + ' . floatval($questionScore) . '
5230
                    WHERE exe_id = ' . $exeId;
5231
            Database::query($sql);
5232
        }
5233
5234
        $return_array = array(
5235
            'score'         => $questionScore,
5236
            'weight'        => $questionWeighting,
5237
            'extra'         => $extra_data,
5238
            'open_question' => $arrques,
5239
            'open_answer'   => $arrans,
5240
            'answer_type'   => $answerType
5241
        );
5242
5243
        return $return_array;
5244
    }
5245
5246
    /**
5247
     * Sends a notification when a user ends an examn
5248
     *
5249
     * @param array $question_list_answers
5250
     * @param string $origin
5251
     * @param int $exe_id
5252
     * @param float $score
5253
     * @param float $weight
5254
     * @return bool
5255
     */
5256
    public function send_mail_notification_for_exam($question_list_answers, $origin, $exe_id, $score, $weight)
5257
    {
5258
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1) {
5259
            return false;
5260
        }
5261
5262
        // Email configuration settings
5263
        $courseCode = api_get_course_id();
5264
        $courseInfo = api_get_course_info($courseCode);
5265
        $sessionId = api_get_session_id();
5266
5267
        if (empty($courseInfo)) {
5268
            return false;
5269
        }
5270
5271
        $url_email = api_get_path(WEB_CODE_PATH)
5272
            . 'exercise/exercise_show.php?'
5273
            . api_get_cidreq()
5274
            . '&id_session='
5275
            . $sessionId
5276
            . '&id='
5277
            . $exe_id
5278
            . '&action=qualify';
5279
        $user_info = api_get_user_info(api_get_user_id());
5280
5281
        $scoreLabel = '';
5282
        if (api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true) {
5283
            $scoreLabel = ExerciseLib::show_score($score, $weight, false, true);
5284
            $scoreLabel = "<tr>
5285
                            <td>".get_lang('Score')."</td>
5286
                            <td>&nbsp;$scoreLabel</td>
5287
                        </tr>";
5288
        }
5289
5290
        $msg = get_lang('ExerciseAttempted').'<br /><br />'
5291
                    .get_lang('AttemptDetails').' : <br /><br />
5292
                    <table>
5293
                        <tr>
5294
                            <td><em>'.get_lang('CourseName').'</em></td>
5295
                            <td>&nbsp;<b>#course#</b></td>
5296
                        </tr>
5297
                        <tr>
5298
                            <td>'.get_lang('TestAttempted').'</td>
5299
                            <td>&nbsp;#exercise#</td>
5300
                        </tr>
5301
                        <tr>
5302
                            <td>'.get_lang('StudentName').'</td>
5303
                            <td>&nbsp;#firstName# #lastName#</td>
5304
                        </tr>
5305
                        <tr>
5306
                            <td>'.get_lang('StudentEmail').'</td>
5307
                            <td>&nbsp;#email#</td>
5308
                        </tr>
5309
                        '.$scoreLabel.'
5310
                    </table>';
5311
        $open_question_list = null;
5312
5313
        $msg = str_replace("#email#", $user_info['email'], $msg);
5314
        $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5315
        $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5316
        $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5317
        $msg = str_replace("#course#", $courseInfo['name'], $msg1);
5318
5319
        if ($origin != 'learnpath') {
5320
            $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5321
        }
5322
        $msg1 = str_replace("#url#", $url_email, $msg);
5323
        $mail_content = $msg1;
5324
        $subject = get_lang('ExerciseAttempted');
5325
5326
        if (!empty($sessionId)) {
5327
            $addGeneralCoach = true;
5328
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
5329
            if ($setting === true) {
5330
                $addGeneralCoach = false;
5331
            }
5332
            $teachers = CourseManager::get_coach_list_from_course_code(
5333
                $courseCode,
5334
                $sessionId,
5335
                $addGeneralCoach
5336
            );
5337
        } else {
5338
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5339
        }
5340
5341
        if (!empty($teachers)) {
5342
            foreach ($teachers as $user_id => $teacher_data) {
5343
                MessageManager::send_message_simple(
5344
                    $user_id,
5345
                    $subject,
5346
                    $mail_content
5347
                );
5348
            }
5349
        }
5350
    }
5351
5352
    /**
5353
     * Sends a notification when a user ends an examn
5354
     *
5355
     * @param integer $exe_id
5356
     */
5357
    public function send_notification_for_open_questions($question_list_answers, $origin, $exe_id)
5358
    {
5359
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5360
            return null;
5361
        }
5362
        // Email configuration settings
5363
        $courseCode     = api_get_course_id();
5364
        $course_info    = api_get_course_info($courseCode);
5365
5366
        $url_email = api_get_path(WEB_CODE_PATH)
5367
            . 'exercise/exercise_show.php?'
5368
            . api_get_cidreq()
5369
            . '&id_session='
5370
            . api_get_session_id()
5371
            . '&id='
5372
            . $exe_id
5373
            . '&action=qualify';
5374
        $user_info = api_get_user_info(api_get_user_id());
5375
5376
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
5377
                    .get_lang('AttemptDetails').' : <br /><br />'
5378
                    .'<table>'
5379
                        .'<tr>'
5380
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5381
                            .'<td>&nbsp;<b>#course#</b></td>'
5382
                        .'</tr>'
5383
                        .'<tr>'
5384
                            .'<td>'.get_lang('TestAttempted').'</td>'
5385
                            .'<td>&nbsp;#exercise#</td>'
5386
                        .'</tr>'
5387
                        .'<tr>'
5388
                            .'<td>'.get_lang('StudentName').'</td>'
5389
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5390
                        .'</tr>'
5391
                        .'<tr>'
5392
                            .'<td>'.get_lang('StudentEmail').'</td>'
5393
                            .'<td>&nbsp;#mail#</td>'
5394
                        .'</tr>'
5395
                    .'</table>';
5396
        $open_question_list = null;
5397 View Code Duplication
        foreach ($question_list_answers as $item) {
5398
            $question    = $item['question'];
5399
            $answer      = $item['answer'];
5400
            $answer_type = $item['answer_type'];
5401
5402
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
5403
                $open_question_list .=
5404
                    '<tr>'
5405
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5406
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5407
                    .'</tr>'
5408
                    .'<tr>'
5409
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5410
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5411
                    .'</tr>';
5412
            }
5413
        }
5414
5415
        if (!empty($open_question_list)) {
5416
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
5417
                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
5418
            $msg .= $open_question_list;
5419
            $msg .= '</table><br />';
5420
5421
5422
            $msg1   = str_replace("#exercise#",    $this->exercise, $msg);
5423
            $msg    = str_replace("#firstName#",   $user_info['firstname'],$msg1);
5424
            $msg1   = str_replace("#lastName#",    $user_info['lastname'],$msg);
5425
            $msg    = str_replace("#mail#",        $user_info['email'],$msg1);
5426
            $msg    = str_replace("#course#",      $course_info['name'],$msg1);
5427
5428
            if ($origin != 'learnpath') {
5429
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5430
            }
5431
            $msg1 = str_replace("#url#", $url_email, $msg);
5432
            $mail_content = $msg1;
5433
            $subject = get_lang('OpenQuestionsAttempted');
5434
5435 View Code Duplication
            if (api_get_session_id()) {
5436
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5437
            } else {
5438
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5439
            }
5440
5441
            if (!empty($teachers)) {
5442
                foreach ($teachers as $user_id => $teacher_data) {
5443
                    MessageManager::send_message_simple(
5444
                        $user_id,
5445
                        $subject,
5446
                        $mail_content
5447
                    );
5448
                }
5449
            }
5450
        }
5451
    }
5452
5453
    function send_notification_for_oral_questions($question_list_answers, $origin, $exe_id)
5454
    {
5455
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5456
            return null;
5457
        }
5458
        // Email configuration settings
5459
        $courseCode     = api_get_course_id();
5460
        $course_info    = api_get_course_info($courseCode);
5461
5462
        $url_email = api_get_path(WEB_CODE_PATH)
5463
            . 'exercise/exercise_show.php?'
5464
            . api_get_cidreq()
5465
            . '&id_session='
5466
            . api_get_session_id()
5467
            . '&id='
5468
            . $exe_id
5469
            . '&action=qualify';
5470
        $user_info = api_get_user_info(api_get_user_id());
5471
5472
        $oral_question_list = null;
5473 View Code Duplication
        foreach ($question_list_answers as $item) {
5474
            $question    = $item['question'];
5475
            $answer      = $item['answer'];
5476
            $answer_type = $item['answer_type'];
5477
5478
            if (!empty($question) && !empty($answer) && $answer_type == ORAL_EXPRESSION) {
5479
                $oral_question_list.='<br /><table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'
5480
                    .'<tr>'
5481
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5482
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5483
                    .'</tr>'
5484
                    .'<tr>'
5485
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5486
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5487
                    .'</tr></table>';
5488
            }
5489
        }
5490
5491
        if (!empty($oral_question_list)) {
5492
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
5493
                    '.get_lang('AttemptDetails').' : <br /><br />'
5494
                    .'<table>'
5495
                        .'<tr>'
5496
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5497
                            .'<td>&nbsp;<b>#course#</b></td>'
5498
                        .'</tr>'
5499
                        .'<tr>'
5500
                            .'<td>'.get_lang('TestAttempted').'</td>'
5501
                            .'<td>&nbsp;#exercise#</td>'
5502
                        .'</tr>'
5503
                        .'<tr>'
5504
                            .'<td>'.get_lang('StudentName').'</td>'
5505
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5506
                        .'</tr>'
5507
                        .'<tr>'
5508
                            .'<td>'.get_lang('StudentEmail').'</td>'
5509
                            .'<td>&nbsp;#mail#</td>'
5510
                        .'</tr>'
5511
                    .'</table>';
5512
            $msg .=  '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'),$oral_question_list).'<br />';
5513
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5514
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5515
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5516
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5517
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5518
5519
            if ($origin != 'learnpath') {
5520
                $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5521
            }
5522
            $msg1 = str_replace("#url#", $url_email, $msg);
5523
            $mail_content = $msg1;
5524
            $subject = get_lang('OralQuestionsAttempted');
5525
5526 View Code Duplication
            if (api_get_session_id()) {
5527
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5528
            } else {
5529
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5530
            }
5531
5532
            if (!empty($teachers)) {
5533
                foreach ($teachers as $user_id => $teacher_data) {
5534
                    MessageManager::send_message_simple(
5535
                        $user_id,
5536
                        $subject,
5537
                        $mail_content
5538
                    );
5539
                }
5540
            }
5541
        }
5542
    }
5543
5544
    /**
5545
     * @param array $user_data result of api_get_user_info()
5546
     * @param string $start_date
5547
     * @param null $duration
5548
     * @param string $ip Optional. The user IP
5549
     * @return string
5550
     */
5551
    public function show_exercise_result_header($user_data, $start_date = null, $duration = null, $ip = null)
5552
    {
5553
        $array = array();
5554
5555
        if (!empty($user_data)) {
5556
            $array[] = array('title' => get_lang('Name'), 'content' => $user_data['complete_name']);
5557
            $array[] = array('title' => get_lang('Username'), 'content' => $user_data['username']);
5558
            if (!empty($user_data['official_code'])) {
5559
                $array[] = array(
5560
                    'title' => get_lang('OfficialCode'),
5561
                    'content' => $user_data['official_code']
5562
                );
5563
            }
5564
        }
5565
        // Description can be very long and is generally meant to explain
5566
        //   rules *before* the exam. Leaving here to make display easier if
5567
        //   necessary
5568
        /*
5569
        if (!empty($this->description)) {
5570
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
5571
        }
5572
        */
5573 View Code Duplication
        if (!empty($start_date)) {
5574
            $array[] = array('title' => get_lang('StartDate'), 'content' => $start_date);
5575
        }
5576
5577 View Code Duplication
        if (!empty($duration)) {
5578
            $array[] = array('title' => get_lang('Duration'), 'content' => $duration);
5579
        }
5580
5581 View Code Duplication
        if (!empty($ip)) {
5582
            $array[] = array('title' => get_lang('IP'), 'content' => $ip);
5583
        }
5584
        $html  = '<div class="question-result">';
5585
        $html .= Display::page_header(
5586
            Display::return_icon('test-quiz.png', get_lang('Result'),null, ICON_SIZE_MEDIUM).' '.$this->exercise.' : '.get_lang('Result')
5587
        );
5588
        $html .= Display::description($array);
5589
        $html .="</div>";
5590
        return $html;
5591
    }
5592
5593
    /**
5594
     * Create a quiz from quiz data
5595
     * @param string  Title
5596
     * @param int     Time before it expires (in minutes)
5597
     * @param int     Type of exercise
5598
     * @param int     Whether it's randomly picked questions (1) or not (0)
5599
     * @param int     Whether the exercise is visible to the user (1) or not (0)
5600
     * @param int     Whether the results are show to the user (0) or not (1)
5601
     * @param int     Maximum number of attempts (0 if no limit)
5602
     * @param int     Feedback type
5603
     * @todo this was function was added due the import exercise via CSV
5604
     * @return    int New exercise ID
5605
     */
5606
    public function createExercise(
5607
        $title,
5608
        $expired_time = 0,
5609
        $type = 2,
5610
        $random = 0,
5611
        $active = 1,
5612
        $results_disabled = 0,
5613
        $max_attempt = 0,
5614
        $feedback = 3,
5615
        $propagateNegative = 0
5616
    ) {
5617
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
5618
        $type = intval($type);
5619
        $random = intval($random);
5620
        $active = intval($active);
5621
        $results_disabled = intval($results_disabled);
5622
        $max_attempt = intval($max_attempt);
5623
        $feedback = intval($feedback);
5624
        $expired_time = intval($expired_time);
5625
        $title = Database::escape_string($title);
5626
        $propagateNegative = intval($propagateNegative);
5627
        $sessionId = api_get_session_id();
5628
        $course_id = api_get_course_int_id();
5629
        // Save a new quiz
5630
        $sql = "INSERT INTO $tbl_quiz (
5631
                c_id,
5632
                title,
5633
                type,
5634
                random,
5635
                active,
5636
                results_disabled,
5637
                max_attempt,
5638
                start_time,
5639
                end_time,
5640
                feedback_type,
5641
                expired_time,
5642
                session_id,
5643
                propagate_neg
5644
            )
5645
            VALUES (
5646
                '$course_id',
5647
                '$title',
5648
                $type,
5649
                $random,
5650
                $active,
5651
                $results_disabled,
5652
                $max_attempt,
5653
                '',
5654
                '',
5655
                $feedback,
5656
                $expired_time,
5657
                $sessionId,
5658
                $propagateNegative
5659
            )";
5660
        Database::query($sql);
5661
        $quiz_id = Database::insert_id();
5662
5663
        if ($quiz_id) {
5664
5665
            $sql = "UPDATE $tbl_quiz SET id = iid WHERE iid = {$quiz_id} ";
5666
            Database::query($sql);
5667
        }
5668
5669
        return $quiz_id;
5670
    }
5671
5672
    function process_geometry()
5673
    {
5674
5675
    }
5676
5677
    /**
5678
     * Returns the exercise result
5679
     * @param 	int		attempt id
5680
     * @return 	float 	exercise result
5681
     */
5682
    public function get_exercise_result($exe_id)
5683
    {
5684
        $result = array();
5685
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
5686
5687
        if (!empty($track_exercise_info)) {
5688
            $totalScore = 0;
5689
            $objExercise = new Exercise();
5690
            $objExercise->read($track_exercise_info['exe_exo_id']);
5691
            if (!empty($track_exercise_info['data_tracking'])) {
5692
                $question_list = explode(',', $track_exercise_info['data_tracking']);
5693
            }
5694
            foreach ($question_list as $questionId) {
5695
                $question_result = $objExercise->manage_answer(
5696
                    $exe_id,
5697
                    $questionId,
5698
                    '',
5699
                    'exercise_show',
5700
                    array(),
5701
                    false,
5702
                    true,
5703
                    false,
5704
                    $objExercise->selectPropagateNeg()
5705
                );
5706
                $totalScore      += $question_result['score'];
5707
            }
5708
5709
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
5710
                $totalScore = 0;
5711
            }
5712
            $result = array(
5713
                'score' => $totalScore,
5714
                'weight' => $track_exercise_info['exe_weighting']
5715
            );
5716
        }
5717
        return $result;
5718
    }
5719
5720
    /**
5721
     * Checks if the exercise is visible due a lot of conditions
5722
     * visibility, time limits, student attempts
5723
     * Return associative array
5724
     * value : true if execise visible
5725
     * message : HTML formated message
5726
     * rawMessage : text message
5727
     * @param int $lpId
5728
     * @param int $lpItemId
5729
     * @param int $lpItemViewId
5730
     * @param bool $filterByAdmin
5731
     * @return array
5732
     */
5733
    public function is_visible(
5734
        $lpId = 0,
5735
        $lpItemId = 0,
5736
        $lpItemViewId = 0,
5737
        $filterByAdmin = true
5738
    ) {
5739
        // 1. By default the exercise is visible
5740
        $isVisible = true;
5741
        $message = null;
5742
5743
        // 1.1 Admins and teachers can access to the exercise
5744
        if ($filterByAdmin) {
5745
            if (api_is_platform_admin() || api_is_course_admin()) {
5746
                return array('value' => true, 'message' => '');
5747
            }
5748
        }
5749
5750
        // Deleted exercise.
5751 View Code Duplication
        if ($this->active == -1) {
5752
            return array(
5753
                'value' => false,
5754
                'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5755
                'rawMessage' => get_lang('ExerciseNotFound')
5756
            );
5757
        }
5758
5759
        // Checking visibility in the item_property table.
5760
        $visibility = api_get_item_visibility(
5761
            api_get_course_info(),
5762
            TOOL_QUIZ,
5763
            $this->id,
5764
            api_get_session_id()
5765
        );
5766
5767
        if ($visibility == 0 || $visibility == 2) {
5768
            $this->active = 0;
5769
        }
5770
5771
        // 2. If the exercise is not active.
5772
        if (empty($lpId)) {
5773
            // 2.1 LP is OFF
5774 View Code Duplication
            if ($this->active == 0) {
5775
                return array(
5776
                    'value' => false,
5777
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5778
                    'rawMessage' => get_lang('ExerciseNotFound')
5779
                );
5780
            }
5781
        } else {
5782
            // 2.1 LP is loaded
5783
            if ($this->active == 0 && !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())) {
5784
                return array(
5785
                    'value' => false,
5786
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5787
                    'rawMessage' => get_lang('ExerciseNotFound')
5788
                );
5789
            }
5790
        }
5791
5792
        //3. We check if the time limits are on
5793
        if (!empty($this->start_time) || !empty($this->end_time)) {
5794
            $limitTimeExists = true;
5795
        } else {
5796
            $limitTimeExists = false;
5797
        }
5798
5799
        if ($limitTimeExists) {
5800
            $timeNow = time();
5801
5802
            $existsStartDate = false;
5803
            $nowIsAfterStartDate = true;
5804
            $existsEndDate = false;
5805
            $nowIsBeforeEndDate = true;
5806
5807
            if (!empty($this->start_time)) {
5808
                $existsStartDate = true;
5809
            }
5810
5811
            if (!empty($this->end_time)) {
5812
                $existsEndDate = true;
5813
            }
5814
5815
            // check if we are before-or-after end-or-start date
5816
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
5817
                $nowIsAfterStartDate = false;
5818
                    }
5819
5820
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
5821
                $nowIsBeforeEndDate = false;
5822
                }
5823
5824
            // lets check all cases
5825
            if ($existsStartDate && !$existsEndDate) {
5826
                // exists start date and dont exists end date
5827
                if ($nowIsAfterStartDate) {
5828
                    // after start date, no end date
5829
                    $isVisible = true;
5830
                    $message = sprintf(get_lang('ExerciseAvailableSinceX'),
5831
                        api_convert_and_format_date($this->start_time));
5832
                } else {
5833
                    // before start date, no end date
5834
                    $isVisible = false;
5835
                    $message = sprintf(get_lang('ExerciseAvailableFromX'),
5836
                        api_convert_and_format_date($this->start_time));
5837
            }
5838
            } else if (!$existsStartDate && $existsEndDate) {
5839
                // doesnt exist start date, exists end date
5840
                if ($nowIsBeforeEndDate) {
5841
                    // before end date, no start date
5842
                    $isVisible = true;
5843
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5844
                        api_convert_and_format_date($this->end_time));
5845
                } else {
5846
                    // after end date, no start date
5847
                    $isVisible = false;
5848
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5849
                        api_convert_and_format_date($this->end_time));
5850
                }
5851
            } elseif ($existsStartDate && $existsEndDate) {
5852
                // exists start date and end date
5853
                if ($nowIsAfterStartDate) {
5854
                    if ($nowIsBeforeEndDate) {
5855
                        // after start date and before end date
5856
                        $isVisible = true;
5857
                        $message = sprintf(get_lang('ExerciseIsActivatedFromXToY'),
5858
                    api_convert_and_format_date($this->start_time),
5859
                            api_convert_and_format_date($this->end_time));
5860 View Code Duplication
                    } else {
5861
                        // after start date and after end date
5862
                        $isVisible = false;
5863
                        $message = sprintf(get_lang('ExerciseWasActivatedFromXToY'),
5864
                            api_convert_and_format_date($this->start_time),
5865
                            api_convert_and_format_date($this->end_time));
5866
                    }
5867 View Code Duplication
                } else {
5868
                    if ($nowIsBeforeEndDate) {
5869
                        // before start date and before end date
5870
                        $isVisible = false;
5871
                        $message = sprintf(get_lang('ExerciseWillBeActivatedFromXToY'),
5872
                            api_convert_and_format_date($this->start_time),
5873
                            api_convert_and_format_date($this->end_time));
5874
                    }
5875
                    // case before start date and after end date is impossible
5876
                }
5877
            } elseif (!$existsStartDate && !$existsEndDate) {
5878
                // doesnt exist start date nor end date
5879
                $isVisible = true;
5880
                $message = "";
5881
            }
5882
        }
5883
5884
        // 4. We check if the student have attempts
5885
        $exerciseAttempts = $this->selectAttempts();
5886
5887
        if ($isVisible) {
5888
            if ($exerciseAttempts > 0) {
5889
5890
                $attemptCount = Event::get_attempt_count_not_finished(
5891
                    api_get_user_id(),
5892
                    $this->id,
5893
                    $lpId,
5894
                    $lpItemId,
5895
                    $lpItemViewId
5896
                );
5897
5898
                if ($attemptCount >= $exerciseAttempts) {
5899
                    $message = sprintf(
5900
                        get_lang('ReachedMaxAttempts'),
5901
                        $this->name,
5902
                        $exerciseAttempts
5903
                    );
5904
                    $isVisible = false;
5905
                }
5906
            }
5907
        }
5908
5909
        $rawMessage = "";
5910
        if (!empty($message)){
5911
            $rawMessage = $message;
5912
            $message = Display::return_message($message, 'warning', false);
5913
        }
5914
5915
        return array(
5916
            'value' => $isVisible,
5917
            'message' => $message,
5918
            'rawMessage' => $rawMessage
5919
        );
5920
    }
5921
5922
    public function added_in_lp()
5923
    {
5924
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
5925
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
5926
            WHERE c_id = {$this->course_id} AND item_type = '" . TOOL_QUIZ . "' AND path = '{$this->id}'";
5927
        $result = Database::query($sql);
5928
        if (Database::num_rows($result) > 0) {
5929
            return true;
5930
        }
5931
        return false;
5932
    }
5933
5934
    /**
5935
     * Returns an array with the media list
5936
     * @param array question list
5937
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
5938
     * <code>
5939
     * array (size=2)
5940
     *  999 =>
5941
     *    array (size=3)
5942
     *      0 => int 7
5943
     *      1 => int 6
5944
     *      2 => int 3254
5945
     *  100 =>
5946
     *   array (size=1)
5947
     *      0 => int 5
5948
     *  </code>
5949
     * @return array
5950
     */
5951
    private function setMediaList($questionList)
5952
    {
5953
        $mediaList = array();
5954
        if (!empty($questionList)) {
5955
            foreach ($questionList as $questionId) {
5956
                $objQuestionTmp = Question::read($questionId, $this->course_id);
5957
5958
                // If a media question exists
5959
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
5960
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
5961
                } else {
5962
                    //Always the last item
5963
                    $mediaList[999][] = $objQuestionTmp->id;
5964
                }
5965
            }
5966
        }
5967
        $this->mediaList = $mediaList;
5968
    }
5969
5970
    /**
5971
     * Returns an array with this form
5972
     * @example
5973
     * <code>
5974
     * array (size=3)
5975
    999 =>
5976
    array (size=3)
5977
    0 => int 3422
5978
    1 => int 3423
5979
    2 => int 3424
5980
    100 =>
5981
    array (size=2)
5982
    0 => int 3469
5983
    1 => int 3470
5984
    101 =>
5985
    array (size=1)
5986
    0 => int 3482
5987
     * </code>
5988
     * The array inside the key 999 means the question list that belongs to the media id = 999,
5989
     * this case is special because 999 means "no media".
5990
     * @return array
5991
     */
5992
    public function getMediaList()
5993
    {
5994
        return $this->mediaList;
5995
    }
5996
5997
    /**
5998
     * Is media question activated?
5999
     * @return bool
6000
     */
6001
    public function mediaIsActivated()
6002
    {
6003
        $mediaQuestions = $this->getMediaList();
6004
        $active = false;
6005
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6006
            $media_count = count($mediaQuestions);
6007
            if ($media_count > 1) {
6008
                return true;
6009
            } elseif ($media_count == 1) {
6010
                if (isset($mediaQuestions[999])) {
6011
                    return false;
6012
                } else {
6013
                    return true;
6014
                }
6015
            }
6016
        }
6017
6018
        return $active;
6019
    }
6020
6021
    /**
6022
     * Gets question list from the exercise
6023
     *
6024
     * @return array
6025
     */
6026
    public function getQuestionList()
6027
    {
6028
        return $this->questionList;
6029
    }
6030
6031
    /**
6032
     * Question list with medias compressed like this
6033
     * @example
6034
     * <code>
6035
     * array(
6036
     *      question_id_1,
6037
     *      question_id_2,
6038
     *      media_id, <- this media id contains question ids
6039
     *      question_id_3,
6040
     * )
6041
     * </code>
6042
     * @return array
6043
     */
6044
    public function getQuestionListWithMediasCompressed()
6045
    {
6046
        return $this->questionList;
6047
    }
6048
6049
    /**
6050
     * Question list with medias uncompressed like this
6051
     * @example
6052
     * <code>
6053
     * array(
6054
     *      question_id,
6055
     *      question_id,
6056
     *      question_id, <- belongs to a media id
6057
     *      question_id, <- belongs to a media id
6058
     *      question_id,
6059
     * )
6060
     * </code>
6061
     * @return array
6062
     */
6063
    public function getQuestionListWithMediasUncompressed()
6064
    {
6065
        return $this->questionListUncompressed;
6066
    }
6067
6068
    /**
6069
     * Sets the question list when the exercise->read() is executed
6070
     * @param   bool    $adminView  Whether to view the set the list of *all* questions or just the normal student view
6071
     */
6072
    public function setQuestionList($adminView = false)
6073
    {
6074
        // Getting question list.
6075
        $questionList = $this->selectQuestionList(true, $adminView);
6076
6077
        $this->setMediaList($questionList);
6078
6079
        $this->questionList = $this->transformQuestionListWithMedias($questionList, false);
6080
        $this->questionListUncompressed = $this->transformQuestionListWithMedias($questionList, true);
6081
    }
6082
6083
    /**
6084
     *
6085
     * @params array question list
6086
     * @params bool expand or not question list (true show all questions, false show media question id instead of the question ids)
6087
     *
6088
     **/
6089 View Code Duplication
    public function transformQuestionListWithMedias($question_list, $expand_media_questions = false)
6090
    {
6091
        $new_question_list = array();
6092
        if (!empty($question_list)) {
6093
            $media_questions = $this->getMediaList();
6094
6095
            $media_active = $this->mediaIsActivated($media_questions);
6096
6097
            if ($media_active) {
6098
                $counter = 1;
6099
                foreach ($question_list as $question_id) {
6100
                    $add_question = true;
6101
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6102
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6103
                            $add_question = false;
6104
                            if (!in_array($media_id, $new_question_list)) {
6105
                                $new_question_list[$counter] = $media_id;
6106
                                $counter++;
6107
                            }
6108
                            break;
6109
                        }
6110
                    }
6111
                    if ($add_question) {
6112
                        $new_question_list[$counter] = $question_id;
6113
                        $counter++;
6114
                    }
6115
                }
6116
                if ($expand_media_questions) {
6117
                    $media_key_list = array_keys($media_questions);
6118
                    foreach ($new_question_list as &$question_id) {
6119
                        if (in_array($question_id, $media_key_list)) {
6120
                            $question_id = $media_questions[$question_id];
6121
                        }
6122
                    }
6123
                    $new_question_list = array_flatten($new_question_list);
6124
                }
6125
            } else {
6126
                $new_question_list = $question_list;
6127
            }
6128
        }
6129
6130
        return $new_question_list;
6131
    }
6132
6133
    function get_validated_question_list()
6134
    {
6135
        $tabres = array();
6136
        $isRandomByCategory = $this->isRandomByCat();
6137
        if ($isRandomByCategory == 0) {
6138
            if ($this->isRandom()) {
6139
                $tabres = $this->selectRandomList();
6140
            } else {
6141
                $tabres = $this->selectQuestionList();
6142
            }
6143
        } else {
6144
            if ($this->isRandom()) {
6145
                // USE question categories
6146
                // get questions by category for this exercise
6147
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6148
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6149
                // value is the array of question id of this category
6150
                $questionList = array();
6151
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6152
                $isRandomByCategory = $this->selectRandomByCat();
6153
                // We sort categories based on the term between [] in the head
6154
                // of the category's description
6155
                /* examples of categories :
6156
                 * [biologie] Maitriser les mecanismes de base de la genetique
6157
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6158
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6159
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6160
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6161
                 * [chimie] Connaître les charges des particules
6162
                 * We want that in the order of the groups defined by the term
6163
                 * between brackets at the beginning of the category title
6164
                */
6165
                // If test option is Grouped By Categories
6166
                if ($isRandomByCategory == 2) {
6167
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6168
                }
6169
                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...
6170
                    $number_of_random_question = $this->random;
6171
                    if ($this->random == -1) {
6172
                        $number_of_random_question = count($this->questionList);
6173
                    }
6174
                    $questionList = array_merge(
6175
                        $questionList,
6176
                        TestCategory::getNElementsFromArray(
6177
                            $tabquestion,
6178
                            $number_of_random_question
6179
                        )
6180
                    );
6181
                }
6182
                // shuffle the question list if test is not grouped by categories
6183
                if ($isRandomByCategory == 1) {
6184
                    shuffle($questionList); // or not
6185
                }
6186
                $tabres = $questionList;
6187
            } 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...
6188
                // Problem, random by category has been selected and
6189
                // we have no $this->isRandom number of question selected
6190
                // Should not happened
6191
            }
6192
        }
6193
        return $tabres;
6194
    }
6195
6196
    function get_question_list($expand_media_questions = false)
6197
    {
6198
        $question_list = $this->get_validated_question_list();
6199
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6200
        return $question_list;
6201
    }
6202
6203 View Code Duplication
    function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6204
    {
6205
        $new_question_list = array();
6206
        if (!empty($question_list)) {
6207
            $media_questions = $this->getMediaList();
6208
            $media_active = $this->mediaIsActivated($media_questions);
6209
6210
            if ($media_active) {
6211
                $counter = 1;
6212
                foreach ($question_list as $question_id) {
6213
                    $add_question = true;
6214
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6215
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6216
                            $add_question = false;
6217
                            if (!in_array($media_id, $new_question_list)) {
6218
                                $new_question_list[$counter] = $media_id;
6219
                                $counter++;
6220
                            }
6221
                            break;
6222
                        }
6223
                    }
6224
                    if ($add_question) {
6225
                        $new_question_list[$counter] = $question_id;
6226
                        $counter++;
6227
                    }
6228
                }
6229
                if ($expand_media_questions) {
6230
                    $media_key_list = array_keys($media_questions);
6231
                    foreach ($new_question_list as &$question_id) {
6232
                        if (in_array($question_id, $media_key_list)) {
6233
                            $question_id = $media_questions[$question_id];
6234
                        }
6235
                    }
6236
                    $new_question_list = array_flatten($new_question_list);
6237
                }
6238
            } else {
6239
                $new_question_list = $question_list;
6240
            }
6241
        }
6242
        return $new_question_list;
6243
    }
6244
6245
    /**
6246
     * @param int $exe_id
6247
     * @return array|mixed
6248
     */
6249
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6250
    {
6251
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6252
        $exe_id = intval($exe_id);
6253
        $sql_track = "SELECT * FROM $track_exercises WHERE exe_id = $exe_id ";
6254
        $result = Database::query($sql_track);
6255
        $new_array = array();
6256
        if (Database::num_rows($result) > 0 ) {
6257
            $new_array = Database::fetch_array($result, 'ASSOC');
6258
6259
            $new_array['duration'] = null;
6260
6261
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6262
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6263
6264
            if (!empty($start_date) && !empty($end_date)) {
6265
                $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 6265 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...
6266
                $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 6266 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...
6267
                if ($start_date && $end_date) {
6268
                    $mytime = $end_date- $start_date;
6269
                    $new_learnpath_item = new learnpathItem(null);
6270
                    $time_attemp = $new_learnpath_item->get_scorm_time('js', $mytime);
6271
                    $h = get_lang('h');
6272
                    $time_attemp = str_replace('NaN', '00' . $h . '00\'00"', $time_attemp);
6273
                    $new_array['duration'] = $time_attemp;
6274
                }
6275
            }
6276
        }
6277
        return $new_array;
6278
    }
6279
6280
    public function edit_question_to_remind($exe_id, $question_id, $action = 'add')
6281
    {
6282
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6283
        $question_id = intval($question_id);
6284
        $exe_id = intval($exe_id);
6285
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6286
        if ($exercise_info) {
6287
6288
            if (empty($exercise_info['questions_to_check'])) {
6289
                if ($action == 'add') {
6290
                    $sql = "UPDATE $track_exercises SET questions_to_check = '$question_id' WHERE exe_id = $exe_id ";
6291
                    Database::query($sql);
6292
                }
6293
            } else {
6294
                $remind_list = explode(',',$exercise_info['questions_to_check']);
6295
6296
                $remind_list_string = '';
6297
                if ($action == 'add') {
6298
                    if (!in_array($question_id, $remind_list)) {
6299
                        $remind_list[] = $question_id;
6300
                        if (!empty($remind_list)) {
6301
                            sort($remind_list);
6302
                            array_filter($remind_list);
6303
                        }
6304
                        $remind_list_string = implode(',', $remind_list);
6305
                    }
6306
                } elseif ($action == 'delete')  {
6307
                    if (!empty($remind_list)) {
6308
                        if (in_array($question_id, $remind_list)) {
6309
                            $remind_list = array_flip($remind_list);
6310
                            unset($remind_list[$question_id]);
6311
                            $remind_list = array_flip($remind_list);
6312
6313
                            if (!empty($remind_list)) {
6314
                                sort($remind_list);
6315
                                array_filter($remind_list);
6316
                                $remind_list_string = implode(',', $remind_list);
6317
                            }
6318
                        }
6319
                    }
6320
                }
6321
                $remind_list_string = Database::escape_string($remind_list_string);
6322
                $sql = "UPDATE $track_exercises SET questions_to_check = '$remind_list_string' WHERE exe_id = $exe_id ";
6323
                Database::query($sql);
6324
            }
6325
        }
6326
    }
6327
6328
    public function fill_in_blank_answer_to_array($answer)
6329
    {
6330
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6331
        $teacher_answer_list = $teacher_answer_list[0];
6332
        return $teacher_answer_list;
6333
    }
6334
6335
    public function fill_in_blank_answer_to_string($answer)
6336
    {
6337
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6338
        $result = '';
6339
        if (!empty($teacher_answer_list)) {
6340
            $i = 0;
6341
            foreach ($teacher_answer_list as $teacher_item) {
6342
                $value = null;
6343
                //Cleaning student answer list
6344
                $value = strip_tags($teacher_item);
6345
                $value = api_substr($value, 1, api_strlen($value) - 2);
6346
                $value = explode('/', $value);
6347
                if (!empty($value[0])) {
6348
                    $value = trim($value[0]);
6349
                    $value = str_replace('&nbsp;', '', $value);
6350
                    $result .= $value;
6351
                }
6352
            }
6353
        }
6354
        return $result;
6355
    }
6356
6357
    function return_time_left_div()
6358
    {
6359
        $html = '<div id="clock_warning" style="display:none">';
6360
        $html .= Display::return_message(
6361
            get_lang('ReachedTimeLimit'),
6362
            'warning'
6363
        );
6364
        $html .= ' ';
6365
        $html .= sprintf(
6366
            get_lang('YouWillBeRedirectedInXSeconds'),
6367
            '<span id="counter_to_redirect" class="red_alert"></span>'
6368
        );
6369
        $html .= '</div>';
6370
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6371
        return $html;
6372
    }
6373
6374
    function get_count_question_list()
6375
    {
6376
        //Real question count
6377
        $question_count = 0;
6378
        $question_list = $this->get_question_list();
6379
        if (!empty($question_list)) {
6380
            $question_count = count($question_list);
6381
        }
6382
        return $question_count;
6383
    }
6384
6385
    function get_exercise_list_ordered()
6386
    {
6387
        $table_exercise_order = Database::get_course_table(TABLE_QUIZ_ORDER);
6388
        $course_id = api_get_course_int_id();
6389
        $session_id = api_get_session_id();
6390
        $sql = "SELECT exercise_id, exercise_order
6391
                FROM $table_exercise_order
6392
                WHERE c_id = $course_id AND session_id = $session_id
6393
                ORDER BY exercise_order";
6394
        $result = Database::query($sql);
6395
        $list = array();
6396
        if (Database::num_rows($result)) {
6397
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6398
                $list[$row['exercise_order']] = $row['exercise_id'];
6399
            }
6400
        }
6401
        return $list;
6402
    }
6403
6404
    /**
6405
     * Get categories added in the exercise--category matrix
6406
     * @return bool
6407
     */
6408
    public function get_categories_in_exercise()
6409
    {
6410
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6411
        if (!empty($this->id)) {
6412
            $sql = "SELECT * FROM $table
6413
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6414
            $result = Database::query($sql);
6415
            $list = array();
6416
            if (Database::num_rows($result)) {
6417
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6418
                    $list[$row['category_id']] = $row;
6419
                }
6420
                return $list;
6421
            }
6422
        }
6423
        return false;
6424
    }
6425
6426
    /**
6427
     * @param null $order
6428
     * @return bool
6429
     */
6430
    public function get_categories_with_name_in_exercise($order = null)
6431
    {
6432
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6433
        $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
6434
        $sql = "SELECT * FROM $table qc
6435
                INNER JOIN $table_category c
6436
                ON (category_id = c.iid)
6437
                WHERE exercise_id = {$this->id} AND qc.c_id = {$this->course_id} ";
6438
        if (!empty($order)) {
6439
            $sql .= "ORDER BY $order ";
6440
        }
6441
        $result = Database::query($sql);
6442
        if (Database::num_rows($result)) {
6443
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6444
                $list[$row['category_id']] = $row;
6445
            }
6446
            return $list;
6447
        }
6448
        return false;
6449
    }
6450
6451
    /**
6452
     * Get total number of question that will be parsed when using the category/exercise
6453
     */
6454 View Code Duplication
    public function getNumberQuestionExerciseCategory()
6455
    {
6456
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6457
        if (!empty($this->id)) {
6458
            $sql = "SELECT SUM(count_questions) count_questions
6459
                    FROM $table
6460
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6461
            $result = Database::query($sql);
6462
            if (Database::num_rows($result)) {
6463
                $row = Database::fetch_array($result);
6464
                return $row['count_questions'];
6465
            }
6466
        }
6467
        return 0;
6468
    }
6469
6470
    /**
6471
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table
6472
     * @param array $categories
6473
     */
6474
    public function save_categories_in_exercise($categories)
6475
    {
6476
        if (!empty($categories) && !empty($this->id)) {
6477
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6478
            $sql = "DELETE FROM $table
6479
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6480
            Database::query($sql);
6481
            if (!empty($categories)) {
6482
                foreach ($categories as $category_id => $count_questions) {
6483
                    $params = array(
6484
                        'c_id' => $this->course_id,
6485
                        'exercise_id' => $this->id,
6486
                        'category_id' => $category_id,
6487
                        'count_questions' => $count_questions
6488
                    );
6489
                    Database::insert($table, $params);
6490
                }
6491
            }
6492
        }
6493
    }
6494
6495
    /**
6496
     * @param array $questionList
6497
     * @param int $currentQuestion
6498
     * @param array $conditions
6499
     * @param string $link
6500
     * @return string
6501
     */
6502
    public function progressExercisePaginationBar($questionList, $currentQuestion, $conditions, $link)
6503
    {
6504
        $mediaQuestions = $this->getMediaList();
6505
6506
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6507
        $counter = 0;
6508
        $nextValue = 0;
6509
        $wasMedia = false;
6510
        $before = 0;
6511
        $counterNoMedias = 0;
6512
        foreach ($questionList as $questionId) {
6513
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6514
6515
            if (!empty($nextValue)) {
6516
                if ($wasMedia) {
6517
                    $nextValue = $nextValue - $before + 1;
6518
                }
6519
            }
6520
6521
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6522
                $fixedValue = $counterNoMedias;
6523
6524
                $html .= Display::progressPaginationBar(
6525
                    $nextValue,
6526
                    $mediaQuestions[$questionId],
6527
                    $currentQuestion,
6528
                    $fixedValue,
6529
                    $conditions,
6530
                    $link,
6531
                    true,
6532
                    true
6533
                );
6534
6535
                $counter += count($mediaQuestions[$questionId]) - 1 ;
6536
                $before = count($questionList);
6537
                $wasMedia = true;
6538
                $nextValue += count($questionList);
6539
            } else {
6540
                $html .= Display::parsePaginationItem($questionId, $isCurrent, $conditions, $link, $counter);
6541
                $counter++;
6542
                $nextValue++;
6543
                $wasMedia = false;
6544
            }
6545
            $counterNoMedias++;
6546
        }
6547
        $html .= '</ul></div>';
6548
        return $html;
6549
    }
6550
6551
6552
    /**
6553
     *  Shows a list of numbers that represents the question to answer in a exercise
6554
     *
6555
     * @param array $categories
6556
     * @param int $current
6557
     * @param array $conditions
6558
     * @param string $link
6559
     * @return string
6560
     */
6561
    public function progressExercisePaginationBarWithCategories(
6562
        $categories,
6563
        $current,
6564
        $conditions = array(),
6565
        $link = null
6566
    ) {
6567
        $html = null;
6568
        $counterNoMedias = 0;
6569
        $nextValue = 0;
6570
        $wasMedia = false;
6571
        $before = 0;
6572
6573
        if (!empty($categories)) {
6574
            $selectionType = $this->getQuestionSelectionType();
6575
            $useRootAsCategoryTitle = false;
6576
6577
            // Grouping questions per parent category see BT#6540
6578
6579
            if (in_array(
6580
                $selectionType,
6581
                array(
6582
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
6583
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM
6584
                )
6585
            )) {
6586
                $useRootAsCategoryTitle = true;
6587
            }
6588
6589
            // If the exercise is set to only show the titles of the categories
6590
            // at the root of the tree, then pre-order the categories tree by
6591
            // removing children and summing their questions into the parent
6592
            // categories
6593
6594
            if ($useRootAsCategoryTitle) {
6595
                // The new categories list starts empty
6596
                $newCategoryList = array();
6597
                foreach ($categories as $category) {
6598
                    $rootElement = $category['root'];
6599
6600
                    if (isset($category['parent_info'])) {
6601
                        $rootElement = $category['parent_info']['id'];
6602
                    }
6603
6604
                    //$rootElement = $category['id'];
6605
                    // If the current category's ancestor was never seen
6606
                    // before, then declare it and assign the current
6607
                    // category to it.
6608
                    if (!isset($newCategoryList[$rootElement])) {
6609
                        $newCategoryList[$rootElement] = $category;
6610
                    } else {
6611
                        // If it was already seen, then merge the previous with
6612
                        // the current category
6613
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
6614
                        $category['question_list'] = array_merge($oldQuestionList , $category['question_list']);
6615
                        $newCategoryList[$rootElement] = $category;
6616
                    }
6617
                }
6618
                // Now use the newly built categories list, with only parents
6619
                $categories = $newCategoryList;
6620
            }
6621
6622
            foreach ($categories as $category) {
6623
                $questionList = $category['question_list'];
6624
                // Check if in this category there questions added in a media
6625
                $mediaQuestionId = $category['media_question'];
6626
                $isMedia = false;
6627
                $fixedValue = null;
6628
6629
                // Media exists!
6630
                if ($mediaQuestionId != 999) {
6631
                    $isMedia = true;
6632
                    $fixedValue = $counterNoMedias;
6633
                }
6634
6635
                //$categoryName = $category['path']; << show the path
6636
                $categoryName = $category['name'];
6637
6638
                if ($useRootAsCategoryTitle) {
6639
                    if (isset($category['parent_info'])) {
6640
                        $categoryName  = $category['parent_info']['title'];
6641
                    }
6642
                }
6643
                $html .= '<div class="row">';
6644
                $html .= '<div class="span2">'.$categoryName.'</div>';
6645
                $html .= '<div class="span8">';
6646
6647
                if (!empty($nextValue)) {
6648
                    if ($wasMedia) {
6649
                        $nextValue = $nextValue - $before + 1;
6650
                    }
6651
                }
6652
                $html .= Display::progressPaginationBar(
6653
                    $nextValue,
6654
                    $questionList,
6655
                    $current,
6656
                    $fixedValue,
6657
                    $conditions,
6658
                    $link,
6659
                    $isMedia,
6660
                    true
6661
                );
6662
                $html .= '</div>';
6663
                $html .= '</div>';
6664
6665
                if ($mediaQuestionId == 999) {
6666
                    $counterNoMedias += count($questionList);
6667
                } else {
6668
                    $counterNoMedias++;
6669
                }
6670
6671
                $nextValue += count($questionList);
6672
                $before = count($questionList);
6673
6674
                if ($mediaQuestionId != 999) {
6675
                    $wasMedia = true;
6676
                } else {
6677
                    $wasMedia = false;
6678
                }
6679
6680
            }
6681
        }
6682
        return $html;
6683
    }
6684
6685
    /**
6686
     * Renders a question list
6687
     *
6688
     * @param array $questionList (with media questions compressed)
6689
     * @param int $currentQuestion
6690
     * @param array $exerciseResult
6691
     * @param array $attemptList
6692
     * @param array $remindList
6693
     */
6694
    public function renderQuestionList($questionList, $currentQuestion, $exerciseResult, $attemptList, $remindList)
6695
    {
6696
        $mediaQuestions = $this->getMediaList();
6697
        $i = 0;
6698
6699
        // Normal question list render (medias compressed)
6700
        foreach ($questionList as $questionId) {
6701
            $i++;
6702
            // For sequential exercises
6703
6704
            if ($this->type == ONE_PER_PAGE) {
6705
                // If it is not the right question, goes to the next loop iteration
6706
                if ($currentQuestion != $i) {
6707
                    continue;
6708
                } else {
6709
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
6710
                        // if the user has already answered this question
6711
                        if (isset($exerciseResult[$questionId])) {
6712
                            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...
6713
                            break;
6714
                        }
6715
                    }
6716
                }
6717
            }
6718
6719
            // The $questionList contains the media id we check if this questionId is a media question type
6720
6721
            if (isset($mediaQuestions[$questionId]) && $mediaQuestions[$questionId] != 999) {
6722
6723
                // The question belongs to a media
6724
                $mediaQuestionList = $mediaQuestions[$questionId];
6725
                $objQuestionTmp = Question::read($questionId);
6726
6727
                $counter = 1;
6728
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
6729
                    echo $objQuestionTmp->show_media_content();
6730
6731
                    $countQuestionsInsideMedia = count($mediaQuestionList);
6732
6733
                    // Show questions that belongs to a media
6734
                    if (!empty($mediaQuestionList)) {
6735
                        // In order to parse media questions we use letters a, b, c, etc.
6736
                        $letterCounter = 97;
6737
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
6738
                            $isLastQuestionInMedia = false;
6739
                            if ($counter == $countQuestionsInsideMedia) {
6740
                                $isLastQuestionInMedia = true;
6741
                            }
6742
                            $this->renderQuestion(
6743
                                $questionIdInsideMedia,
6744
                                $attemptList,
6745
                                $remindList,
6746
                                chr($letterCounter),
6747
                                $currentQuestion,
6748
                                $mediaQuestionList,
6749
                                $isLastQuestionInMedia,
6750
                                $questionList
6751
                            );
6752
                            $letterCounter++;
6753
                            $counter++;
6754
                        }
6755
                    }
6756
                } else {
6757
                    $this->renderQuestion(
6758
                        $questionId,
6759
                        $attemptList,
6760
                        $remindList,
6761
                        $i,
6762
                        $currentQuestion,
6763
                        null,
6764
                        null,
6765
                        $questionList
6766
                    );
6767
                    $i++;
6768
                }
6769
            } else {
6770
                // Normal question render.
6771
                $this->renderQuestion($questionId, $attemptList, $remindList, $i, $currentQuestion, null, null, $questionList);
6772
            }
6773
6774
            // For sequential exercises.
6775
            if ($this->type == ONE_PER_PAGE) {
6776
                // quits the loop
6777
                break;
6778
            }
6779
        }
6780
        // end foreach()
6781
6782
        if ($this->type == ALL_ON_ONE_PAGE) {
6783
            $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 6700. 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...
6784
            echo Display::div($exercise_actions, array('class'=>'exercise_actions'));
6785
        }
6786
    }
6787
6788
    /**
6789
     * @param int $questionId
6790
     * @param array $attemptList
6791
     * @param array $remindList
6792
     * @param int $i
6793
     * @param int $current_question
6794
     * @param array $questions_in_media
6795
     * @param bool $last_question_in_media
6796
     * @param array $realQuestionList
6797
     * @param bool $generateJS
6798
     * @return null
6799
     */
6800
    public function renderQuestion(
6801
        $questionId,
6802
        $attemptList,
6803
        $remindList,
6804
        $i,
6805
        $current_question,
6806
        $questions_in_media = array(),
6807
        $last_question_in_media = false,
6808
        $realQuestionList,
6809
        $generateJS = true
6810
    ) {
6811
6812
        // With this option on the question is loaded via AJAX
6813
        //$generateJS = true;
6814
        //$this->loadQuestionAJAX = true;
6815
6816
        if ($generateJS && $this->loadQuestionAJAX) {
6817
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
6818
            $params = array(
6819
                'questionId' => $questionId,
6820
                'attemptList'=> $attemptList,
6821
                'remindList' => $remindList,
6822
                'i' => $i,
6823
                'current_question' => $current_question,
6824
                'questions_in_media' => $questions_in_media,
6825
                'last_question_in_media' => $last_question_in_media
6826
            );
6827
            $params = json_encode($params);
6828
6829
            $script = '<script>
6830
            $(function(){
6831
                var params = '.$params.';
6832
                $.ajax({
6833
                    type: "GET",
6834
                    async: false,
6835
                    data: params,
6836
                    url: "'.$url.'",
6837
                    success: function(return_value) {
6838
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
6839
                    }
6840
                });
6841
            });
6842
            </script>
6843
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
6844
            echo $script;
6845
        } else {
6846
6847
            global $origin;
6848
            $question_obj = Question::read($questionId);
6849
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
6850
6851
            $remind_highlight = null;
6852
6853
            //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
6854
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
6855
                $remind_highlight = 'no_remind_highlight';
6856
                if (in_array($question_obj->type, Question::question_type_no_review())) {
6857
                    return null;
6858
                }
6859
            }
6860
6861
            $attributes = array('id' =>'remind_list['.$questionId.']');
6862
            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...
6863
                //$attributes['checked'] = 1;
6864
                //$remind_highlight = ' remind_highlight ';
6865
            }
6866
6867
            // Showing the question
6868
6869
            $exercise_actions  = null;
6870
6871
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
6872
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
6873
6874
            // Shows the question + possible answers
6875
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
6876
            echo $this->showQuestion($question_obj, false, $origin, $i, $showTitle, false, $user_choice, false, null, false, $this->getModelType(), $this->categoryMinusOne);
6877
6878
            // Button save and continue
6879 View Code Duplication
            switch ($this->type) {
6880
                case ONE_PER_PAGE:
6881
                    $exercise_actions .= $this->show_button($questionId, $current_question, null, $remindList);
6882
                    break;
6883
                case ALL_ON_ONE_PAGE:
6884
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', null, true, 1); ">'.get_lang('SaveForNow').'</a>';
6885
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6886
                    $exercise_actions .= Display::div($button, array('class'=>'exercise_save_now_button'));
6887
                    break;
6888
            }
6889
6890
            if (!empty($questions_in_media)) {
6891
                $count_of_questions_inside_media = count($questions_in_media);
6892
                if ($count_of_questions_inside_media > 1) {
6893
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', false, false, 0); ">'.get_lang('SaveForNow').'</a>';
6894
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6895
                    $exercise_actions = Display::div($button, array('class'=>'exercise_save_now_button'));
6896
                }
6897
6898
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
6899
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
6900
                }
6901
            }
6902
6903
            // Checkbox review answers
6904
            if ($this->review_answers && !in_array($question_obj->type, Question::question_type_no_review())) {
6905
                $remind_question_div = Display::tag('label', Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), array('class' => 'checkbox', 'for' =>'remind_list['.$questionId.']'));
6906
                $exercise_actions   .= Display::div($remind_question_div, array('class'=>'exercise_save_now_button'));
6907
            }
6908
6909
            echo Display::div(' ', array('class'=>'clear'));
6910
6911
            $paginationCounter = null;
6912
            if ($this->type == ONE_PER_PAGE) {
6913
                if (empty($questions_in_media)) {
6914
                    $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6915
                } else {
6916
                    if ($last_question_in_media) {
6917
                        $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6918
                    }
6919
                }
6920
            }
6921
6922
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
6923
            echo Display::div($exercise_actions, array('class'=>'form-actions'));
6924
            echo '</div>';
6925
        }
6926
    }
6927
6928
    /**
6929
     * Shows a question
6930
     * @param Question $objQuestionTmp
6931
     * @param bool $only_questions if true only show the questions, no exercise title
6932
     * @param bool $origin origin i.e = learnpath
6933
     * @param string $current_item current item from the list of questions
6934
     * @param bool $show_title
6935
     * @param bool $freeze
6936
     * @param array $user_choice
6937
     * @param bool $show_comment
6938
     * @param null $exercise_feedback
6939
     * @param bool $show_answers
6940
     * @param null $modelType
6941
     * @param bool $categoryMinusOne
6942
     * @return bool|null|string
6943
     */
6944
    public function showQuestion(
6945
        Question $objQuestionTmp,
6946
        $only_questions = false,
6947
        $origin = false,
6948
        $current_item = '',
6949
        $show_title = true,
6950
        $freeze = false,
6951
        $user_choice = array(),
6952
        $show_comment = false,
6953
        $exercise_feedback = null,
6954
        $show_answers = false,
6955
        $modelType = null,
6956
        $categoryMinusOne = true
6957
    ) {
6958
        // Text direction for the current language
6959
        //$is_ltr_text_direction = api_get_text_direction() != 'rtl';
6960
        // Change false to true in the following line to enable answer hinting
6961
        $debug_mark_answer = $show_answers; //api_is_allowed_to_edit() && false;
6962
        // Reads question information
6963
        if (!$objQuestionTmp) {
6964
            // Question not found
6965
            return false;
6966
        }
6967
6968
        $html = null;
6969
6970
        $questionId = $objQuestionTmp->id;
6971
6972
        if ($exercise_feedback != EXERCISE_FEEDBACK_TYPE_END) {
6973
            $show_comment = false;
6974
        }
6975
6976
        $answerType = $objQuestionTmp->selectType();
6977
        $pictureName = $objQuestionTmp->selectPicture();
6978
6979
        $s = null;
6980
        $form = new FormValidator('question');
6981
        $renderer = $form->defaultRenderer();
6982
        $form_template = '{content}';
6983
        $renderer->setFormTemplate($form_template);
6984
6985
        if ($answerType != HOT_SPOT && $answerType != HOT_SPOT_DELINEATION) {
6986
            // Question is not a hotspot
6987
            if (!$only_questions) {
6988
                $questionDescription = $objQuestionTmp->selectDescription();
6989
                if ($show_title) {
6990
                    $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...
6991
                    $html .= $categoryName;
6992
                    $html .= Display::div($current_item.'. '.$objQuestionTmp->selectTitle(), array('class' => 'question_title'));
6993
                    if (!empty($questionDescription)) {
6994
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
6995
                    }
6996 View Code Duplication
                } else {
6997
                    $html .= '<div class="media">';
6998
                    $html .= '<div class="pull-left">';
6999
                    $html .= '<div class="media-object">';
7000
                    $html .= Display::div($current_item, array('class' => 'question_no_title'));
7001
                    $html .= '</div>';
7002
                    $html .= '</div>';
7003
                    $html .= '<div class="media-body">';
7004
                    if (!empty($questionDescription)) {
7005
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
7006
                    }
7007
                    $html .= '</div>';
7008
                    $html .= '</div>';
7009
                }
7010
            }
7011
7012
            if (in_array($answerType, array(FREE_ANSWER, ORAL_EXPRESSION)) && $freeze) {
7013
                return null;
7014
            }
7015
7016
            $html .= '<div class="question_options">';
7017
            // construction of the Answer object (also gets all answers details)
7018
            $objAnswerTmp = new Answer($questionId, null, $this);
7019
7020
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
7021
            $course_id = api_get_course_int_id();
7022
            $sessionId = api_get_session_id();
7023
            $quiz_question_options = Question::readQuestionOption($questionId, $course_id);
7024
7025
            // For "matching" type here, we need something a little bit special
7026
            // because the match between the suggestions and the answers cannot be
7027
            // done easily (suggestions and answers are in the same table), so we
7028
            // have to go through answers first (elems with "correct" value to 0).
7029
            $select_items = array();
7030
            //This will contain the number of answers on the left side. We call them
7031
            // suggestions here, for the sake of comprehensions, while the ones
7032
            // on the right side are called answers
7033
            $num_suggestions = 0;
7034
7035
            if ($answerType == MATCHING || $answerType == DRAGGABLE) {
7036 View Code Duplication
                if ($answerType == DRAGGABLE) {
7037
                    $s .= '<div class="ui-widget ui-helper-clearfix">
7038
                            <ul class="drag_question ui-helper-reset ui-helper-clearfix">';
7039
                } else {
7040
                    $s .= '<div id="drag'.$questionId.'_question" class="drag_question">';
7041
                    $s .= '<table class="data_table">';
7042
                }
7043
7044
                $j = 1; //iterate through answers
7045
                $letter = 'A'; //mark letters for each answer
7046
                $answer_matching = array();
7047
                $capital_letter = array();
7048
                //for ($answerId=1; $answerId <= $nbrAnswers; $answerId++) {
7049
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7050
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7051
                    $answer = $objAnswerTmp->selectAnswer($answerId);
7052
                    if ($answerCorrect == 0) {
7053
                        // options (A, B, C, ...) that will be put into the list-box
7054
                        // have the "correct" field set to 0 because they are answer
7055
                        $capital_letter[$j] = $letter;
7056
                        //$answer_matching[$j]=$objAnswerTmp->selectAnswerByAutoId($numAnswer);
7057
                        $answer_matching[$j] = array('id' => $answerId, 'answer' => $answer);
7058
                        $j++;
7059
                        $letter++;
7060
                    }
7061
                }
7062
7063
                $i = 1;
7064
7065
                $select_items[0]['id'] = 0;
7066
                $select_items[0]['letter'] = '--';
7067
                $select_items[0]['answer'] = '';
7068
7069 View Code Duplication
                foreach ($answer_matching as $id => $value) {
7070
                    $select_items[$i]['id'] = $value['id'];
7071
                    $select_items[$i]['letter'] = $capital_letter[$id];
7072
                    $select_items[$i]['answer'] = $value['answer'];
7073
                    $i++;
7074
                }
7075
                $num_suggestions = ($nbrAnswers - $j) + 1;
7076
            } elseif ($answerType == FREE_ANSWER) {
7077
                $content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
7078
                $toolBar = 'TestFreeAnswer';
7079
                if ($modelType == EXERCISE_MODEL_TYPE_COMMITTEE) {
7080
                    $toolBar = 'TestFreeAnswerStrict';
7081
                }
7082
                $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => $toolBar));
7083
                $form->setDefaults(array("choice[".$questionId."]" => $content));
7084
                $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...
7085
            } elseif ($answerType == ORAL_EXPRESSION) {
7086
                // Add nanogong
7087 View Code Duplication
                if (api_get_setting('enable_record_audio') === 'true') {
7088
7089
                    //@todo pass this as a parameter
7090
                    global $exercise_stat_info, $exerciseId;
7091
7092
                    if (!empty($exercise_stat_info)) {
7093
                        $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...
7094
                            api_get_session_id(),
7095
                            api_get_user_id(),
7096
                            $exercise_stat_info['exe_exo_id'],
7097
                            $exercise_stat_info['exe_id']
7098
                        );
7099
                    } else {
7100
                        $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...
7101
                            api_get_session_id(),
7102
                            api_get_user_id(),
7103
                            $exerciseId,
7104
                            'temp_exe'
7105
                        );
7106
                    }
7107
7108
                    $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...
7109
                }
7110
7111
                $form->addElement(
7112
                    'html_editor',
7113
                    "choice[".$questionId."]",
7114
                    null,
7115
                    array('id' => "choice[".$questionId."]"),
7116
                    array('ToolbarSet' => 'TestFreeAnswer')
7117
                );
7118
                //$form->setDefaults(array("choice[".$questionId."]" => $content));
7119
                $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...
7120
            }
7121
7122
            // Now navigate through the possible answers, using the max number of
7123
            // answers for the question as a limiter
7124
            $lines_count = 1; // a counter for matching-type answers
7125
7126
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7127
                $header = Display::tag('th', get_lang('Options'));
7128
                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...
7129
                    $header .= Display::tag('th', $item);
7130
                }
7131
                if ($show_comment) {
7132
                    $header .= Display::tag('th', get_lang('Feedback'));
7133
                }
7134
                $s .= '<table class="data_table">';
7135
                $s .= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7136
            }
7137
7138 View Code Duplication
            if ($show_comment) {
7139
                if (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_COMBINATION, UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION, GLOBAL_MULTIPLE_ANSWER))) {
7140
                    $header = Display::tag('th', get_lang('Options'));
7141
                    if ($exercise_feedback == EXERCISE_FEEDBACK_TYPE_END) {
7142
                        $header .= Display::tag('th', get_lang('Feedback'));
7143
                    }
7144
                    $s .= '<table class="data_table">';
7145
                    $s.= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7146
                }
7147
            }
7148
7149
            $matching_correct_answer = 0;
7150
            $user_choice_array = array();
7151
            if (!empty($user_choice)) {
7152
                foreach ($user_choice as $item) {
7153
                    $user_choice_array[] = $item['answer'];
7154
                }
7155
            }
7156
7157
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7158
                $answer = $objAnswerTmp->selectAnswer($answerId);
7159
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7160
                $comment = $objAnswerTmp->selectComment($answerId);
7161
7162
                //$numAnswer       = $objAnswerTmp->selectAutoId($answerId);
7163
                $numAnswer = $answerId;
7164
7165
                $attributes = array();
7166
                // Unique answer
7167
                if (in_array($answerType, array(UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION))) {
7168
7169
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7170
                    if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
7171
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7172
                    } else {
7173
                        $attributes = array('id' => $input_id);
7174
                    }
7175
7176
                    if ($debug_mark_answer) {
7177
                        if ($answerCorrect) {
7178
                            $attributes['checked'] = 1;
7179
                            $attributes['selected'] = 1;
7180
                        }
7181
                    }
7182
7183
                    $answer = Security::remove_XSS($answer);
7184
                    $s .= Display::input('hidden', 'choice2['.$questionId.']', '0');
7185
7186
                    $answer_input = null;
7187
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7188
                        $attributes['style'] = 'display:none';
7189
                        $answer_input .= '<div id="answer'.$questionId.$numAnswer.'" style="float:left" class="highlight_image_default highlight_image">';
7190
                    }
7191
7192
                    $answer_input .= '<label class="radio">';
7193
                    $answer_input .= Display::input('radio', 'choice['.$questionId.']', $numAnswer, $attributes);
7194
                    $answer_input .= $answer;
7195
                    $answer_input .= '</label>';
7196
7197
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7198
                        $answer_input .= "</div>";
7199
                    }
7200
7201
                    if ($show_comment) {
7202
                        $s .= '<tr><td>';
7203
                        $s .= $answer_input;
7204
                        $s .= '</td>';
7205
                        $s .= '<td>';
7206
                        $s .= $comment;
7207
                        $s .= '</td>';
7208
                        $s .= '</tr>';
7209
                    } else {
7210
                        $s .= $answer_input;
7211
                    }
7212
7213
                } elseif (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_TRUE_FALSE, GLOBAL_MULTIPLE_ANSWER))) {
7214
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7215
                    $answer = Security::remove_XSS($answer);
7216
7217
                    if (in_array($numAnswer, $user_choice_array)) {
7218
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7219
                    } else {
7220
                        $attributes = array('id' => $input_id);
7221
                    }
7222
7223
                    if ($debug_mark_answer) {
7224
                        if ($answerCorrect) {
7225
                            $attributes['checked'] = 1;
7226
                            $attributes['selected'] = 1;
7227
                        }
7228
                    }
7229
7230 View Code Duplication
                    if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
7231
                        $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7232
7233
                        $answer_input = '<label class="checkbox">';
7234
                        $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', $numAnswer, $attributes);
7235
                        $answer_input .= $answer;
7236
                        $answer_input .= '</label>';
7237
7238
                        if ($show_comment) {
7239
                            $s .= '<tr><td>';
7240
                            $s .= $answer_input;
7241
                            $s .= '</td>';
7242
                            $s .= '<td>';
7243
                            $s .= $comment;
7244
                            $s .= '</td>';
7245
                            $s .='</tr>';
7246
                        } else {
7247
                            $s .= $answer_input;
7248
                        }
7249
                    } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
7250
7251
                        $my_choice = array();
7252
                        if (!empty($user_choice_array)) {
7253
                            foreach ($user_choice_array as $item) {
7254
                                $item = explode(':', $item);
7255
                                $my_choice[$item[0]] = $item[1];
7256
                            }
7257
                        }
7258
7259
                        $s .='<tr>';
7260
                        $s .= Display::tag('td', $answer);
7261
7262
                        if (!empty($quiz_question_options)) {
7263
                            foreach ($quiz_question_options as $id => $item) {
7264
                                $id = $item['iid'];
7265
                                if (isset($my_choice[$numAnswer]) && $id == $my_choice[$numAnswer]) {
7266
                                    $attributes = array('checked' => 1, 'selected' => 1);
7267
                                } else {
7268
                                    $attributes = array();
7269
                                }
7270
7271
                                if ($debug_mark_answer) {
7272
                                    if ($id == $answerCorrect) {
7273
                                        $attributes['checked'] = 1;
7274
                                        $attributes['selected'] = 1;
7275
                                    }
7276
                                }
7277
                                $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $id, $attributes), array('style' => ''));
7278
                            }
7279
                        }
7280
7281
                        if ($show_comment) {
7282
                            $s .= '<td>';
7283
                            $s .= $comment;
7284
                            $s .= '</td>';
7285
                        }
7286
                        $s.='</tr>';
7287
                    }
7288
7289
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
7290
7291
                    // multiple answers
7292
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7293
7294
                    if (in_array($numAnswer, $user_choice_array)) {
7295
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7296
                    } else {
7297
                        $attributes = array('id' => $input_id);
7298
                    }
7299
7300
                    if ($debug_mark_answer) {
7301
                        if ($answerCorrect) {
7302
                            $attributes['checked'] = 1;
7303
                            $attributes['selected'] = 1;
7304
                        }
7305
                    }
7306
7307
                    $answer = Security::remove_XSS($answer);
7308
                    $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7309
                    $answer_input .= '<label class="checkbox">';
7310
                    $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', 1, $attributes);
7311
                    $answer_input .= $answer;
7312
                    $answer_input .= '</label>';
7313
7314
                    if ($show_comment) {
7315
                        $s.= '<tr>';
7316
                        $s .= '<td>';
7317
                        $s.= $answer_input;
7318
                        $s .= '</td>';
7319
                        $s .= '<td>';
7320
                        $s .= $comment;
7321
                        $s .= '</td>';
7322
                        $s.= '</tr>';
7323
                    } else {
7324
                        $s.= $answer_input;
7325
                    }
7326
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7327
                    $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7328
7329
                    $my_choice = array();
7330
                    if (!empty($user_choice_array)) {
7331
                        foreach ($user_choice_array as $item) {
7332
                            $item = explode(':', $item);
7333
                            $my_choice[$item[0]] = $item[1];
7334
                        }
7335
                    }
7336
                    $answer = Security::remove_XSS($answer);
7337
                    $s .='<tr>';
7338
                    $s .= Display::tag('td', $answer);
7339
7340
                    foreach ($objQuestionTmp->options as $key => $item) {
7341
                        if (isset($my_choice[$numAnswer]) && $key == $my_choice[$numAnswer]) {
7342
                            $attributes = array('checked' => 1, 'selected' => 1);
7343
                        } else {
7344
                            $attributes = array();
7345
                        }
7346
7347
                        if ($debug_mark_answer) {
7348
                            if ($key == $answerCorrect) {
7349
                                $attributes['checked'] = 1;
7350
                                $attributes['selected'] = 1;
7351
                            }
7352
                        }
7353
                        $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $key, $attributes));
7354
                    }
7355
7356
                    if ($show_comment) {
7357
                        $s .= '<td>';
7358
                        $s .= $comment;
7359
                        $s .= '</td>';
7360
                    }
7361
                    $s.='</tr>';
7362
                } elseif ($answerType == FILL_IN_BLANKS) {
7363
                    list($answer) = explode('::', $answer);
7364
7365
                    //Correct answer
7366
                    api_preg_match_all('/\[[^]]+\]/', $answer, $correct_answer_list);
7367
7368
                    //Student's answezr
7369
                    if (isset($user_choice[0]['answer'])) {
7370
                        api_preg_match_all('/\[[^]]+\]/', $user_choice[0]['answer'], $student_answer_list);
7371
                        $student_answer_list = $student_answer_list[0];
7372
                    }
7373
7374
                    //If debug
7375
                    if ($debug_mark_answer) {
7376
                        $student_answer_list = $correct_answer_list[0];
7377
                    }
7378
7379
                    if (!empty($correct_answer_list) && !empty($student_answer_list)) {
7380
                        $correct_answer_list = $correct_answer_list[0];
7381
                        $i = 0;
7382
                        foreach ($correct_answer_list as $correct_item) {
7383
                            $value = null;
7384
                            if (isset($student_answer_list[$i]) && !empty($student_answer_list[$i])) {
7385
7386
                                //Cleaning student answer list
7387
                                $value = strip_tags($student_answer_list[$i]);
7388
                                $value = api_substr($value, 1, api_strlen($value) - 2);
7389
                                $value = explode('/', $value);
7390
7391
                                if (!empty($value[0])) {
7392
                                    $value = str_replace('&nbsp;', '', trim($value[0]));
7393
                                }
7394
                                $correct_item = preg_quote($correct_item);
7395
                                $correct_item = api_preg_replace('|/|', '\/', $correct_item);   // to prevent error if there is a / in the text to find
7396
                                $answer = api_preg_replace('/'.$correct_item.'/', Display::input('text', "choice[$questionId][]", $value), $answer, 1);
7397
                            }
7398
                            $i++;
7399
                        }
7400
                    } else {
7401
                        $answer = api_preg_replace('/\[[^]]+\]/', Display::input('text', "choice[$questionId][]", '', $attributes), $answer);
7402
                    }
7403
                    $s .= $answer;
7404
                } elseif ($answerType == MATCHING) {
7405
                    // matching type, showing suggestions and answers
7406
                    // TODO: replace $answerId by $numAnswer
7407
7408
                    if ($lines_count == 1) {
7409
                        $s .= $objAnswerTmp->getJs();
7410
                    }
7411
                    if ($answerCorrect != 0) {
7412
                        // only show elements to be answered (not the contents of
7413
                        // the select boxes, who are correct = 0)
7414
                        $s .= '<tr><td width="45%">';
7415
                        $parsed_answer = $answer;
7416
                        $windowId = $questionId.'_'.$lines_count;
7417
                        //left part questions
7418
                        $s .= ' <div id="window_'.$windowId.'" class="window window_left_question window'.$questionId.'_question">
7419
                                    <b>'.$lines_count.'</b>.&nbsp'.$parsed_answer.'
7420
                                </div>
7421
                                </td>';
7422
7423
                        // middle part (matches selects)
7424
7425
                        $s .= '<td width="10%" align="center">&nbsp;&nbsp;';
7426
                        $s .= '<div style="display:block">';
7427
7428
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']">';
7429
                        $selectedValue = 0;
7430
                        // fills the list-box
7431
                        $item = 0;
7432 View Code Duplication
                        foreach ($select_items as $val) {
7433
                            // set $debug_mark_answer to true at public static function start to
7434
                            // show the correct answer with a suffix '-x'
7435
                            $selected = '';
7436
                            if ($debug_mark_answer) {
7437
                                if ($val['id'] == $answerCorrect) {
7438
                                    $selected = 'selected="selected"';
7439
                                    $selectedValue = $val['id'];
7440
                                }
7441
                            }
7442
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7443
                                $selected = 'selected="selected"';
7444
                                $selectedValue = $val['id'];
7445
                            }
7446
                            //$s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
7447
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7448
                            $item++;
7449
                        }
7450
7451 View Code Duplication
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7452
                            $s.= '<script>
7453
                                jsPlumb.ready(function() {
7454
                                    jsPlumb.connect({
7455
                                        source: "window_'.$windowId.'",
7456
                                        target: "window_'.$questionId.'_'.$selectedValue.'_answer",
7457
                                        endpoint:["Blank", { radius:15 }],
7458
                                        anchor:["RightMiddle","LeftMiddle"],
7459
                                        paintStyle:{ strokeStyle:"#8a8888" , lineWidth:8 },
7460
                                        connector: [connectorType, { curviness: curvinessValue } ],
7461
                                    })
7462
                                });
7463
                                </script>';
7464
                        }
7465
                        $s .= '</select></div></td>';
7466
7467
                        $s.='<td width="45%" valign="top" >';
7468
7469 View Code Duplication
                        if (isset($select_items[$lines_count])) {
7470
                            $s.= '<div id="window_'.$windowId.'_answer" class="window window_right_question">
7471
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7472
                                  </div>';
7473
                        } else {
7474
                            $s.='&nbsp;';
7475
                        }
7476
7477
                        $s .= '</td>';
7478
                        $s .= '</tr>';
7479
                        $lines_count++;
7480
                        //if the left side of the "matching" has been completely
7481
                        // shown but the right side still has values to show...
7482
                        if (($lines_count - 1) == $num_suggestions) {
7483
                            // if it remains answers to shown at the right side
7484 View Code Duplication
                            while (isset($select_items[$lines_count])) {
7485
                                $s .= '<tr>
7486
                                      <td colspan="2"></td>
7487
                                      <td valign="top">';
7488
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7489
                                $s .= $select_items[$lines_count]['answer'];
7490
                                $s.="</td>
7491
                                </tr>";
7492
                                $lines_count++;
7493
                            } // end while()
7494
                        }  // end if()
7495
                        $matching_correct_answer++;
7496
                    }
7497
                } elseif ($answerType ==  DRAGGABLE) {
7498
                    // matching type, showing suggestions and answers
7499
                    // TODO: replace $answerId by $numAnswer
7500
7501
                    if ($answerCorrect != 0) {
7502
                        // only show elements to be answered (not the contents of
7503
                        // the select boxes, who are correct = 0)
7504
                        $s .= '<td>';
7505
                        $parsed_answer = $answer;
7506
                        $windowId = $questionId.'_'.$numAnswer; //67_293 - 67_294
7507
7508
                        //left part questions
7509
                        $s .= '<li class="ui-state-default" id="'.$windowId.'">';
7510
                        $s .= ' <div id="window_'.$windowId.'" class="window'.$questionId.'_question_draggable question_draggable">
7511
                                   '.$parsed_answer.'
7512
                                </div>';
7513
7514
                        $s .= '<div style="display:none">';
7515
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']" class="select_option">';
7516
                        $selectedValue = 0;
7517
                        // fills the list-box
7518
                        $item = 0;
7519 View Code Duplication
                        foreach ($select_items as $val) {
7520
                            // set $debug_mark_answer to true at function start to
7521
                            // show the correct answer with a suffix '-x'
7522
                            $selected = '';
7523
                            if ($debug_mark_answer) {
7524
                                if ($val['id'] == $answerCorrect) {
7525
                                    $selected = 'selected="selected"';
7526
                                    $selectedValue = $val['id'];
7527
                                }
7528
                            }
7529
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7530
                                $selected = 'selected="selected"';
7531
                                $selectedValue = $val['id'];
7532
                            }
7533
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7534
                            $item++;
7535
                        }
7536
                        $s .= '</select>';
7537
7538 View Code Duplication
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7539
                            $s.= '<script>
7540
                                $(function() {
7541
                                    deleteItem($("#'.$questionId.'_'.$selectedValue.'"), $("#drop_'.$windowId.'"));
7542
                                });
7543
                                </script>';
7544
                        }
7545
7546 View Code Duplication
                        if (isset($select_items[$lines_count])) {
7547
                            $s.= '<div id="window_'.$windowId.'_answer" class="">
7548
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7549
                                  </div>';
7550
                        } else {
7551
                            $s.='&nbsp;';
7552
                        }
7553
                        $lines_count++;
7554
                        //if the left side of the "matching" has been completely
7555
                        // shown but the right side still has values to show...
7556
7557 View Code Duplication
                        if (($lines_count - 1) == $num_suggestions) {
7558
                            // if it remains answers to shown at the right side
7559
                            while (isset($select_items[$lines_count])) {
7560
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7561
                                $s .= $select_items[$lines_count]['answer'];
7562
                                $lines_count++;
7563
                            }
7564
                        }
7565
                        $s .= '</div>';
7566
                        $matching_correct_answer++;
7567
                        $s .= '</li>';
7568
                    }
7569
                }
7570
            } // end for()
7571
7572
            if ($show_comment) {
7573
                $s .= '</table>';
7574
            } else {
7575
                if ($answerType == MATCHING || $answerType == UNIQUE_ANSWER_NO_OPTION || $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
7576
                    $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7577
                    $s .= '</table>';
7578
                }
7579
            }
7580
7581
            if ($answerType == DRAGGABLE) {
7582
                $s .= '</ul><div class="clear"></div>';
7583
7584
                $counterAnswer = 1;
7585
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7586
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7587
                    $windowId = $questionId.'_'.$counterAnswer;
7588
                    if ($answerCorrect == 0) {
7589
                        $s .= '<div id="drop_'.$windowId.'" class="droppable ui-state-default">'.$counterAnswer.'</div>';
7590
                        $counterAnswer++;
7591
                    }
7592
                }
7593
            }
7594
7595
            if ($answerType == MATCHING) {
7596
                $s .= '</div>';
7597
            }
7598
7599
            $s .= '</div>';
7600
7601
            // destruction of the Answer object
7602
            unset($objAnswerTmp);
7603
7604
            // destruction of the Question object
7605
            unset($objQuestionTmp);
7606
7607
            $html .= $s;
7608
            return $html;
7609
        } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
7610
            // Question is a HOT_SPOT
7611
            //checking document/images visibility
7612 View Code Duplication
            if (api_is_platform_admin() || api_is_course_admin()) {
7613
                $course = api_get_course_info();
7614
                $doc_id = DocumentManager::get_document_id($course, '/images/'.$pictureName);
7615
                if (is_numeric($doc_id)) {
7616
                    $images_folder_visibility = api_get_item_visibility($course, 'document', $doc_id, api_get_session_id());
7617
                    if (!$images_folder_visibility) {
7618
                        //This message is shown only to the course/platform admin if the image is set to visibility = false
7619
                        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...
7620
                    }
7621
                }
7622
            }
7623
            $questionName = $objQuestionTmp->selectTitle();
7624
            $questionDescription = $objQuestionTmp->selectDescription();
7625
7626
            if ($freeze) {
7627
                $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...
7628
                $html .= $s;
7629
                return $html;
7630
            }
7631
7632
            // Get the answers, make a list
7633
            $objAnswerTmp = new Answer($questionId);
7634
7635
            // get answers of hotpost
7636
            $answers_hotspot = array();
7637
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7638
                //$answers = $objAnswerTmp->selectAnswerByAutoId($objAnswerTmp->selectAutoId($answerId));
7639
                $answers_hotspot[$answerId] = $objAnswerTmp->selectAnswer($answerId);
7640
            }
7641
7642
            // display answers of hotpost order by id
7643
            $answer_list = '<div style="padding: 10px; margin-left: 0px; border: 1px solid #A4A4A4; height: 408px; width: 200px;"><b>'.get_lang('HotspotZones').'</b><dl>';
7644
            if (!empty($answers_hotspot)) {
7645
                ksort($answers_hotspot);
7646
                foreach ($answers_hotspot as $key => $value) {
7647
                    $answer_list .= '<dt>'.$key.'.- '.$value.'</dt><br />';
7648
                }
7649
            }
7650
            $answer_list .= '</dl></div>';
7651
7652
            if ($answerType == HOT_SPOT_DELINEATION) {
7653
                $answer_list = '';
7654
                $swf_file = 'hotspot_delineation_user';
7655
                $swf_height = 405;
7656
            } else {
7657
                $swf_file = 'hotspot_user';
7658
                $swf_height = 436;
7659
            }
7660
7661
            if (!$only_questions) {
7662
                if ($show_title) {
7663
                    $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...
7664
                    $html .=  '<div class="question_title">'.$current_item.'. '.$questionName.'</div>';
7665
                    $html .=  $questionDescription;
7666 View Code Duplication
                } else {
7667
                    $html .= '<div class="media">';
7668
                    $html .= '<div class="pull-left">';
7669
                    $html .= '<div class="media-object">';
7670
                    $html .= Display::div($current_item.'. ', array('class' => 'question_no_title'));
7671
                    $html .= '</div>';
7672
                    $html .= '</div>';
7673
                    $html .= '<div class="media-body">';
7674
                    if (!empty($questionDescription)) {
7675
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
7676
                    }
7677
                    $html .= '</div>';
7678
                    $html .= '</div>';
7679
                }
7680
                //@todo I need to the get the feedback type
7681
                $html .=  '<input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />';
7682
                $html .=  '<table class="exercise_questions">
7683
                           <tr>
7684
                            <td valign="top" colspan="2">';
7685
                $html .=  '</td></tr>';
7686
            }
7687
7688
            $canClick = isset($_GET['editQuestion']) ? '0' : (isset($_GET['modifyAnswers']) ? '0' : '1');
7689
            $s .= api_get_js('js/hotspot/js/hotspot.js');
7690
            $s .= '<script>
7691
                    <!--
7692
                    // Globals
7693
                    // Major version of Flash required
7694
                    var requiredMajorVersion = 7;
7695
                    // Minor version of Flash required
7696
                    var requiredMinorVersion = 0;
7697
                    // Minor version of Flash required
7698
                    var requiredRevision = 0;
7699
                    // the version of javascript supported
7700
                    var jsVersion = 1.0;
7701
                    // -->
7702
                    </script>
7703
                    <script language="VBScript" type="text/vbscript">
7704
                    <!-- // Visual basic helper required to detect Flash Player ActiveX control version information
7705
                    Function VBGetSwfVer(i)
7706
                      on error resume next
7707
                      Dim swControl, swVersion
7708
                      swVersion = 0
7709
7710
                      set swControl = CreateObject("ShockwaveFlash.ShockwaveFlash." + CStr(i))
7711
                      if (IsObject(swControl)) then
7712
                        swVersion = swControl.GetVariable("$version")
7713
                      end if
7714
                      VBGetSwfVer = swVersion
7715
                    End Function
7716
                    // -->
7717
                    </script>
7718
7719
                    <script language="JavaScript1.1" type="text/javascript">
7720
                    <!-- // Detect Client Browser type
7721
                    var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
7722
                    var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
7723
                    var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
7724
                    jsVersion = 1.1;
7725
                    // JavaScript helper required to detect Flash Player PlugIn version information
7726
                    function JSGetSwfVer(i) {
7727
                        // NS/Opera version >= 3 check for Flash plugin in plugin array
7728
                        if (navigator.plugins != null && navigator.plugins.length > 0) {
7729
                            if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
7730
                                var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
7731
                                var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
7732
                                descArray = flashDescription.split(" ");
7733
                                tempArrayMajor = descArray[2].split(".");
7734
                                versionMajor = tempArrayMajor[0];
7735
                                versionMinor = tempArrayMajor[1];
7736
                                if ( descArray[3] != "" ) {
7737
                                    tempArrayMinor = descArray[3].split("r");
7738
                                } else {
7739
                                    tempArrayMinor = descArray[4].split("r");
7740
                                }
7741
                                versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;
7742
                                flashVer = versionMajor + "." + versionMinor + "." + versionRevision;
7743
                            } else {
7744
                                flashVer = -1;
7745
                            }
7746
                        }
7747
                        // MSN/WebTV 2.6 supports Flash 4
7748
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4;
7749
                        // WebTV 2.5 supports Flash 3
7750
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3;
7751
                        // older WebTV supports Flash 2
7752
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2;
7753
                        // Can\'t detect in all other cases
7754
                        else {
7755
                            flashVer = -1;
7756
                        }
7757
                        return flashVer;
7758
                    }
7759
                    // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available
7760
7761
                    function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) {
7762
                        reqVer = parseFloat(reqMajorVer + "." + reqRevision);
7763
                        // loop backwards through the versions until we find the newest version
7764
                        for (i=25;i>0;i--) {
7765
                            if (isIE && isWin && !isOpera) {
7766
                                versionStr = VBGetSwfVer(i);
7767
                            } else {
7768
                                versionStr = JSGetSwfVer(i);
7769
                            }
7770
                            if (versionStr == -1 ) {
7771
                                return false;
7772
                            } else if (versionStr != 0) {
7773
                                if(isIE && isWin && !isOpera) {
7774
                                    tempArray         = versionStr.split(" ");
7775
                                    tempString        = tempArray[1];
7776
                                    versionArray      = tempString .split(",");
7777
                                } else {
7778
                                    versionArray      = versionStr.split(".");
7779
                                }
7780
                                versionMajor      = versionArray[0];
7781
                                versionMinor      = versionArray[1];
7782
                                versionRevision   = versionArray[2];
7783
7784
                                versionString     = versionMajor + "." + versionRevision;   // 7.0r24 == 7.24
7785
                                versionNum        = parseFloat(versionString);
7786
                                // is the major.revision >= requested major.revision AND the minor version >= requested minor
7787
                                if ( (versionMajor > reqMajorVer) && (versionNum >= reqVer) ) {
7788
                                    return true;
7789
                                } else {
7790
                                    return ((versionNum >= reqVer && versionMinor >= reqMinorVer) ? true : false );
7791
                                }
7792
                            }
7793
                        }
7794
                    }
7795
                    // -->
7796
                    </script>';
7797
            $s .= '<tr><td valign="top" colspan="2" width="520"><table><tr><td width="520">
7798
                    <script>
7799
                        // Version check based upon the values entered above in "Globals"
7800
                        var hasReqestedVersion = DetectFlashVer(requiredMajorVersion, requiredMinorVersion, requiredRevision);
7801
7802
                        // Check to see if the version meets the requirements for playback
7803
                        if (hasReqestedVersion) {  // if we\'ve detected an acceptable version
7804
                            var oeTags = \'<object type="application/x-shockwave-flash" data="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" width="600" height="'.$swf_height.'">\'
7805
                                        + \'<param name="wmode" value="transparent">\'
7806
                                        + \'<param name="movie" value="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" />\'
7807
                                        + \'<\/object>\';
7808
                            document.write(oeTags);   // embed the Flash Content SWF when all tests are passed
7809
                        } else {  // flash is too old or we can\'t detect the plugin
7810
                            var alternateContent = "Error<br \/>"
7811
                                + "Hotspots requires Macromedia Flash 7.<br \/>"
7812
                                + "<a href=\"http://www.macromedia.com/go/getflash/\">Get Flash<\/a>";
7813
                            document.write(alternateContent);  // insert non-flash content
7814
                        }
7815
                    </script>
7816
                    </td>
7817
                    <td valign="top" align="left">'.$answer_list.'</td></tr>
7818
                    </table>
7819
            </td></tr>';
7820
            $html .= $s;
7821
            $html .= '</table>';
7822
            return $html;
7823
        }
7824
        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...
7825
    }
7826
7827
    /**
7828
     * @param int $exeId
7829
     * @return array
7830
     */
7831
    public function returnQuestionListByAttempt($exeId)
7832
    {
7833
        return $this->displayQuestionListByAttempt($exeId, false, true);
7834
    }
7835
7836
    /**
7837
     * Display the exercise results
7838
     * @param int  $exe_id
7839
     * @param bool $saveUserResult save users results (true) or just show the results (false)
7840
     * @param bool $returnExerciseResult return array with exercise result info
7841
     * @return mixed
7842
     */
7843
    public function displayQuestionListByAttempt($exe_id, $saveUserResult = false, $returnExerciseResult = false)
7844
    {
7845
        global $origin, $debug;
7846
7847
        //Getting attempt info
7848
        $exercise_stat_info = $this->getStatTrackExerciseInfoByExeId($exe_id);
7849
7850
        //Getting question list
7851
        $question_list = array();
7852
        if (!empty($exercise_stat_info['data_tracking'])) {
7853
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
7854
        } else {
7855
            //Try getting the question list only if save result is off
7856
            if ($saveUserResult == false) {
7857
                $question_list = $this->selectQuestionList();
7858
            }
7859
            error_log("Data tracking is empty! exe_id: $exe_id");
7860
        }
7861
7862
        $counter = 1;
7863
        $total_score = 0;
7864
        $total_weight = 0;
7865
7866
        $exercise_content = null;
7867
7868
        //Hide results
7869
        $show_results = false;
7870
        $show_only_score = false;
7871
7872
        if ($this->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
7873
            $show_results = true;
7874
        }
7875
7876
        $showScoreOptions = [
7877
            RESULT_DISABLE_SHOW_SCORE_ONLY,
7878
            RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
7879
            RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT
7880
        ];
7881
7882
        if (in_array($this->results_disabled, $showScoreOptions)) {
7883
            $show_only_score = true;
7884
        }
7885
7886 View Code Duplication
        if ($show_results || $show_only_score) {
7887
            $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
7888
            // Shows exercise header.
7889
            echo $this->show_exercise_result_header(
7890
                $user_info['complete_name'],
7891
                api_convert_and_format_date($exercise_stat_info['start_date'], DATE_TIME_FORMAT_LONG),
7892
                $exercise_stat_info['duration']
7893
            );
7894
        }
7895
7896
        // Display text when test is finished #4074 and for LP #4227
7897
        $end_of_message = $this->selectTextWhenFinished();
7898
        if (!empty($end_of_message)) {
7899
            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...
7900
            echo "<div class='clear'>&nbsp;</div>";
7901
        }
7902
7903
        $question_list_answers = array();
7904
        $media_list = array();
7905
        $category_list = array();
7906
        $tempParentId = null;
7907
        $mediaCounter = 0;
7908
7909
        $exerciseResultInfo = array();
7910
7911
        // Loop over all question to show results for each of them, one by one
7912
        if (!empty($question_list)) {
7913
            if ($debug) {
7914
                error_log('Looping question_list '.print_r($question_list, 1));
7915
            }
7916
7917
            foreach ($question_list as $questionId) {
7918
7919
                // Creates a temporary Question object
7920
                $objQuestionTmp = Question::read($questionId);
7921
7922
                // This variable commes from exercise_submit_modal.php
7923
                ob_start();
7924
                $hotspot_delineation_result = null;
7925
7926
                // We're inside *one* question. Go through each possible answer for this question
7927
                $result = $this->manageAnswers(
7928
                    $exercise_stat_info['exe_id'],
7929
                    $questionId,
7930
                    null,
7931
                    'exercise_result',
7932
                    array(),
7933
                    $saveUserResult,
7934
                    true,
7935
                    $show_results,
7936
                    $hotspot_delineation_result
7937
                );
7938
7939
                if (empty($result)) {
7940
                    continue;
7941
                }
7942
7943
                $total_score += $result['score'];
7944
                $total_weight += $result['weight'];
7945
7946
                $question_list_answers[] = array(
7947
                    'question' => $result['open_question'],
7948
                    'answer' => $result['open_answer'],
7949
                    'answer_type' => $result['answer_type']
7950
                );
7951
7952
                $my_total_score = $result['score'];
7953
                $my_total_weight = $result['weight'];
7954
7955
                // Category report
7956
                $category_was_added_for_this_test = false;
7957
                $categoryExerciseList = $this->getListOfCategoriesWithQuestionForTest();
7958
7959
                $category_list = array();
7960
                if (isset($categoryExerciseList) && !empty($categoryExerciseList)) {
7961
                    foreach ($categoryExerciseList as $category_id => $categoryInfo) {
7962
                        if (!isset($category_list[$category_id])) {
7963
                            $category_list[$category_id] = array();
7964
                            $category_list[$category_id]['score'] = 0;
7965
                            $category_list[$category_id]['total'] = 0;
7966
                        }
7967
                        $category_list[$category_id]['score'] += $my_total_score;
7968
                        $category_list[$category_id]['total'] += $my_total_weight;
7969
                        $category_was_added_for_this_test = true;
7970
                    }
7971
                }
7972
7973
                // No category for this question!
7974
                if ($category_was_added_for_this_test == false) {
7975
                    if (!isset($category_list['none'])) {
7976
                        $category_list['none'] = array();
7977
                        $category_list['none']['score'] = 0;
7978
                        $category_list['none']['total'] = 0;
7979
                    }
7980
7981
                    $category_list['none']['score'] += $my_total_score;
7982
                    $category_list['none']['total'] += $my_total_weight;
7983
                }
7984
7985
                if ($this->selectPropagateNeg() == 0 && $my_total_score < 0) {
7986
                    $my_total_score = 0;
7987
                }
7988
7989
                $comnt = null;
7990 View Code Duplication
                if ($show_results) {
7991
                    $comnt = get_comments($exe_id, $questionId);
7992
                    if (!empty($comnt)) {
7993
                        echo '<b>'.get_lang('Feedback').'</b>';
7994
                        echo '<div id="question_feedback">'.$comnt.'</div>';
7995
                    }
7996
                }
7997
7998
                $score = array();
7999
                $score['result'] = get_lang('Score')." : ".ExerciseLib::show_score($my_total_score, $my_total_weight, false, true);
8000
                $score['pass'] = $my_total_score >= $my_total_weight ? true : false;
8001
                $score['score'] = $my_total_score;
8002
                $score['weight'] = $my_total_weight;
8003
                $score['comments'] = $comnt;
8004
8005
                $exerciseResultInfo[$questionId]['score'] = $score;
8006
                $exerciseResultInfo[$questionId]['details'] = $result;
8007
8008
                // If no results we hide the results
8009
                if ($show_results == false) {
8010
                    $score = array();
8011
                }
8012
                $contents = ob_get_clean();
8013
8014
                $question_content = '<div class="question_row">';
8015
8016
                if ($show_results) {
8017
8018
                    $show_media = false;
8019
                    $counterToShow = $counter;
8020
                    if ($objQuestionTmp->parent_id != 0) {
8021
8022
                        if (!in_array($objQuestionTmp->parent_id, $media_list)) {
8023
                            $media_list[] = $objQuestionTmp->parent_id;
8024
                            $show_media = true;
8025
                        }
8026
                        if ($tempParentId == $objQuestionTmp->parent_id) {
8027
                            $mediaCounter++;
8028
                        } else {
8029
                            $mediaCounter = 0;
8030
                        }
8031
                        $counterToShow = chr(97 + $mediaCounter);
8032
                        $tempParentId = $objQuestionTmp->parent_id;
8033
                    }
8034
8035
                    // Shows question title an description.
8036
                    $question_content .= $objQuestionTmp->return_header(null, $counterToShow, $score, $show_media, $this->getHideQuestionTitle());
8037
8038
                    // display question category, if any
8039
                    $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...
8040
                }
8041
                $counter++;
8042
8043
                $question_content .= $contents;
8044
                $question_content .= '</div>';
8045
8046
                $exercise_content .= $question_content;
8047
            } // end foreach() block that loops over all questions
8048
        }
8049
8050
        $total_score_text = null;
8051
8052
        if ($returnExerciseResult) {
8053
            return $exerciseResultInfo;
8054
        }
8055
8056
        if ($origin != 'learnpath') {
8057
            if ($show_results || $show_only_score) {
8058
                $total_score_text .= $this->get_question_ribbon($total_score, $total_weight, true);
8059
            }
8060
        }
8061
8062 View Code Duplication
        if (!empty($category_list) && ($show_results || $show_only_score)) {
8063
            //Adding total
8064
            $category_list['total'] = array('score' => $total_score, 'total' => $total_weight);
8065
            echo TestCategory::get_stats_table_by_attempt($this->id, $category_list, $this->categoryMinusOne);
8066
        }
8067
8068
        echo $total_score_text;
8069
        echo $exercise_content;
8070
8071
        if (!$show_only_score) {
8072
            echo $total_score_text;
8073
        }
8074
8075
        if ($saveUserResult) {
8076
8077
            // Tracking of results
8078
            $learnpath_id = $exercise_stat_info['orig_lp_id'];
8079
            $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
8080
            $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
8081
8082 View Code Duplication
            if (api_is_allowed_to_session_edit()) {
8083
                update_event_exercise(
8084
                    $exercise_stat_info['exe_id'],
8085
                    $this->selectId(),
8086
                    $total_score,
8087
                    $total_weight,
8088
                    api_get_session_id(),
8089
                    $learnpath_id,
8090
                    $learnpath_item_id,
8091
                    $learnpath_item_view_id,
8092
                    $exercise_stat_info['exe_duration'],
8093
                    '',
8094
                    array()
8095
                );
8096
            }
8097
8098
            // Send notification.
8099
            if (!api_is_allowed_to_edit(null, true)) {
8100
                $isSuccess = ExerciseLib::is_success_exercise_result($total_score, $total_weight, $this->selectPassPercentage());
8101
                $this->sendCustomNotification($exe_id, $exerciseResultInfo, $isSuccess);
8102
                $this->sendNotificationForOpenQuestions($question_list_answers, $origin, $exe_id);
8103
                $this->sendNotificationForOralQuestions($question_list_answers, $origin, $exe_id);
8104
            }
8105
        }
8106
    }
8107
8108
    /**
8109
     * Returns an HTML ribbon to show on top of the exercise result, with
8110
     * colouring depending on the success or failure of the student
8111
     * @param integer $score
8112
     * @param integer $weight
8113
     * @param bool $check_pass_percentage
8114
     * @return string
8115
     */
8116
    public function get_question_ribbon($score, $weight, $check_pass_percentage = false)
8117
    {
8118
        $eventMessage = null;
8119
        $ribbon = '<div class="question_row">';
8120
        $ribbon .= '<div class="ribbon">';
8121
        if ($check_pass_percentage) {
8122
            $is_success = ExerciseLib::is_success_exercise_result($score, $weight, $this->selectPassPercentage());
8123
            // Color the final test score if pass_percentage activated
8124
            $ribbon_total_success_or_error = "";
8125
            if (ExerciseLib::is_pass_pourcentage_enabled($this->selectPassPercentage())) {
8126
                if ($is_success) {
8127
                    $eventMessage = $this->getOnSuccessMessage();
8128
                    $ribbon_total_success_or_error = ' ribbon-total-success';
8129
                } else {
8130
                    $eventMessage = $this->getOnFailedMessage();
8131
                    $ribbon_total_success_or_error = ' ribbon-total-error';
8132
                }
8133
            }
8134
            $ribbon .= '<div class="rib rib-total '.$ribbon_total_success_or_error.'">';
8135
        } else {
8136
            $ribbon .= '<div class="rib rib-total">';
8137
        }
8138
        $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
8139
        $ribbon .= ExerciseLib::show_score($score, $weight, false, true);
8140
        $ribbon .= '</h3>';
8141
        $ribbon .= '</div>';
8142
8143
        if ($check_pass_percentage) {
8144
            $ribbon .= ExerciseLib::show_success_message($score, $weight, $this->selectPassPercentage());
8145
        }
8146
        $ribbon .= '</div>';
8147
        $ribbon .= '</div>';
8148
8149
        $ribbon .= $eventMessage;
8150
8151
        return $ribbon;
8152
    }
8153
8154
    /**
8155
     * Returns an array of categories details for the questions of the current
8156
     * exercise.
8157
     * @return array
8158
     */
8159
    public function getQuestionWithCategories()
8160
    {
8161
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
8162
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
8163
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8164
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
8165
        $sql = "SELECT DISTINCT cat.*
8166
                FROM $TBL_EXERCICE_QUESTION e
8167
                INNER JOIN $TBL_QUESTIONS q
8168
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8169
                INNER JOIN $categoryRelTable catRel
8170
                ON (catRel.question_id = e.question_id)
8171
                INNER JOIN $categoryTable cat
8172
                ON (cat.id = catRel.category_id)
8173
                WHERE
8174
                  e.c_id = {$this->course_id} AND
8175
                  e.exercice_id	= ".intval($this->id);
8176
8177
        $result = Database::query($sql);
8178
        $categoriesInExercise = array();
8179
        if (Database::num_rows($result)) {
8180
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
8181
        }
8182
8183
        return $categoriesInExercise;
8184
    }
8185
8186
    /**
8187
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option
8188
     */
8189
    public function get_max_score()
8190
    {
8191
        $out_max_score = 0;
8192
        // list of question's id !!! the array key start at 1 !!!
8193
        $questionList = $this->selectQuestionList(true);
8194
8195
        // test is randomQuestions - see field random of test
8196
        if ($this->random > 0 && $this->randomByCat == 0) {
8197
            $numberRandomQuestions = $this->random;
8198
            $questionScoreList = array();
8199
            for ($i = 1; $i <= count($questionList); $i++) {
8200
                $tmpobj_question = Question::read($questionList[$i]);
8201
                $questionScoreList[] = $tmpobj_question->weighting;
8202
            }
8203
            rsort($questionScoreList);
8204
            // add the first $numberRandomQuestions value of score array to get max_score
8205
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
8206
                $out_max_score += $questionScoreList[$i];
8207
            }
8208
        } else if ($this->random > 0 && $this->randomByCat > 0) {
8209
            // test is random by category
8210
            // get the $numberRandomQuestions best score question of each category
8211
8212
            $numberRandomQuestions = $this->random;
8213
            $tab_categories_scores = array();
8214
            for ($i = 1; $i <= count($questionList); $i++) {
8215
                $question_category_id = TestCategory::getCategoryForQuestion($questionList[$i]);
8216
                if (!is_array($tab_categories_scores[$question_category_id])) {
8217
                    $tab_categories_scores[$question_category_id] = array();
8218
                }
8219
                $tmpobj_question = Question::read($questionList[$i]);
8220
                $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
8221
            }
8222
8223
            // here we've got an array with first key, the category_id, second key, score of question for this cat
8224
            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...
8225
                rsort($tab_scores);
8226
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
8227
                    $out_max_score += $tab_scores[$i];
8228
                }
8229
            }
8230
        } else {
8231
            // standard test, just add each question score
8232
            foreach ($questionList as $questionId) {
8233
                $question = Question::read($questionId, $this->course_id);
8234
                $out_max_score += $question->weighting;
8235
            }
8236
        }
8237
8238
        return $out_max_score;
8239
    }
8240
8241
    /**
8242
    * @return string
8243
    */
8244
    public function get_formated_title()
8245
    {
8246
        return api_html_entity_decode($this->selectTitle());
8247
    }
8248
8249
    /**
8250
     * @param $in_title
8251
     * @return string
8252
     */
8253
    public static function get_formated_title_variable($in_title)
8254
    {
8255
        return api_html_entity_decode($in_title);
8256
    }
8257
8258
    /**
8259
     * @return string
8260
     */
8261
    public function format_title()
8262
    {
8263
        return api_htmlentities($this->title);
8264
    }
8265
8266
    /**
8267
     * @param $in_title
8268
     * @return string
8269
     */
8270
    public static function format_title_variable($in_title)
8271
    {
8272
        return api_htmlentities($in_title);
8273
    }
8274
8275
    /**
8276
     * @param int $courseId
8277
     * @param int $sessionId
8278
     * @return array exercises
8279
     */
8280 View Code Duplication
    public function getExercisesByCouseSession($courseId, $sessionId)
8281
    {
8282
        $courseId = intval($courseId);
8283
        $sessionId = intval($sessionId);
8284
8285
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8286
        $sql = "SELECT * FROM $tbl_quiz cq
8287
                WHERE
8288
                    cq.c_id = %s AND
8289
                    (cq.session_id = %s OR cq.session_id = 0) AND
8290
                    cq.active = 0
8291
                ORDER BY cq.id";
8292
        $sql = sprintf($sql, $courseId, $sessionId);
8293
8294
        $result = Database::query($sql);
8295
8296
        $rows = array();
8297
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8298
            $rows[] = $row;
8299
        }
8300
8301
        return $rows;
8302
    }
8303
8304
    /**
8305
     *
8306
     * @param int $courseId
8307
     * @param int $sessionId
8308
     * @param array $quizId
8309
     * @return array exercises
8310
     */
8311
    public function getExerciseAndResult($courseId, $sessionId, $quizId = array())
8312
    {
8313
        if (empty($quizId)) {
8314
            return array();
8315
        }
8316
8317
        $sessionId = intval($sessionId);
8318
8319
        $ids = is_array($quizId) ? $quizId : array($quizId);
8320
        $ids = array_map('intval', $ids);
8321
        $ids = implode(',', $ids);
8322
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8323
        if ($sessionId != 0) {
8324
            $sql = "SELECT * FROM $track_exercises te
8325
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8326
              WHERE
8327
              te.id = %s AND
8328
              te.session_id = %s AND
8329
              cq.id IN (%s)
8330
              ORDER BY cq.id";
8331
8332
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
8333
        } else {
8334
            $sql = "SELECT * FROM $track_exercises te
8335
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8336
              WHERE
8337
              te.id = %s AND
8338
              cq.id IN (%s)
8339
              ORDER BY cq.id";
8340
            $sql = sprintf($sql, $courseId, $ids);
8341
        }
8342
        $result = Database::query($sql);
8343
        $rows = array();
8344
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8345
            $rows[] = $row;
8346
        }
8347
8348
        return $rows;
8349
    }
8350
8351
    /**
8352
     * @param $exeId
8353
     * @param $exercise_stat_info
8354
     * @param $remindList
8355
     * @param $currentQuestion
8356
     * @return int|null
8357
     */
8358
    public static function getNextQuestionId($exeId, $exercise_stat_info, $remindList, $currentQuestion)
8359
    {
8360
        $result = get_exercise_results_by_attempt($exeId, 'incomplete');
8361
8362
        if (isset($result[$exeId])) {
8363
            $result = $result[$exeId];
8364
        } else {
8365
            return null;
8366
        }
8367
8368
        $data_tracking  = $exercise_stat_info['data_tracking'];
8369
        $data_tracking  = explode(',', $data_tracking);
8370
8371
        // if this is the final question do nothing.
8372
        if ($currentQuestion == count($data_tracking)) {
8373
            return null;
8374
        }
8375
8376
        $currentQuestion = $currentQuestion - 1;
8377
8378
        if (!empty($result['question_list'])) {
8379
            $answeredQuestions = array();
8380
8381
            foreach ($result['question_list'] as $question) {
8382
                if (!empty($question['answer'])) {
8383
                    $answeredQuestions[] = $question['question_id'];
8384
                }
8385
            }
8386
8387
            // Checking answered questions
8388
8389
            $counterAnsweredQuestions = 0;
8390
            foreach ($data_tracking as $questionId) {
8391
                if (!in_array($questionId, $answeredQuestions)) {
8392
                    if ($currentQuestion != $counterAnsweredQuestions) {
8393
                        break;
8394
                    }
8395
                }
8396
                $counterAnsweredQuestions++;
8397
            }
8398
8399
            $counterRemindListQuestions = 0;
8400
            // Checking questions saved in the reminder list
8401
8402
            if (!empty($remindList)) {
8403
                foreach ($data_tracking as $questionId) {
8404
                    if (in_array($questionId, $remindList)) {
8405
                        // Skip the current question
8406
                        if ($currentQuestion != $counterRemindListQuestions) {
8407
                            break;
8408
                        }
8409
                    }
8410
                    $counterRemindListQuestions++;
8411
                }
8412
8413
                if ($counterRemindListQuestions < $currentQuestion) {
8414
                    return null;
8415
                }
8416
8417
                if (!empty($counterRemindListQuestions)) {
8418
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
8419
                        return $counterAnsweredQuestions;
8420
                    } else {
8421
                        return $counterRemindListQuestions;
8422
                    }
8423
                }
8424
            }
8425
8426
            return $counterAnsweredQuestions;
8427
        }
8428
    }
8429
8430
    /**
8431
     * Gets the position of a questionId in the question list
8432
     * @param $questionId
8433
     * @return int
8434
     */
8435
    public function getPositionInCompressedQuestionList($questionId)
8436
    {
8437
        $questionList = $this->getQuestionListWithMediasCompressed();
8438
        $mediaQuestions = $this->getMediaList();
8439
        $position = 1;
8440
        foreach ($questionList as $id) {
8441
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
8442
                $mediaQuestionList = $mediaQuestions[$id];
8443
                if (in_array($questionId, $mediaQuestionList)) {
8444
                    return $position;
8445
                } else {
8446
                    $position++;
8447
                }
8448
            } else {
8449
                if ($id == $questionId) {
8450
                    return $position;
8451
                } else {
8452
                    $position++;
8453
                }
8454
            }
8455
        }
8456
        return 1;
8457
    }
8458
8459
    /**
8460
     * Get the correct answers in all attempts
8461
     * @param int $learnPathId
8462
     * @param int $learnPathItemId
8463
     * @return array
8464
     */
8465
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
8466
    {
8467
        $attempts = Event::getExerciseResultsByUser(
8468
            api_get_user_id(),
8469
            $this->id,
8470
            api_get_course_int_id(),
8471
            api_get_session_id(),
8472
            $learnPathId,
8473
            $learnPathItemId,
8474
            'asc'
8475
        );
8476
8477
        $corrects = [];
8478
8479
        foreach ($attempts as $attempt) {
8480
            foreach ($attempt['question_list'] as $answers) {
8481
                foreach ($answers as $answer) {
8482
                    $objAnswer = new Answer($answer['question_id']);
8483
8484
                    switch ($objAnswer->getQuestionType()) {
8485
                        case FILL_IN_BLANKS:
8486
                            $isCorrect = FillBlanks::isCorrect($answer['answer']);
8487
                            break;
8488
                        case MATCHING:
8489
                            //no break
8490
                        case DRAGGABLE:
8491
                            //no break
8492
                        case MATCHING_DRAGGABLE:
8493
                            $isCorrect = Matching::isCorrect(
8494
                                $answer['position'],
8495
                                $answer['answer'],
8496
                                $answer['question_id']
8497
                            );
8498
                            break;
8499
                        default:
8500
                            $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
8501
                    }
8502
8503
                    if ($isCorrect) {
8504
                        $corrects[$answer['question_id']][] = $answer;
8505
                    }
8506
                }
8507
            }
8508
        }
8509
8510
        return $corrects;
8511
    }
8512
}
8513