Test Setup Failed
Push — master ( 31ad05...ee210b )
by Yannick
253:41 queued 196:01
created

Exercise::selectSaveCorrectAnswers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 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
 * @author Olivier Brouckaert
12
 * @author Julio Montoya Cleaning exercises
13
 * Modified by Hubert Borderiou #294
14
 */
15
class Exercise
16
{
17
    public $id;
18
    public $name;
19
    public $title;
20
    public $exercise;
21
    public $description;
22
    public $sound;
23
    public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
24
    public $random;
25
    public $random_answers;
26
    public $active;
27
    public $timeLimit;
28
    public $attempts;
29
    public $feedback_type;
30
    public $end_time;
31
    public $start_time;
32
    public $questionList;  // array with the list of this exercise's questions
33
    public $results_disabled;
34
    public $expired_time;
35
    public $course;
36
    public $course_id;
37
    public $propagate_neg;
38
    public $saveCorrectAnswers;
39
    public $review_answers;
40
    public $randomByCat;
41
    public $text_when_finished;
42
    public $display_category_name;
43
    public $pass_percentage;
44
    public $edit_exercise_in_lp = false;
45
    public $is_gradebook_locked = false;
46
    public $exercise_was_added_in_lp = false;
47
    public $lpList = array();
48
    public $force_edit_exercise_in_lp = false;
49
    public $sessionId = 0;
50
    public $debug = false;
51
52
    /**
53
     * Constructor of the class
54
     *
55
     * @author Olivier Brouckaert
56
     */
57
    public function __construct($course_id = null)
58
    {
59
        $this->id = 0;
60
        $this->exercise = '';
61
        $this->description = '';
62
        $this->sound = '';
63
        $this->type = ALL_ON_ONE_PAGE;
64
        $this->random = 0;
65
        $this->random_answers = 0;
66
        $this->active = 1;
67
        $this->questionList = array();
68
        $this->timeLimit = 0;
69
        $this->end_time = '0000-00-00 00:00:00';
70
        $this->start_time = '0000-00-00 00:00:00';
71
        $this->results_disabled = 1;
72
        $this->expired_time = '0000-00-00 00:00:00';
73
        $this->propagate_neg = 0;
74
        $this->saveCorrectAnswers = 0;
75
        $this->review_answers = false;
76
        $this->randomByCat = 0;
77
        $this->text_when_finished = '';
78
        $this->display_category_name = 0;
79
        $this->pass_percentage = '';
80
81
        if (!empty($course_id)) {
82
            $course_info = api_get_course_info_by_id($course_id);
83
        } else {
84
            $course_info = api_get_course_info();
85
        }
86
        $this->course_id = $course_info['real_id'];
87
        $this->course = $course_info;
88
        $this->sessionId = api_get_session_id();
89
    }
90
91
    /**
92
     * Reads exercise information from the data base
93
     *
94
     * @author Olivier Brouckaert
95
     * @param integer $id - exercise Id
96
     *
97
     * @return boolean - true if exercise exists, otherwise false
98
     */
99
    public function read($id)
100
    {
101
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
102
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
103
104
        $id  = intval($id);
105
        if (empty($this->course_id)) {
106
            return false;
107
        }
108
        $sql = "SELECT * FROM $TBL_EXERCISES WHERE c_id = ".$this->course_id." AND id = ".$id;
109
        $result = Database::query($sql);
110
111
        // if the exercise has been found
112
        if ($object = Database::fetch_object($result)) {
113
            $this->id = $id;
114
            $this->exercise = $object->title;
115
            $this->name = $object->title;
116
            $this->title = $object->title;
117
            $this->description = $object->description;
118
            $this->sound = $object->sound;
119
            $this->type = $object->type;
120
            if (empty($this->type)) {
121
                $this->type = ONE_PER_PAGE;
122
            }
123
            $this->random = $object->random;
124
            $this->random_answers = $object->random_answers;
125
            $this->active = $object->active;
126
            $this->results_disabled = $object->results_disabled;
127
            $this->attempts = $object->max_attempt;
128
            $this->feedback_type = $object->feedback_type;
129
            $this->propagate_neg = $object->propagate_neg;
130
            $this->saveCorrectAnswers = $object->save_correct_answers;
131
            $this->randomByCat = $object->random_by_category;
132
            $this->text_when_finished = $object->text_when_finished;
133
            $this->display_category_name = $object->display_category_name;
134
            $this->pass_percentage = $object->pass_percentage;
135
            $this->sessionId = $object->session_id;
136
137
            $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
138
139
            $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
140
            $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
141
            $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null;
142
143
            $sql = "SELECT lp_id, max_score
144
                    FROM $table_lp_item
145
                    WHERE   c_id = {$this->course_id} AND
146
                            item_type = '".TOOL_QUIZ."' AND
147
                            path = '".$id."'";
148
            $result = Database::query($sql);
149
150
            if (Database::num_rows($result) > 0) {
151
                $this->exercise_was_added_in_lp = true;
152
                $this->lpList = Database::store_result($result, 'ASSOC');
153
            }
154
155
            $this->force_edit_exercise_in_lp = api_get_configuration_value('force_edit_exercise_in_lp');
156
157
            if ($this->exercise_was_added_in_lp) {
158
                $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
159
            } else {
160
                $this->edit_exercise_in_lp = true;
161
            }
162
163
            if ($object->end_time != '0000-00-00 00:00:00') {
164
                $this->end_time 	= $object->end_time;
165
            }
166
            if ($object->start_time != '0000-00-00 00:00:00') {
167
                $this->start_time 	= $object->start_time;
168
            }
169
170
            //control time
171
            $this->expired_time 	= $object->expired_time;
172
173
            //Checking if question_order is correctly set
174
175
            //$this->questionList     = $this->selectQuestionList(true);
176
            if ($parseQuestionList) {
0 ignored issues
show
Bug introduced by
The variable $parseQuestionList 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...
177
                $this->setQuestionList();
178
            }
179
180
            //overload questions list with recorded questions list
181
            //load questions only for exercises of type 'one question per page'
182
            //this is needed only is there is no questions
183
            /*
184
			// @todo not sure were in the code this is used somebody mess with the exercise tool
185
			// @todo don't know who add that config and why $_configuration['live_exercise_tracking']
186
			global $_configuration, $questionList;
187
			if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST' && defined('QUESTION_LIST_ALREADY_LOGGED') &&
188
			isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']) {
189
				$this->questionList = $questionList;
190
			}*/
191
            return true;
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * @return string
199
     */
200
    public function getCutTitle()
201
    {
202
        return cut($this->exercise, EXERCISE_MAX_NAME_SIZE);
203
    }
204
205
    /**
206
     * returns the exercise ID
207
     *
208
     * @author Olivier Brouckaert
209
     * @return int - exercise ID
210
     */
211
    public function selectId()
212
    {
213
        return $this->id;
214
    }
215
216
    /**
217
     * returns the exercise title
218
     *
219
     * @author Olivier Brouckaert
220
     * @return string - exercise title
221
     */
222
    public function selectTitle()
223
    {
224
        return $this->exercise;
225
    }
226
227
    /**
228
     * returns the number of attempts setted
229
     *
230
     * @return int - exercise attempts
231
     */
232
    public function selectAttempts()
233
    {
234
        return $this->attempts;
235
    }
236
237
    /** returns the number of FeedbackType  *
238
     *  0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback
239
     * @return int - exercise attempts
240
     */
241
    public function selectFeedbackType()
242
    {
243
        return $this->feedback_type;
244
    }
245
246
    /**
247
     * returns the time limit
248
     */
249
    public function selectTimeLimit()
250
    {
251
        return $this->timeLimit;
252
    }
253
254
    /**
255
     * returns the exercise description
256
     *
257
     * @author Olivier Brouckaert
258
     * @return string - exercise description
259
     */
260
    public function selectDescription()
261
    {
262
        return $this->description;
263
    }
264
265
    /**
266
     * returns the exercise sound file
267
     *
268
     * @author Olivier Brouckaert
269
     * @return string - exercise description
270
     */
271
    public function selectSound()
272
    {
273
        return $this->sound;
274
    }
275
276
    /**
277
     * returns the exercise type
278
     *
279
     * @author Olivier Brouckaert
280
     * @return integer - exercise type
281
     */
282
    public function selectType()
283
    {
284
        return $this->type;
285
    }
286
287
    /**
288
     * @return int
289
     */
290
    public function getModelType()
291
    {
292
        return $this->modelType;
293
    }
294
295
    /**
296
     * @return int
297
     */
298
    public function selectEndButton()
299
    {
300
        return $this->endButton;
301
    }
302
303
    /**
304
     * @return string
305
     */
306
    public function getOnSuccessMessage()
307
    {
308
        return $this->onSuccessMessage;
309
    }
310
311
    /**
312
     * @return string
313
     */
314
    public function getOnFailedMessage()
315
    {
316
        return $this->onFailedMessage;
317
    }
318
319
    /**
320
     * @author hubert borderiou 30-11-11
321
     * @return integer : do we display the question category name for students
322
     */
323
    public function selectDisplayCategoryName()
324
    {
325
        return $this->display_category_name;
326
    }
327
328
    /**
329
     * @return int
330
     */
331
    public function selectPassPercentage()
332
    {
333
        return $this->pass_percentage;
334
    }
335
336
    /**
337
     *
338
     * Modify object to update the switch display_category_name
339
     * @author hubert borderiou 30-11-11
340
     * @param int $in_txt is an integer 0 or 1
341
     */
342
    public function updateDisplayCategoryName($in_txt)
343
    {
344
        $this->display_category_name = $in_txt;
345
    }
346
347
    /**
348
     * @author hubert borderiou 28-11-11
349
     * @return string html text : the text to display ay the end of the test.
350
     */
351
    public function selectTextWhenFinished()
352
    {
353
        return $this->text_when_finished;
354
    }
355
356
    /**
357
     * @author hubert borderiou 28-11-11
358
     * @return string  html text : update the text to display ay the end of the test.
359
     */
360
    public function updateTextWhenFinished($in_txt)
361
    {
362
        $this->text_when_finished = $in_txt;
363
    }
364
365
    /**
366
     * return 1 or 2 if randomByCat
367
     * @author hubert borderiou
368
     * @return integer - quiz random by category
369
     */
370
    public function selectRandomByCat()
371
    {
372
        return $this->randomByCat;
373
    }
374
375
    /**
376
     * return 0 if no random by cat
377
     * return 1 if random by cat, categories shuffled
378
     * return 2 if random by cat, categories sorted by alphabetic order
379
     * @author hubert borderiou
380
     * @return integer - quiz random by category
381
     */
382
    public function isRandomByCat()
383
    {
384
        /*$res = 0;
385
        if ($this->randomByCat == 1) {
386
            $res = 1;
387
        } else if ($this->randomByCat == 2) {
388
            $res = 2;
389
        }
390
        */
391
392
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
393
        if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
394
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
395
        } else if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
396
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
397
        }
398
399
        return $res;
400
    }
401
402
    /**
403
     * return nothing
404
     * update randomByCat value for object
405
     * @param int $random
406
     *
407
     * @author hubert borderiou
408
     */
409
    public function updateRandomByCat($random)
410
    {
411
        if (in_array($random, array(
412
                EXERCISE_CATEGORY_RANDOM_SHUFFLED,
413
                EXERCISE_CATEGORY_RANDOM_ORDERED,
414
                EXERCISE_CATEGORY_RANDOM_DISABLED
415
            )
416
        )) {
417
            $this->randomByCat = $random;
418
        } else {
419
            $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
420
        }
421
    }
422
423
    /**
424
     * Tells if questions are selected randomly, and if so returns the draws
425
     *
426
     * @author Carlos Vargas
427
     * @return integer - results disabled exercise
428
     */
429
    public function selectResultsDisabled()
430
    {
431
        return $this->results_disabled;
432
    }
433
434
    /**
435
     * tells if questions are selected randomly, and if so returns the draws
436
     *
437
     * @author Olivier Brouckaert
438
     * @return integer - 0 if not random, otherwise the draws
439
     */
440
    public function isRandom()
441
    {
442
        if($this->random > 0 || $this->random == -1) {
443
            return true;
444
        } else {
445
            return false;
446
        }
447
    }
448
449
    /**
450
     * returns random answers status.
451
     *
452
     * @author Juan Carlos Rana
453
     */
454
    public function selectRandomAnswers()
455
    {
456
        return $this->random_answers;
457
    }
458
459
    /**
460
     * Same as isRandom() but has a name applied to values different than 0 or 1
461
     */
462
    public function getShuffle()
463
    {
464
        return $this->random;
465
    }
466
467
    /**
468
     * returns the exercise status (1 = enabled ; 0 = disabled)
469
     *
470
     * @author Olivier Brouckaert
471
     * @return boolean - true if enabled, otherwise false
472
     */
473
    public function selectStatus()
474
    {
475
        return $this->active;
476
    }
477
478
    /**
479
     * If false the question list will be managed as always if true the question will be filtered
480
     * depending of the exercise settings (table c_quiz_rel_category)
481
     * @param bool active or inactive grouping
482
     **/
483
    public function setCategoriesGrouping($status)
484
    {
485
        $this->categories_grouping = (bool) $status;
486
    }
487
488
    /**
489
     * @return int
490
     */
491
    public function getHideQuestionTitle()
492
    {
493
        return $this->hideQuestionTitle;
494
    }
495
496
    /**
497
     * @param $value
498
     */
499
    public function setHideQuestionTitle($value)
500
    {
501
        $this->hideQuestionTitle = intval($value);
502
    }
503
504
    /**
505
     * @return int
506
     */
507
    public function getScoreTypeModel()
508
    {
509
        return $this->scoreTypeModel;
510
    }
511
512
    /**
513
     * @param int $value
514
     */
515
    public function setScoreTypeModel($value)
516
    {
517
        $this->scoreTypeModel = intval($value);
518
    }
519
520
    /**
521
     * @return int
522
     */
523
    public function getGlobalCategoryId()
524
    {
525
        return $this->globalCategoryId;
526
    }
527
528
    /**
529
     * @param int $value
530
     */
531
    public function setGlobalCategoryId($value)
532
    {
533
        if (is_array($value) && isset($value[0])) {
534
            $value = $value[0];
535
        }
536
        $this->globalCategoryId = intval($value);
537
    }
538
539
    /**
540
     *
541
     * @param int $start
542
     * @param int $limit
543
     * @param int $sidx
544
     * @param string $sord
545
     * @param array $where_condition
546
     * @param array $extraFields
547
     */
548
    public function getQuestionListPagination($start, $limit, $sidx, $sord, $where_condition = array(), $extraFields = array())
549
    {
550
        if (!empty($this->id)) {
551
            $category_list = TestCategory::getListOfCategoriesNameForTest($this->id, false);
552
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
553
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
554
555
            $sql = "SELECT q.iid
556
                    FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS  q
557
                        ON (e.question_id = q.iid AND e.c_id = ".$this->course_id." )
558
					WHERE e.exercice_id	= '".Database::escape_string($this->id)."'
559
					";
560
561
            $orderCondition = "ORDER BY question_order";
562
563
            if (!empty($sidx) && !empty($sord)) {
564
                if ($sidx == 'question') {
565
566
                    if (in_array(strtolower($sord), array('desc', 'asc'))) {
567
                        $orderCondition = " ORDER BY q.$sidx $sord";
568
                    }
569
                }
570
            }
571
572
            $sql .= $orderCondition;
573
574
            $limitCondition = null;
575
576 View Code Duplication
            if (isset($start) && isset($limit)) {
577
                $start = intval($start);
578
                $limit = intval($limit);
579
                $limitCondition = " LIMIT $start, $limit";
580
            }
581
            $sql .= $limitCondition;
582
            $result = Database::query($sql);
583
            $questions = array();
584
            if (Database::num_rows($result)) {
585
                if (!empty($extraFields)) {
586
                    $extraFieldValue = new ExtraFieldValue('question');
587
                }
588
                while ($question = Database::fetch_array($result, 'ASSOC')) {
589
                    /** @var Question $objQuestionTmp */
590
                    $objQuestionTmp = Question::read($question['iid']);
591
                    $category_labels = TestCategory::return_category_labels($objQuestionTmp->category_list, $category_list);
592
593
                    if (empty($category_labels)) {
594
                        $category_labels = "-";
595
                    }
596
597
                    // Question type
598
                    list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html();
599
600
                    $question_media = null;
601
                    if (!empty($objQuestionTmp->parent_id)) {
602
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
603
                        $question_media  = Question::getMediaLabel($objQuestionMedia->question);
604
                    }
605
606
                    $questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM).$question_media);
607
608
                    $question = array(
609
                        'id' => $question['iid'],
610
                        'question' => $objQuestionTmp->selectTitle(),
611
                        'type' => $questionType,
612
                        'category' => Display::tag('div', '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'),
613
                        'score' => $objQuestionTmp->selectWeighting(),
614
                        'level' => $objQuestionTmp->level
615
                    );
616
                    if (!empty($extraFields)) {
617
                        foreach ($extraFields as $extraField) {
618
                            $value = $extraFieldValue->get_values_by_handler_and_field_id($question['id'], $extraField['id']);
619
                            $stringValue = null;
620
                            if ($value) {
621
                                $stringValue = $value['field_value'];
622
                            }
623
                            $question[$extraField['field_variable']] = $stringValue;
624
                        }
625
                    }
626
                    $questions[] = $question;
627
                }
628
            }
629
            return $questions;
630
        }
631
    }
632
633
    /**
634
     * Get question count per exercise from DB (any special treatment)
635
     * @return int
636
     */
637 View Code Duplication
    public function getQuestionCount()
638
    {
639
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
640
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
641
        $sql = "SELECT count(q.id) as count
642
                FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
643
                    ON (e.question_id = q.id)
644
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= ".Database::escape_string($this->id);
645
        $result = Database::query($sql);
646
647
        $count = 0;
648
        if (Database::num_rows($result)) {
649
            $row = Database::fetch_array($result);
650
            $count = $row['count'];
651
        }
652
653
        return $count;
654
    }
655
656
    /**
657
     * @return array
658
     */
659 View Code Duplication
    public function getQuestionOrderedListByName()
660
    {
661
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
662
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
663
664
        // Getting question list from the order (question list drag n drop interface ).
665
        $sql = "SELECT e.question_id
666
                FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
667
                    ON (e.question_id= q.id)
668
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
669
                ORDER BY q.question";
670
        $result = Database::query($sql);
671
        $list = array();
672
        if (Database::num_rows($result)) {
673
            $list = Database::store_result($result, 'ASSOC');
674
        }
675
        return $list;
676
    }
677
678
    /**
679
     * Gets the question list ordered by the question_order setting (drag and drop)
680
     * @return array
681
     */
682
    private function getQuestionOrderedList()
683
    {
684
        $questionList = array();
685
686
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
687
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
688
689
        // Getting question_order to verify that the question
690
        // list is correct and all question_order's were set
691
        $sql = "SELECT DISTINCT e.question_order
692
                FROM $TBL_EXERCICE_QUESTION e
693
                INNER JOIN $TBL_QUESTIONS q
694
                    ON (e.question_id = q.id)
695
                WHERE
696
                  e.c_id = {$this->course_id} AND
697
                  e.exercice_id	= ".Database::escape_string($this->id);
698
699
        $result = Database::query($sql);
700
701
        $count_question_orders = Database::num_rows($result);
702
703
        // Getting question list from the order (question list drag n drop interface ).
704
        $sql = "SELECT DISTINCT e.question_id, e.question_order
705
                FROM $TBL_EXERCICE_QUESTION e
706
                INNER JOIN $TBL_QUESTIONS q
707
                    ON (e.question_id= q.id)
708
                WHERE
709
                    e.c_id = {$this->course_id} AND
710
                    e.exercice_id	= '".Database::escape_string($this->id)."'
711
                ORDER BY question_order";
712
713
        $result = Database::query($sql);
714
715
        // Fills the array with the question ID for this exercise
716
        // the key of the array is the question position
717
        $temp_question_list = array();
718
719
        $counter = 1;
720
        while ($new_object = Database::fetch_object($result)) {
721
            // Correct order.
722
            $questionList[$new_object->question_order] = $new_object->question_id;
723
            // Just in case we save the order in other array
724
            $temp_question_list[$counter] = $new_object->question_id;
725
            $counter++;
726
        }
727
728
        if (!empty($temp_question_list)) {
729
            /* If both array don't match it means that question_order was not correctly set
730
               for all questions using the default mysql order */
731
            if (count($temp_question_list) != $count_question_orders) {
732
                $questionList = $temp_question_list;
733
            }
734
        }
735
736
        return $questionList;
737
    }
738
739
    /**
740
     * Select N values from the questions per category array
741
     *
742
     * @param array $categoriesAddedInExercise
743
     * @param array $question_list
744
     * @param array $questions_by_category per category
745
     * @param bool $flatResult
746
     * @param bool $randomizeQuestions
747
     *
748
     * @return array
749
     */
750
    private function pickQuestionsPerCategory(
751
        $categoriesAddedInExercise,
752
        $question_list,
753
        & $questions_by_category,
754
        $flatResult = true,
755
        $randomizeQuestions = false
756
    ) {
757
        $addAll = true;
758
        $categoryCountArray = array();
759
760
        // Getting how many questions will be selected per category.
761
        if (!empty($categoriesAddedInExercise)) {
762
            $addAll = false;
763
            // Parsing question according the category rel exercise settings
764
            foreach ($categoriesAddedInExercise as $category_info) {
765
                $category_id = $category_info['category_id'];
766
                if (isset($questions_by_category[$category_id])) {
767
                    // How many question will be picked from this category.
768
                    $count = $category_info['count_questions'];
769
                    // -1 means all questions
770
                    if ($count == -1) {
771
                        $categoryCountArray[$category_id] = 999;
772
                    } else {
773
                        $categoryCountArray[$category_id] = $count;
774
                    }
775
                }
776
            }
777
        }
778
779
        if (!empty($questions_by_category)) {
780
            $temp_question_list = array();
781
782
            foreach ($questions_by_category as $category_id => & $categoryQuestionList) {
783
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
784
                    if (isset($categoryCountArray[$category_id])) {
785
                        $numberOfQuestions = $categoryCountArray[$category_id];
786
                    } else {
787
                        $numberOfQuestions = 0;
788
                    }
789
                }
790
791
                if ($addAll) {
792
                    $numberOfQuestions = 999;
793
                }
794
795
                if (!empty($numberOfQuestions)) {
796
                    $elements = TestCategory::getNElementsFromArray(
797
                        $categoryQuestionList,
798
                        $numberOfQuestions,
799
                        $randomizeQuestions
800
                    );
801
802
                    if (!empty($elements)) {
803
                        $temp_question_list[$category_id] = $elements;
804
                        $categoryQuestionList = $elements;
805
                    }
806
                }
807
            }
808
809
            if (!empty($temp_question_list)) {
810
                if ($flatResult) {
811
                    $temp_question_list = array_flatten($temp_question_list);
812
                }
813
                $question_list = $temp_question_list;
814
            }
815
        }
816
817
        return $question_list;
818
    }
819
820
    /**
821
     * Selecting question list depending in the exercise-category
822
     * relationship (category table in exercise settings)
823
     *
824
     * @param array $question_list
825
     * @param int $questionSelectionType
826
     * @return array
827
     */
828
    public function getQuestionListWithCategoryListFilteredByCategorySettings($question_list, $questionSelectionType)
829
    {
830
        $result = array(
831
            'question_list' => array(),
832
            'category_with_questions_list' => array()
833
        );
834
835
        // Order/random categories
836
        $cat = new TestCategory();
837
838
        // Setting category order.
839
840
        switch ($questionSelectionType) {
841
            case EX_Q_SELECTION_ORDERED: // 1
842
            case EX_Q_SELECTION_RANDOM:  // 2
843
                // This options are not allowed here.
844
                break;
845 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
846
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
847
                    $this,
848
                    $this->course['real_id'],
849
                    'title ASC',
850
                    false,
851
                    true
852
                );
853
854
                $questions_by_category = TestCategory::getQuestionsByCat(
855
                    $this->id,
856
                    $question_list,
857
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...itle ASC', false, true) on line 846 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...
858
                );
859
860
861
                $question_list = $this->pickQuestionsPerCategory(
862
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...itle ASC', false, true) on line 846 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...
863
                    $question_list,
864
                    $questions_by_category,
865
                    true,
866
                    false
867
                );
868
                break;
869
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
870 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
871
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
872
                    $this,
873
                    $this->course['real_id'],
874
                    null,
875
                    true,
876
                    true
877
                );
878
                $questions_by_category = TestCategory::getQuestionsByCat(
879
                    $this->id,
880
                    $question_list,
881
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 871 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...
882
                );
883
                $question_list = $this->pickQuestionsPerCategory(
884
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 871 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...
885
                    $question_list,
886
                    $questions_by_category,
887
                    true,
888
                    false
889
                );
890
            break;
891 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
892
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
893
                    $this,
894
                    $this->course['real_id'],
895
                    'title DESC',
896
                    false,
897
                    true
898
                );
899
                $questions_by_category = TestCategory::getQuestionsByCat(
900
                    $this->id,
901
                    $question_list,
902
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 892 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...
903
                );
904
                $question_list = $this->pickQuestionsPerCategory(
905
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 892 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...
906
                    $question_list,
907
                    $questions_by_category,
908
                    true,
909
                    true
910
                );
911
                break;
912
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
913 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
914
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
915
                    $this,
916
                    $this->course['real_id'],
917
                    null,
918
                    true,
919
                    true
920
                );
921
                $questions_by_category = TestCategory::getQuestionsByCat(
922
                    $this->id,
923
                    $question_list,
924
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 914 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...
925
                );
926
                $question_list = $this->pickQuestionsPerCategory(
927
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 914 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...
928
                    $question_list,
929
                    $questions_by_category,
930
                    true,
931
                    true
932
                );
933
                break;
934
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
935
                break;
936
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
937
                break;
938 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
939
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
940
                    $this,
941
                    $this->course['real_id'],
942
                    'root ASC, lft ASC',
943
                    false,
944
                    true
945
                );
946
                $questions_by_category = TestCategory::getQuestionsByCat(
947
                    $this->id,
948
                    $question_list,
949
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 939 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...
950
                );
951
                $question_list = $this->pickQuestionsPerCategory(
952
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 939 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...
953
                    $question_list,
954
                    $questions_by_category,
955
                    true,
956
                    false
957
                );
958
                break;
959 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
960
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
961
                    $this,
962
                    $this->course['real_id'],
963
                    'root, lft ASC',
964
                    false,
965
                    true
966
                );
967
                $questions_by_category = TestCategory::getQuestionsByCat(
968
                    $this->id,
969
                    $question_list,
970
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 960 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...
971
                );
972
                $question_list = $this->pickQuestionsPerCategory(
973
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 960 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...
974
                    $question_list,
975
                    $questions_by_category,
976
                    true,
977
                    true
978
                );
979
                break;
980
        }
981
982
        $result['question_list'] = isset($question_list) ? $question_list : array();
983
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : array();
984
985
        // Adding category info in the category list with question list:
986
987
        if (!empty($questions_by_category)) {
988
989
            /*$em = Database::getManager();
990
            $repo = $em->getRepository('ChamiloCoreBundle:CQuizCategory');*/
991
992
            $newCategoryList = array();
993
994
            foreach ($questions_by_category as $categoryId => $questionList) {
995
                $cat = new TestCategory($categoryId);
996
                $cat = (array)$cat;
997
                $cat['iid'] = $cat['id'];
998
                //*$cat['name'] = $cat['name'];
999
1000
                $categoryParentInfo = null;
1001
                // Parent is not set no loop here
1002
                if (!empty($cat['parent_id'])) {
1003
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
1004
                        $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...
1005
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
1006
                    } else {
1007
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
1008
                    }
1009
                    $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...
1010
                    $index = 0;
1011
                    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...
1012
                        //$index = 1;
1013
                    }
1014
                    /** @var \Chamilo\Entity\CQuizCategory $categoryParent*/
1015
1016
                    foreach ($path as $categoryParent) {
1017
                        $visibility = $categoryParent->getVisibility();
1018
1019
                        if ($visibility == 0) {
1020
                            $categoryParentId = $categoryId;
1021
                            $categoryTitle = $cat['title'];
1022
                            if (count($path) > 1) {
1023
                                continue;
1024
                            }
1025
                        } else {
1026
                            $categoryParentId = $categoryParent->getIid();
1027
                            $categoryTitle = $categoryParent->getTitle();
1028
                        }
1029
1030
                        $categoryParentInfo['id'] = $categoryParentId;
1031
                        $categoryParentInfo['iid'] = $categoryParentId;
1032
                        $categoryParentInfo['parent_path'] = null;
1033
                        $categoryParentInfo['title'] = $categoryTitle;
1034
                        $categoryParentInfo['name'] = $categoryTitle;
1035
                        $categoryParentInfo['parent_id'] = null;
1036
                        break;
1037
                    }
1038
                }
1039
                $cat['parent_info'] = $categoryParentInfo;
1040
                $newCategoryList[$categoryId] = array(
1041
                    'category' => $cat,
1042
                    'question_list' => $questionList
1043
                );
1044
            }
1045
1046
            $result['category_with_questions_list'] = $newCategoryList;
1047
        }
1048
        return $result;
1049
    }
1050
1051
    /**
1052
     * returns the array with the question ID list
1053
     *
1054
     * @author Olivier Brouckaert
1055
     * @return array - question ID list
1056
     */
1057
    public function selectQuestionList($from_db = false)
1058
    {
1059
        if ($from_db && !empty($this->id)) {
1060
            $nbQuestions = $this->getQuestionCount();
1061
            $questionSelectionType = $this->getQuestionSelectionType();
1062
1063
            switch ($questionSelectionType) {
1064
                case EX_Q_SELECTION_ORDERED:
1065
                    $questionList = $this->getQuestionOrderedList();
1066
                    break;
1067
                case EX_Q_SELECTION_RANDOM:
1068
                    // Not a random exercise, or if there are not at least 2 questions
1069
                    if ($this->random == 0 || $nbQuestions < 2) {
1070
                        $questionList = $this->getQuestionOrderedList();
1071
                    } else {
1072
                        $questionList = $this->selectRandomList();
1073
                    }
1074
                    break;
1075
                default:
1076
                    $questionList = $this->getQuestionOrderedList();
1077
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1078
                        $questionList,
1079
                        $questionSelectionType
1080
                    );
1081
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1082
                    $questionList = $result['question_list'];
1083
                    break;
1084
            }
1085
1086
            return $questionList;
1087
        }
1088
1089
        return $this->questionList;
1090
1091
    }
1092
1093
1094
    /**
1095
     * returns the number of questions in this exercise
1096
     *
1097
     * @author Olivier Brouckaert
1098
     * @return integer - number of questions
1099
     */
1100
    public function selectNbrQuestions()
1101
    {
1102
        return sizeof($this->questionList);
1103
    }
1104
1105
    /**
1106
     * @return int
1107
     */
1108
    public function selectPropagateNeg()
1109
    {
1110
        return $this->propagate_neg;
1111
    }
1112
1113
    /**
1114
     * @return int
1115
     */
1116
    public function selectSaveCorrectAnswers()
1117
    {
1118
        return $this->saveCorrectAnswers;
1119
    }
1120
1121
    /**
1122
     * Selects questions randomly in the question list
1123
     *
1124
     * @author Olivier Brouckaert
1125
     * @author Hubert Borderiou 15 nov 2011
1126
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1127
     *					 without randomizing, otherwise, returns the list with questions selected randomly
1128
     */
1129
    public function selectRandomList()
1130
    {
1131
        /*$nbQuestions	= $this->selectNbrQuestions();
1132
        $temp_list		= $this->questionList;
1133
1134
        //Not a random exercise, or if there are not at least 2 questions
1135
        if($this->random == 0 || $nbQuestions < 2) {
1136
            return $this->questionList;
1137
        }
1138
        if ($nbQuestions != 0) {
1139
            shuffle($temp_list);
1140
            $my_random_list = array_combine(range(1,$nbQuestions),$temp_list);
1141
            $my_question_list = array();
1142
            // $this->random == -1 if random with all questions
1143
            if ($this->random > 0) {
1144
                $i = 0;
1145
                foreach ($my_random_list as $item) {
1146
                    if ($i < $this->random) {
1147
                        $my_question_list[$i] = $item;
1148
                    } else {
1149
                        break;
1150
                    }
1151
                    $i++;
1152
                }
1153
            } else {
1154
                $my_question_list = $my_random_list;
1155
            }
1156
            return $my_question_list;
1157
        }*/
1158
1159
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1160
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1161
1162
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1163
1164
        $randomLimit = "LIMIT $random";
1165
        // Random all questions so no limit
1166
        if ($random == -1) {
1167
            $randomLimit = null;
1168
        }
1169
1170
        // @todo improve this query
1171
        $sql = "SELECT e.question_id
1172
                FROM $TBL_EXERCISE_QUESTION e INNER JOIN $TBL_QUESTIONS q
1173
                    ON (e.question_id= q.iid)
1174
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
1175
                ORDER BY RAND()
1176
                $randomLimit ";
1177
        $result = Database::query($sql);
1178
        $questionList = array();
1179
        while ($row = Database::fetch_object($result)) {
1180
            $questionList[] = $row->question_id;
1181
        }
1182
1183
        return $questionList;
1184
    }
1185
1186
    /**
1187
     * returns 'true' if the question ID is in the question list
1188
     *
1189
     * @author Olivier Brouckaert
1190
     * @param integer $questionId - question ID
1191
     * @return boolean - true if in the list, otherwise false
1192
     */
1193
    public function isInList($questionId)
1194
    {
1195
        if (is_array($this->questionList)) {
1196
            return in_array($questionId,$this->questionList);
1197
        } else {
1198
            return false;
1199
        }
1200
    }
1201
1202
    /**
1203
     * changes the exercise title
1204
     *
1205
     * @author Olivier Brouckaert
1206
     * @param string $title - exercise title
1207
     */
1208
    public function updateTitle($title)
1209
    {
1210
        $this->exercise=$title;
1211
    }
1212
1213
    /**
1214
     * changes the exercise max attempts
1215
     *
1216
     * @param int $attempts - exercise max attempts
1217
     */
1218
    public function updateAttempts($attempts)
1219
    {
1220
        $this->attempts=$attempts;
1221
    }
1222
1223
    /**
1224
     * changes the exercise feedback type
1225
     *
1226
     * @param int $feedback_type
1227
     */
1228
    public function updateFeedbackType($feedback_type)
1229
    {
1230
        $this->feedback_type=$feedback_type;
1231
    }
1232
1233
    /**
1234
     * changes the exercise description
1235
     *
1236
     * @author Olivier Brouckaert
1237
     * @param string $description - exercise description
1238
     */
1239
    public function updateDescription($description)
1240
    {
1241
        $this->description=$description;
1242
    }
1243
1244
    /**
1245
     * changes the exercise expired_time
1246
     *
1247
     * @author Isaac flores
1248
     * @param int $expired_time The expired time of the quiz
1249
     */
1250
    public function updateExpiredTime($expired_time)
1251
    {
1252
        $this->expired_time = $expired_time;
1253
    }
1254
1255
    /**
1256
     * @param $value
1257
     */
1258
    public function updatePropagateNegative($value)
1259
    {
1260
        $this->propagate_neg = $value;
1261
    }
1262
1263
    /**
1264
     * @param $value int
1265
     */
1266
    public function updateSaveCorrectAnswers($value)
1267
    {
1268
        $this->saveCorrectAnswers = $value;
1269
    }
1270
1271
    /**
1272
     * @param $value
1273
     */
1274
    public function updateReviewAnswers($value)
1275
    {
1276
        $this->review_answers = isset($value) && $value ? true : false;
1277
    }
1278
1279
    /**
1280
     * @param $value
1281
     */
1282
    public function updatePassPercentage($value)
1283
    {
1284
        $this->pass_percentage = $value;
1285
    }
1286
1287
    /**
1288
     * @param string $text
1289
     */
1290
    public function updateEmailNotificationTemplate($text)
1291
    {
1292
        $this->emailNotificationTemplate = $text;
1293
    }
1294
1295
    /**
1296
     * @param string $text
1297
     */
1298
    public function updateEmailNotificationTemplateToUser($text)
1299
    {
1300
        $this->emailNotificationTemplateToUser = $text;
1301
    }
1302
1303
    /**
1304
     * @param string $value
1305
     */
1306
    public function setNotifyUserByEmail($value)
1307
    {
1308
        $this->notifyUserByEmail = $value;
1309
    }
1310
1311
    /**
1312
     * @param int $value
1313
     */
1314
    public function updateEndButton($value)
1315
    {
1316
        $this->endButton = intval($value);
1317
    }
1318
1319
    /**
1320
     * @param string $value
1321
     */
1322
    public function setOnSuccessMessage($value)
1323
    {
1324
        $this->onSuccessMessage = $value;
1325
    }
1326
1327
    /**
1328
     * @param string $value
1329
     */
1330
    public function setOnFailedMessage($value)
1331
    {
1332
        $this->onFailedMessage = $value;
1333
    }
1334
1335
    /**
1336
     * @param $value
1337
     */
1338
    public function setModelType($value)
1339
    {
1340
        $this->modelType = intval($value);
1341
    }
1342
1343
    /**
1344
     * @param int $value
1345
     */
1346
    public function setQuestionSelectionType($value)
1347
    {
1348
        $this->questionSelectionType = intval($value);
1349
    }
1350
1351
    /**
1352
     * @return int
1353
     */
1354
    public function getQuestionSelectionType()
1355
    {
1356
        return $this->questionSelectionType;
1357
    }
1358
1359
    /**
1360
     * @param array $categories
1361
     */
1362
    public function updateCategories($categories)
1363
    {
1364
        if (!empty($categories)) {
1365
            $categories = array_map('intval', $categories);
1366
            $this->categories = $categories;
1367
        }
1368
    }
1369
1370
    /**
1371
     * changes the exercise sound file
1372
     *
1373
     * @author Olivier Brouckaert
1374
     * @param string $sound - exercise sound file
1375
     * @param string $delete - ask to delete the file
1376
     */
1377
    public function updateSound($sound,$delete)
1378
    {
1379
        global $audioPath, $documentPath;
1380
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1381
1382
        if ($sound['size'] && (strstr($sound['type'],'audio') || strstr($sound['type'],'video'))) {
1383
            $this->sound=$sound['name'];
1384
1385
            if (@move_uploaded_file($sound['tmp_name'],$audioPath.'/'.$this->sound)) {
1386
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1387
                        WHERE c_id = ".$this->course_id." AND path='".str_replace($documentPath,'',$audioPath).'/'.$this->sound."'";
1388
                $result = Database::query($sql);
1389
1390
                if (!Database::num_rows($result)) {
1391
                    $id = add_document(
1392
                        $this->course,
1393
                        str_replace($documentPath,'',$audioPath).'/'.$this->sound,
1394
                        'file',
1395
                        $sound['size'],
1396
                        $sound['name']
1397
                    );
1398
                    api_item_property_update(
1399
                        $this->course,
1400
                        TOOL_DOCUMENT,
1401
                        $id,
1402
                        'DocumentAdded',
1403
                        api_get_user_id()
1404
                    );
1405
                    item_property_update_on_folder(
1406
                        $this->course,
1407
                        str_replace($documentPath, '', $audioPath),
1408
                        api_get_user_id()
1409
                    );
1410
                }
1411
            }
1412
        } elseif($delete && is_file($audioPath.'/'.$this->sound)) {
1413
            $this->sound='';
1414
        }
1415
    }
1416
1417
    /**
1418
     * changes the exercise type
1419
     *
1420
     * @author Olivier Brouckaert
1421
     * @param integer $type - exercise type
1422
     */
1423
    public function updateType($type)
1424
    {
1425
        $this->type=$type;
1426
    }
1427
1428
    /**
1429
     * sets to 0 if questions are not selected randomly
1430
     * if questions are selected randomly, sets the draws
1431
     *
1432
     * @author Olivier Brouckaert
1433
     * @param integer $random - 0 if not random, otherwise the draws
1434
     */
1435
    public function setRandom($random)
1436
    {
1437
        /*if ($random == 'all') {
1438
            $random = $this->selectNbrQuestions();
1439
        }*/
1440
        $this->random = $random;
1441
    }
1442
1443
    /**
1444
     * sets to 0 if answers are not selected randomly
1445
     * if answers are selected randomly
1446
     * @author Juan Carlos Rana
1447
     * @param integer $random_answers - random answers
1448
     */
1449
    public function updateRandomAnswers($random_answers)
1450
    {
1451
        $this->random_answers = $random_answers;
1452
    }
1453
1454
    /**
1455
     * enables the exercise
1456
     *
1457
     * @author Olivier Brouckaert
1458
     */
1459
    public function enable()
1460
    {
1461
        $this->active=1;
1462
    }
1463
1464
    /**
1465
     * disables the exercise
1466
     *
1467
     * @author Olivier Brouckaert
1468
     */
1469
    public function disable()
1470
    {
1471
        $this->active=0;
1472
    }
1473
1474
    /**
1475
     * Set disable results
1476
     */
1477
    public function disable_results()
1478
    {
1479
        $this->results_disabled = true;
1480
    }
1481
1482
    /**
1483
     * Enable results
1484
     */
1485
    public function enable_results()
1486
    {
1487
        $this->results_disabled = false;
1488
    }
1489
1490
    /**
1491
     * @param int $results_disabled
1492
     */
1493
    public function updateResultsDisabled($results_disabled)
1494
    {
1495
        $this->results_disabled = intval($results_disabled);
1496
    }
1497
1498
    /**
1499
     * updates the exercise in the data base
1500
     *
1501
     * @author Olivier Brouckaert
1502
     */
1503
    public function save($type_e = '')
1504
    {
1505
        $_course = $this->course;
1506
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1507
1508
        $id = $this->id;
1509
        $exercise = $this->exercise;
1510
        $description = $this->description;
1511
        $sound = $this->sound;
1512
        $type = $this->type;
1513
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1514
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1515
        $random = $this->random;
1516
        $random_answers = $this->random_answers;
1517
        $active = $this->active;
1518
        $propagate_neg = $this->propagate_neg;
1519
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? true : false;
1520
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1521
        $randomByCat = intval($this->randomByCat);
1522
        $text_when_finished = $this->text_when_finished;
1523
        $display_category_name = intval($this->display_category_name);
1524
        $pass_percentage = intval($this->pass_percentage);
1525
        $session_id = $this->sessionId;
1526
1527
        //If direct we do not show results
1528
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1529
            $results_disabled = 0;
1530
        } else {
1531
            $results_disabled = intval($this->results_disabled);
1532
        }
1533
1534
        $expired_time = intval($this->expired_time);
1535
1536
        // Exercise already exists
1537
        if ($id) {
1538
            // we prepare date in the database using the api_get_utc_datetime() function
1539 View Code Duplication
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
1540
                $start_time = $this->start_time;
1541
            } else {
1542
                $start_time = '0000-00-00 00:00:00';
1543
            }
1544
1545 View Code Duplication
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
1546
                $end_time = $this->end_time;
1547
            } else {
1548
                $end_time = '0000-00-00 00:00:00';
1549
            }
1550
1551
            $params = [
1552
                'title' => $exercise,
1553
                'description' => $description,
1554
            ];
1555
1556
            $paramsExtra = [];
1557
            if ($type_e != 'simple') {
1558
                $paramsExtra = [
1559
                    'sound' => $sound,
1560
                    'type' => $type,
1561
                    'random' => $random,
1562
                    'random_answers' => $random_answers,
1563
                    'active' => $active,
1564
                    'feedback_type' => $feedback_type,
1565
                    'start_time' => $start_time,
1566
                    'end_time' => $end_time,
1567
                    'max_attempt' => $attempts,
1568
                    'expired_time' => $expired_time,
1569
                    'propagate_neg' => $propagate_neg,
1570
                    'save_correct_answers' => $saveCorrectAnswers,
1571
                    'review_answers' => $review_answers,
1572
                    'random_by_category' => $randomByCat,
1573
                    'text_when_finished' => $text_when_finished,
1574
                    'display_category_name' => $display_category_name,
1575
                    'pass_percentage' => $pass_percentage,
1576
                    'results_disabled' => $results_disabled,
1577
                    'question_selection_type' => $this->getQuestionSelectionType()
1578
                ];
1579
            }
1580
1581
            $params = array_merge($params, $paramsExtra);
1582
1583
            Database::update(
1584
                $TBL_EXERCISES,
1585
                $params,
1586
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1587
            );
1588
1589
            // update into the item_property table
1590
            api_item_property_update(
1591
                $_course,
1592
                TOOL_QUIZ,
1593
                $id,
1594
                'QuizUpdated',
1595
                api_get_user_id()
1596
            );
1597
1598
            if (api_get_setting('search.search_enabled') == 'true') {
1599
                $this->search_engine_edit();
1600
            }
1601
        } else {
1602
            // Creates a new exercise
1603
1604
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1605
            // for date because, bellow, we call function api_set_default_visibility()
1606
            // In this function, api_set_default_visibility,
1607
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1608
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1609 View Code Duplication
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
1610
                $start_time = $this->start_time;
1611
            } else {
1612
                $start_time = '0000-00-00 00:00:00';
1613
            }
1614
1615 View Code Duplication
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
1616
                $end_time = $this->end_time;
1617
            } else {
1618
                $end_time = '0000-00-00 00:00:00';
1619
            }
1620
1621
            $params = [
1622
                'c_id' => $this->course_id,
1623
                'start_time' => $start_time,
1624
                'end_time' => $end_time,
1625
                'title' => $exercise,
1626
                'description' => $description,
1627
                'sound' => $sound,
1628
                'type' => $type,
1629
                'random' => $random,
1630
                'random_answers' => $random_answers,
1631
                'active' => $active,
1632
                'results_disabled' => $results_disabled,
1633
                'max_attempt' => $attempts,
1634
                'feedback_type' => $feedback_type,
1635
                'expired_time' => $expired_time,
1636
                'session_id' => $session_id,
1637
                'review_answers' => $review_answers,
1638
                'random_by_category' => $randomByCat,
1639
                'text_when_finished' => $text_when_finished,
1640
                'display_category_name' => $display_category_name,
1641
                'pass_percentage' => $pass_percentage,
1642
                'save_correct_answers' => $saveCorrectAnswers
1643
            ];
1644
1645
            $this->id = Database::insert($TBL_EXERCISES, $params);
1646
1647
            if ($this->id) {
1648
1649
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1650
                Database::query($sql);
1651
1652
                $sql = "UPDATE $TBL_EXERCISES
1653
                        SET question_selection_type= ".intval($this->getQuestionSelectionType())."
1654
                        WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1655
                Database::query($sql);
1656
1657
                // insert into the item_property table
1658
                api_item_property_update(
1659
                    $this->course,
1660
                    TOOL_QUIZ,
1661
                    $this->id,
1662
                    'QuizAdded',
1663
                    api_get_user_id()
1664
                );
1665
1666
                // This function save the quiz again, carefull about start_time
1667
                // and end_time if you remove this line (see above)
1668
                api_set_default_visibility(
1669
                    $this->id,
1670
                    TOOL_QUIZ,
1671
                    null,
1672
                    $this->course
1673
                );
1674
1675
                if (api_get_setting(
1676
                        'search.search_enabled'
1677
                    ) == 'true' && extension_loaded('xapian')
1678
                ) {
1679
                    $this->search_engine_save();
1680
                }
1681
            }
1682
        }
1683
1684
        $this->save_categories_in_exercise($this->categories);
1685
1686
        // Updates the question position
1687
        $this->update_question_positions();
1688
    }
1689
1690
    /**
1691
     * Updates question position
1692
     */
1693
    public function update_question_positions()
1694
    {
1695
        $quiz_question_table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1696
        //Fixes #3483 when updating order
1697
        $question_list = $this->selectQuestionList(true);
1698
        if (!empty($question_list)) {
1699
            foreach ($question_list as $position => $questionId) {
1700
                $sql = "UPDATE $quiz_question_table SET
1701
                        question_order ='".intval($position)."'
1702
                        WHERE
1703
                            c_id = ".$this->course_id." AND
1704
                            question_id = ".intval($questionId)." AND
1705
                            exercice_id=".intval($this->id);
1706
                Database::query($sql);
1707
            }
1708
        }
1709
    }
1710
1711
    /**
1712
     * Adds a question into the question list
1713
     *
1714
     * @author Olivier Brouckaert
1715
     * @param integer $questionId - question ID
1716
     * @return boolean - true if the question has been added, otherwise false
1717
     */
1718
    public function addToList($questionId)
1719
    {
1720
        // checks if the question ID is not in the list
1721
        if (!$this->isInList($questionId)) {
1722
            // selects the max position
1723
            if (!$this->selectNbrQuestions()) {
1724
                $pos = 1;
1725
            } else {
1726
                if (is_array($this->questionList)) {
1727
                    $pos = max(array_keys($this->questionList)) + 1;
1728
                }
1729
            }
1730
            $this->questionList[$pos] = $questionId;
1731
1732
            return true;
1733
        }
1734
1735
        return false;
1736
    }
1737
1738
    /**
1739
     * removes a question from the question list
1740
     *
1741
     * @author Olivier Brouckaert
1742
     * @param integer $questionId - question ID
1743
     * @return boolean - true if the question has been removed, otherwise false
1744
     */
1745
    public function removeFromList($questionId)
1746
    {
1747
        // searches the position of the question ID in the list
1748
        $pos = array_search($questionId,$this->questionList);
1749
1750
        // question not found
1751
        if ($pos === false) {
1752
            return false;
1753
        } else {
1754
            // dont reduce the number of random question if we use random by category option, or if
1755
            // random all questions
1756
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1757
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1758
                    $this->random -= 1;
1759
                    $this->save();
1760
                }
1761
            }
1762
            // deletes the position from the array containing the wanted question ID
1763
            unset($this->questionList[$pos]);
1764
1765
            return true;
1766
        }
1767
    }
1768
1769
    /**
1770
     * deletes the exercise from the database
1771
     * Notice : leaves the question in the data base
1772
     *
1773
     * @author Olivier Brouckaert
1774
     */
1775
    public function delete()
1776
    {
1777
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1778
        $sql = "UPDATE $TBL_EXERCISES SET active='-1'
1779
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
1780
        Database::query($sql);
1781
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'QuizDeleted', api_get_user_id());
1782
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'delete', api_get_user_id());
1783
1784
        if (api_get_setting(
1785
                'search.search_enabled'
1786
            ) == 'true' && extension_loaded('xapian')
1787
        ) {
1788
            $this->search_engine_delete();
1789
        }
1790
    }
1791
1792
    /**
1793
     * Creates the form to create / edit an exercise
1794
     * @param FormValidator $form
1795
     */
1796
    public function createForm($form, $type='full')
1797
    {
1798
        if (empty($type)) {
1799
            $type = 'full';
1800
        }
1801
1802
        // form title
1803
        if (!empty($_GET['exerciseId'])) {
1804
            $form_title = get_lang('ModifyExercise');
1805
        } else {
1806
            $form_title = get_lang('NewEx');
1807
        }
1808
1809
        $form->addElement('header', $form_title);
1810
1811
        // Title.
1812
        $form->addElement(
1813
            'text',
1814
            'exerciseTitle',
1815
            get_lang('ExerciseName'),
1816
            array('id' => 'exercise_title')
1817
        );
1818
1819
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1820
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1821
1822
        $editor_config = array(
1823
            'ToolbarSet' => 'TestQuestionDescription',
1824
            'Width' => '100%',
1825
            'Height' => '150',
1826
        );
1827
        if (is_array($type)){
1828
            $editor_config = array_merge($editor_config, $type);
1829
        }
1830
1831
        $form->addHtmlEditor(
1832
            'exerciseDescription',
1833
            get_lang('ExerciseDescription'),
1834
            false,
1835
            false,
1836
            $editor_config
1837
        );
1838
1839
        if ($type == 'full') {
1840
            //Can't modify a DirectFeedback question
1841
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1842
                // feedback type
1843
                $radios_feedback = array();
1844
                $radios_feedback[] = $form->createElement(
1845
                    'radio',
1846
                    'exerciseFeedbackType',
1847
                    null,
1848
                    get_lang('ExerciseAtTheEndOfTheTest'),
1849
                    '0',
1850
                    array(
1851
                        'id' => 'exerciseType_0',
1852
                        'onclick' => 'check_feedback()',
1853
                    )
1854
                );
1855
1856 View Code Duplication
                if (api_get_setting('exercise.enable_quiz_scenario') == 'true') {
1857
                    //Can't convert a question from one feedback to another if there is more than 1 question already added
1858
                    if ($this->selectNbrQuestions() == 0) {
1859
                        $radios_feedback[] = $form->createElement(
1860
                            'radio',
1861
                            'exerciseFeedbackType',
1862
                            null,
1863
                            get_lang('DirectFeedback'),
1864
                            '1',
1865
                            array(
1866
                                'id' => 'exerciseType_1',
1867
                                'onclick' => 'check_direct_feedback()',
1868
                            )
1869
                        );
1870
                    }
1871
                }
1872
1873
                $radios_feedback[] = $form->createElement(
1874
                    'radio',
1875
                    'exerciseFeedbackType',
1876
                    null,
1877
                    get_lang('NoFeedback'),
1878
                    '2',
1879
                    array('id' => 'exerciseType_2')
1880
                );
1881
                $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')), '');
1882
1883
                // Type of results display on the final page
1884
                $radios_results_disabled = array();
1885
                $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1886
                $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
1887
                $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2', array('id'=>'result_disabled_2'));
1888
                //$radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ExamModeWithFinalScoreShowOnlyFinalScoreWithCategoriesIfAvailable'),  '3', array('id'=>'result_disabled_3','onclick' => 'check_results_disabled()'));
1889
1890
                $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'), '');
1891
1892
                // Type of questions disposition on page
1893
                $radios = array();
1894
1895
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1896
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1897
1898
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'), '');
1899
1900
            } else {
1901
                // if is Directfeedback but has not questions we can allow to modify the question type
1902
                if ($this->selectNbrQuestions() == 0) {
1903
1904
                    // feedback type
1905
                    $radios_feedback = array();
1906
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('ExerciseAtTheEndOfTheTest'),'0',array('id' =>'exerciseType_0', 'onclick' => 'check_feedback()'));
1907
1908 View Code Duplication
                    if (api_get_setting('exercise.enable_quiz_scenario') == 'true') {
1909
                        $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('DirectFeedback'), '1', array('id' =>'exerciseType_1' , 'onclick' => 'check_direct_feedback()'));
1910
                    }
1911
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('NoFeedback'),'2',array('id' =>'exerciseType_2'));
1912
                    $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
1913
1914
                    //$form->addElement('select', 'exerciseFeedbackType',get_lang('FeedbackType'),$feedback_option,'onchange="javascript:feedbackselection()"');
1915
                    $radios_results_disabled = array();
1916
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1917
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
1918
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
1919
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
1920
1921
                    // Type of questions disposition on page
1922
                    $radios = array();
1923
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1');
1924
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2');
1925
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
1926
1927
                } else {
1928
                    //Show options freeze
1929
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1930
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
1931
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
1932
                    $result_disable_group = $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
1933
                    $result_disable_group->freeze();
1934
1935
                    //we force the options to the DirectFeedback exercisetype
1936
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
1937
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
1938
1939
                    // Type of questions disposition on page
1940
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1941
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1942
1943
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'), '');
1944
                    $type_group->freeze();
1945
                }
1946
            }
1947
1948
            if (true) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
1949
                $option = array(
1950
                    EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
1951
            // number of random question
1952
                    EX_Q_SELECTION_RANDOM => get_lang('Random'),
1953
1954
                    'per_categories' => '--------'.get_lang(
1955
                            'UsingCategories'
1956
                        ).'----------',
1957
1958
                    // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
1959
                    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang(
1960
                        'OrderedCategoriesAlphabeticallyWithQuestionsOrdered'
1961
                    ),
1962
                    // A 123 B 456 C 78 (0, 1, all)
1963
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang(
1964
                        'RandomCategoriesWithQuestionsOrdered'
1965
                    ),
1966
                    // C 78 B 456 A 123
1967
1968
                    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang(
1969
                        'OrderedCategoriesAlphabeticallyWithRandomQuestions'
1970
                    ),
1971
                    // A 321 B 654 C 87
1972
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang(
1973
                        'RandomCategoriesWithRandomQuestions'
1974
                    ),
1975
                    //C 87 B 654 A 321
1976
1977
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
1978
                    /*    B 456 C 78 A 123
1979
                            456 78 123
1980
                            123 456 78
1981
                    */
1982
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
1983
                    /*
1984
                        A 123 B 456 C 78
1985
                        B 456 C 78 A 123
1986
                        B 654 C 87 A 321
1987
                        654 87 321
1988
                        165 842 73
1989
                    */
1990
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
1991
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
1992
                );
1993
1994
                $form->addElement(
1995
                    'select',
1996
                    'question_selection_type',
1997
                    array(get_lang('QuestionSelection')),
1998
                    $option,
1999
                    array(
2000
                        'id' => 'questionSelection',
2001
                        'onclick' => 'checkQuestionSelection()'
2002
                    )
2003
                );
2004
2005
                $displayMatrix = 'none';
2006
                $displayRandom = 'none';
2007
                $selectionType = $this->getQuestionSelectionType();
2008
                switch ($selectionType) {
2009
                    case EX_Q_SELECTION_RANDOM:
2010
                        $displayRandom = 'block';
2011
                        break;
2012
                    case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2013
                        $displayMatrix = 'block';
2014
                        break;
2015
                }
2016
2017
                $form->addElement(
2018
                    'html',
2019
                    '<div id="hidden_random" style="display:'.$displayRandom.'">'
2020
                );
2021
                // Number of random question.
2022
                $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
2023
                $option = range(0, $max);
2024
                $option[0] = get_lang('No');
2025
                $option[-1] = get_lang('AllQuestionsShort');
2026
                $form->addElement(
2027
                    'select',
2028
                    'randomQuestions',
2029
                    array(
2030
                        get_lang('RandomQuestions'),
2031
                        get_lang('RandomQuestionsHelp')
2032
                    ),
2033
                    $option,
2034
                    array('id' => 'randomQuestions')
2035
                );
2036
                $form->addElement('html', '</div>');
2037
2038
                $form->addElement(
2039
                    'html',
2040
                    '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2041
                );
2042
2043
                // Category selection.
2044
                $cat = new TestCategory();
2045
                $cat_form = $cat->returnCategoryForm($this);
2046
                $form->addElement('label', null, $cat_form);
2047
                $form->addElement('html', '</div>');
2048
2049
                // Category name.
2050
                $radio_display_cat_name = array(
2051
                    $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2052
                    $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0')
2053
                );
2054
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'), '');
2055
2056
                // Random answers.
2057
                $radios_random_answers = array(
2058
                    $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2059
                    $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0')
2060
                );
2061
                $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'), '');
2062
2063
                // Hide question title.
2064
                $group = array(
2065
                    $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2066
                    $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0')
2067
                );
2068
                $form->addGroup($group, null, get_lang('HideQuestionTitle'), '');
2069
            } 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...
2070
2071
                // number of random question
2072
                /*
2073
            $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10 ;
2074
            $option = range(0,$max);
2075
            $option[0] = get_lang('No');
2076
            $option[-1] = get_lang('AllQuestionsShort');
2077
            $form->addElement('select', 'randomQuestions',array(get_lang('RandomQuestions'), get_lang('RandomQuestionsHelp')), $option, array('id'=>'randomQuestions'));
2078
2079
            // Random answers
2080
            $radios_random_answers = array();
2081
            $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'),'1');
2082
            $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('No'),'0');
2083
            $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'), '');
2084
2085
            // Random by category
2086
            $form->addElement('html','<div class="clear">&nbsp;</div>');
2087
            $radiocat = array();
2088
            $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesShuffled'),'1');
2089
            $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesSorted'),'2');
2090
            $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('No'),'0');
2091
            $radioCatGroup = $form->addGroup($radiocat, null, get_lang('RandomQuestionByCategory'), '');
2092
            $form->addElement('html','<div class="clear">&nbsp;</div>');
2093
2094
            // add the radio display the category name for student
2095
            $radio_display_cat_name = array();
2096
            $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1');
2097
            $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0');
2098
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'), '');*/
2099
            }
2100
            // Attempts
2101
            $attempt_option = range(0, 10);
2102
            $attempt_option[0] = get_lang('Infinite');
2103
2104
            $form->addElement(
2105
                'select',
2106
                'exerciseAttempts',
2107
                get_lang('ExerciseAttempts'),
2108
                $attempt_option,
2109
                ['id' => 'exerciseAttempts']
2110
            );
2111
2112
            // Exercise time limit
2113
            $form->addElement('checkbox', 'activate_start_date_check',null, get_lang('EnableStartTime'), array('onclick' => 'activate_start_date()'));
2114
2115
            $var = Exercise::selectTimeLimit();
2116
2117
            if (($this->start_time != '0000-00-00 00:00:00'))
2118
                $form->addElement('html','<div id="start_date_div" style="display:block;">');
2119
            else
2120
                $form->addElement('html','<div id="start_date_div" style="display:none;">');
2121
2122
            $form->addElement('date_time_picker', 'start_time');
2123
2124
            $form->addElement('html','</div>');
2125
2126
            $form->addElement('checkbox', 'activate_end_date_check', null , get_lang('EnableEndTime'), array('onclick' => 'activate_end_date()'));
2127
2128
            if (($this->end_time != '0000-00-00 00:00:00'))
2129
                $form->addElement('html','<div id="end_date_div" style="display:block;">');
2130
            else
2131
                $form->addElement('html','<div id="end_date_div" style="display:none;">');
2132
2133
            $form->addElement('date_time_picker', 'end_time');
2134
            $form->addElement('html','</div>');
2135
2136
            //$check_option=$this->selectType();
2137
            $diplay = 'block';
2138
            $form->addElement('checkbox', 'propagate_neg', null, get_lang('PropagateNegativeResults'));
2139
            $form->addCheckBox(
2140
                'save_correct_answers',
2141
                null,
2142
                get_lang('Save the correct answers for the next attempt')
2143
            );
2144
            $form->addElement('html','<div class="clear">&nbsp;</div>');
2145
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2146
2147
            $form->addElement('html','<div id="divtimecontrol"  style="display:'.$diplay.';">');
2148
2149
            //Timer control
2150
            //$time_hours_option = range(0,12);
2151
            //$time_minutes_option = range(0,59);
2152
            $form->addElement(
2153
                'checkbox',
2154
                'enabletimercontrol',
2155
                null,
2156
                get_lang('EnableTimerControl'),
2157
                array(
2158
                    'onclick' => 'option_time_expired()',
2159
                    'id' => 'enabletimercontrol',
2160
                    'onload' => 'check_load_time()',
2161
                )
2162
            );
2163
2164
            $expired_date = (int)$this->selectExpiredTime();
2165
2166
            if (($expired_date!='0')) {
2167
                $form->addElement('html','<div id="timercontrol" style="display:block;">');
2168
            } else {
2169
                $form->addElement('html','<div id="timercontrol" style="display:none;">');
2170
            }
2171
            $form->addText(
2172
                'enabletimercontroltotalminutes',
2173
                get_lang('ExerciseTotalDurationInMinutes'),
2174
                false,
2175
                [
2176
                    'id' => 'enabletimercontroltotalminutes',
2177
                    'cols-size' => [2, 2, 8]
2178
                ]
2179
            );
2180
            $form->addElement('html','</div>');
2181
2182
            $form->addElement(
2183
                'text',
2184
                'pass_percentage',
2185
                array(get_lang('PassPercentage'), null, '%'),
2186
                array('id' => 'pass_percentage')
2187
            );
2188
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2189
2190
            // add the text_when_finished textbox
2191
            $form->addHtmlEditor(
2192
                'text_when_finished',
2193
                get_lang('TextWhenFinished'),
2194
                false,
2195
                false,
2196
                $editor_config
2197
            );
2198
2199
            $defaults = array();
2200
2201
            if (api_get_setting('search.search_enabled') === 'true') {
2202
                require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2203
2204
                $form->addElement ('checkbox', 'index_document','', get_lang('SearchFeatureDoIndexDocument'));
2205
                $form->addElement ('select_language', 'language', get_lang('SearchFeatureDocumentLanguage'));
2206
2207
                $specific_fields = get_specific_field_list();
2208
2209
                foreach ($specific_fields as $specific_field) {
2210
                    $form->addElement ('text', $specific_field['code'], $specific_field['name']);
2211
                    $filter = array(
2212
                        'c_id' => api_get_course_int_id(),
2213
                        'field_id' => $specific_field['id'],
2214
                        'ref_id' => $this->id,
2215
                        'tool_id' => "'" . TOOL_QUIZ . "'"
2216
                    );
2217
                    $values = get_specific_field_values_list($filter, array('value'));
2218
                    if ( !empty($values) ) {
2219
                        $arr_str_values = array();
2220
                        foreach ($values as $value) {
2221
                            $arr_str_values[] = $value['value'];
2222
                        }
2223
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2224
                    }
2225
                }
2226
            }
2227
2228
            $form->addElement('html','</div>');  //End advanced setting
2229
            $form->addElement('html','</div>');
2230
        }
2231
2232
        // submit
2233
        if (isset($_GET['exerciseId'])) {
2234
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2235
        } else {
2236
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2237
        }
2238
2239
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2240
2241
        if ($type == 'full') {
2242
            // rules
2243
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2244
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2245
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2246
        }
2247
2248
        // defaults
2249
        if ($type=='full') {
2250
            if ($this->id > 0) {
2251
                if ($this->random > $this->selectNbrQuestions()) {
2252
                    $defaults['randomQuestions'] =  $this->selectNbrQuestions();
2253
                } else {
2254
                    $defaults['randomQuestions'] = $this->random;
2255
                }
2256
2257
                $defaults['randomAnswers'] = $this->selectRandomAnswers();
2258
                $defaults['exerciseType'] = $this->selectType();
2259
                $defaults['exerciseTitle'] = $this->get_formated_title();
2260
                $defaults['exerciseDescription'] = $this->selectDescription();
2261
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2262
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2263
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2264
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2265
                $defaults['save_correct_answers'] = $this->selectSaveCorrectAnswers();
2266
                $defaults['review_answers'] = $this->review_answers;
2267
                $defaults['randomByCat'] = $this->selectRandomByCat();
2268
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2269
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2270
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2271
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2272
2273
                if (($this->start_time != '0000-00-00 00:00:00')) {
2274
                    $defaults['activate_start_date_check'] = 1;
2275
                }
2276
                if ($this->end_time != '0000-00-00 00:00:00') {
2277
                    $defaults['activate_end_date_check'] = 1;
2278
                }
2279
2280
                $defaults['start_time'] = ($this->start_time!='0000-00-00 00:00:00') ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2281
                $defaults['end_time'] = ($this->end_time!='0000-00-00 00:00:00') ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time()+84600);
2282
2283
                // Get expired time
2284
                if ($this->expired_time != '0') {
2285
                    $defaults['enabletimercontrol'] = 1;
2286
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2287
                } else {
2288
                    $defaults['enabletimercontroltotalminutes'] = 0;
2289
                }
2290
            } else {
2291
                $defaults['exerciseType'] = 2;
2292
                $defaults['exerciseAttempts'] = 0;
2293
                $defaults['randomQuestions'] = 0;
2294
                $defaults['randomAnswers'] = 0;
2295
                $defaults['exerciseDescription'] = '';
2296
                $defaults['exerciseFeedbackType'] = 0;
2297
                $defaults['results_disabled'] = 0;
2298
                $defaults['randomByCat'] = 0;
2299
                $defaults['text_when_finished'] = '';
2300
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2301
                $defaults['display_category_name'] = 1;
2302
                $defaults['end_time']   = date('Y-m-d 12:00:00', time()+84600);
2303
                $defaults['pass_percentage'] = '';
2304
                $defaults['end_button'] = $this->selectEndButton();
2305
                $defaults['question_selection_type'] = 1;
2306
                $defaults['hide_question_title'] = 0;
2307
                $defaults['on_success_message'] = null;
2308
                $defaults['on_failed_message'] = null;
2309
            }
2310
        } else {
2311
            $defaults['exerciseTitle'] = $this->selectTitle();
2312
            $defaults['exerciseDescription'] = $this->selectDescription();
2313
        }
2314
        if (api_get_setting('search.search_enabled') === 'true') {
2315
            $defaults['index_document'] = 'checked="checked"';
2316
        }
2317
        $form->setDefaults($defaults);
2318
2319
        // Freeze some elements.
2320
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2321
            $elementsToFreeze = array(
2322
                'randomQuestions',
2323
                //'randomByCat',
2324
                'exerciseAttempts',
2325
                'propagate_neg',
2326
                'enabletimercontrol',
2327
                'review_answers'
2328
            );
2329
2330
            foreach ($elementsToFreeze as $elementName) {
2331
                /** @var HTML_QuickForm_element $element */
2332
                $element = $form->getElement($elementName);
2333
                $element->freeze();
2334
            }
2335
2336
            //$radioCatGroup->freeze();
2337
        }
2338
    }
2339
2340
    /**
2341
     * function which process the creation of exercises
2342
     * @param FormValidator $form
2343
     * @param string
2344
     */
2345
    public function processCreation($form, $type = '')
2346
    {
2347
        $this->updateTitle(Exercise::format_title_variable($form->getSubmitValue('exerciseTitle')));
2348
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2349
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2350
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2351
        $this->updateType($form->getSubmitValue('exerciseType'));
2352
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2353
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2354
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2355
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2356
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2357
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2358
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2359
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2360
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2361
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2362
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2363
        $this->updateCategories($form->getSubmitValue('category'));
2364
        $this->updateEndButton($form->getSubmitValue('end_button'));
2365
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2366
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2367
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2368
        $this->updateEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2369
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2370
        $this->setModelType($form->getSubmitValue('model_type'));
2371
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2372
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2373
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2374
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2375
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2376
2377
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2378
            $start_time = $form->getSubmitValue('start_time');
2379
            $this->start_time = api_get_utc_datetime($start_time);
2380
        } else {
2381
            $this->start_time = '0000-00-00 00:00:00';
2382
        }
2383
2384
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2385
            $end_time = $form->getSubmitValue('end_time');
2386
            $this->end_time = api_get_utc_datetime($end_time);
2387
        } else {
2388
            $this->end_time   = '0000-00-00 00:00:00';
2389
        }
2390
2391
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2392
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2393
            if ($this->expired_time == 0) {
2394
                $this->expired_time = $expired_total_time;
2395
            }
2396
        } else {
2397
            $this->expired_time = 0;
2398
        }
2399
2400
        if ($form->getSubmitValue('randomAnswers') == 1) {
2401
            $this->random_answers=1;
2402
        } else {
2403
            $this->random_answers=0;
2404
        }
2405
        $this->save($type);
2406
    }
2407
2408
    function search_engine_save()
2409
    {
2410
        if ($_POST['index_document'] != 1) {
2411
            return;
2412
        }
2413
2414
        $em = Database::getManager();
2415
2416
        $course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
2417
2418
        require_once api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php';
2419
        require_once api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php';
2420
        require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2421
2422
        $specific_fields = get_specific_field_list();
2423
        $ic_slide = new IndexableChunk();
2424
2425
        $all_specific_terms = '';
2426
        foreach ($specific_fields as $specific_field) {
2427
            if (isset($_REQUEST[$specific_field['code']])) {
2428
                $sterms = trim($_REQUEST[$specific_field['code']]);
2429
                if (!empty($sterms)) {
2430
                    $all_specific_terms .= ' '. $sterms;
2431
                    $sterms = explode(',', $sterms);
2432
                    foreach ($sterms as $sterm) {
2433
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2434
                        add_specific_field_value($specific_field['id'], $course->getCode(), TOOL_QUIZ, $this->id, $sterm);
2435
                    }
2436
                }
2437
            }
2438
        }
2439
2440
        // build the chunk to index
2441
        $ic_slide->addValue("title", $this->exercise);
2442
        $ic_slide->addCourseId($course->getCode());
2443
        $ic_slide->addToolId(TOOL_QUIZ);
2444
        $xapian_data = array(
2445
            SE_COURSE_ID => $course->getCode(),
2446
            SE_TOOL_ID => TOOL_QUIZ,
2447
            SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2448
            SE_USER => (int)api_get_user_id(),
2449
        );
2450
        $ic_slide->xapian_data = serialize($xapian_data);
2451
        $exercise_description = $all_specific_terms .' '. $this->description;
2452
        $ic_slide->addValue("content", $exercise_description);
2453
2454
        $di = new ChamiloIndexer();
2455
        isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2456
        $di->connectDb(NULL, NULL, $lang);
2457
        $di->addChunk($ic_slide);
2458
2459
        //index and return search engine document id
2460
        $did = $di->index();
2461 View Code Duplication
        if ($did) {
2462
            // save it to db
2463
            $searchEngineRef = new \Chamilo\CoreBundle\Entity\SearchEngineRef();
2464
            $searchEngineRef
2465
                ->setCourse($course)
2466
                ->setToolId(TOOL_QUIZ)
2467
                ->setRefIdHighLevel($this->id)
2468
                ->setSearchDid($did);
2469
2470
            $em->persist($searchEngineRef);
2471
            $em->flush();
2472
        }
2473
    }
2474
2475
    function search_engine_edit()
2476
    {
2477
        // update search enchine and its values table if enabled
2478
        if (api_get_setting(
2479
                'search.search_enabled'
2480
            ) == 'true' && extension_loaded('xapian')
2481
        ) {
2482
            $em = Database::getManager();
2483
2484
            $course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
2485
2486
            // actually, it consists on delete terms from db,
2487
            // insert new ones, create a new search engine document, and remove the old one
2488
            // get search_did
2489
            $se_ref = $em->getRepository('ChamiloCoreBundle:SearchEngineRef')
2490
                ->findOneBy([
2491
                    'course' => $course,
2492
                    'toolId' => TOOL_QUIZ,
2493
                    'refIdHighLevel' => $this->id
2494
                ]);
2495
2496
            if ($se_ref) {
2497
                require_once(api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php');
2498
                require_once(api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php');
2499
                require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
2500
2501
                $specific_fields = get_specific_field_list();
2502
                $ic_slide = new IndexableChunk();
2503
2504
                $all_specific_terms = '';
2505
                foreach ($specific_fields as $specific_field) {
2506
                    delete_all_specific_field_value(
2507
                        $course->getCode(),
2508
                        $specific_field['id'],
2509
                        TOOL_QUIZ,
2510
                        $this->id
2511
                    );
2512
                    if (isset($_REQUEST[$specific_field['code']])) {
2513
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2514
                        $all_specific_terms .= ' '. $sterms;
2515
                        $sterms = explode(',', $sterms);
2516
                        foreach ($sterms as $sterm) {
2517
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2518
                            add_specific_field_value(
2519
                                $specific_field['id'],
2520
                                $course->getCode(),
2521
                                TOOL_QUIZ,
2522
                                $this->id,
2523
                                $sterm
2524
                            );
2525
                        }
2526
                    }
2527
                }
2528
2529
                // build the chunk to index
2530
                $ic_slide->addValue("title", $this->exercise);
2531
                $ic_slide->addCourseId($course->getCode());
2532
                $ic_slide->addToolId(TOOL_QUIZ);
2533
                $xapian_data = array(
2534
                    SE_COURSE_ID => $course->getCode(),
2535
                    SE_TOOL_ID => TOOL_QUIZ,
2536
                    SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2537
                    SE_USER => (int)api_get_user_id(),
2538
                );
2539
                $ic_slide->xapian_data = serialize($xapian_data);
2540
                $exercise_description = $all_specific_terms .' '. $this->description;
2541
                $ic_slide->addValue("content", $exercise_description);
2542
2543
                $di = new ChamiloIndexer();
2544
                isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2545
                $di->connectDb(NULL, NULL, $lang);
2546
                $di->remove_document($se_ref->getSearchDid());
2547
                $di->addChunk($ic_slide);
2548
2549
                //index and return search engine document id
2550
                $did = $di->index();
2551
                if ($did) {
2552
                    // save it to db
2553
                    $em->createQuery('
2554
                        delete from ChamiloCoreBundle:SearchEngineRef ser
2555
                        where ser.course = ?1 and ser.toolId = ?2 and ser.refIdHighLevel = ?3
2556
                    ')->execute([1 => $course, 2 => TOOL_QUIZ, 3 => $this->id]);
2557
2558
                    $searchEngineRef = new \Chamilo\CoreBundle\Entity\SearchEngineRef();
2559
                    $searchEngineRef
2560
                        ->setCourse($course)
2561
                        ->setToolId(TOOL_QUIZ)
2562
                        ->setRefIdHighLevel($this->id)
2563
                        ->setSearchDid($did);
2564
2565
                    $em->persist($searchEngineRef);
2566
                    $em->flush();
2567
                }
2568
            } else {
2569
                $this->search_engine_save();
2570
            }
2571
        }
2572
2573
    }
2574
2575
    function search_engine_delete()
2576
    {
2577
        // remove from search engine if enabled
2578
        if (api_get_setting(
2579
                'search.search_enabled'
2580
            ) == 'true' && extension_loaded('xapian')
2581
        ) {
2582
            $em = Database::getManager();
2583
2584
            $course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
2585
2586
            $res = $em->createQuery('
2587
                    SELECT ser FROM ChamiloCoreBundle:SearchEngineRef ser
2588
                    WHERE ser.course = ?1 AND ser.toolId = ?2 AND ser.refIdHighLevel = ?3 AND ser.refIdSecondLevel IS NULL
2589
                ')
2590
                ->setMaxResults(1)
2591
                ->execute([1 => $course, 2 => TOOL_QUIZ, 3 => $this->id]);
2592
2593
            if (count($res) > 0) {
2594
                $row = current($res);
2595
                require_once(api_get_path(LIBRARY_PATH) .'search/ChamiloIndexer.class.php');
2596
                $di = new ChamiloIndexer();
2597
                $di->remove_document($row->getSearchDid());
2598
                unset($di);
2599
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2600
                foreach ( $this->questionList as $question_i) {
2601
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2602
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2603
                    $qres = Database::query($sql);
2604
                    if (Database::num_rows($qres) > 0) {
2605
                        $qrow = Database::fetch_array($qres);
2606
                        $objQuestion = Question::getInstance($qrow['type']);
2607
                        $objQuestion = Question::read((int)$question_i);
2608
                        $objQuestion->search_engine_edit($this->id, FALSE, TRUE);
2609
                        unset($objQuestion);
2610
                    }
2611
                }
2612
            }
2613
2614
            $em
2615
                ->createQuery('
2616
                    DELETE FROM ChamiloCoreBundle:SearchEngineRef ser
2617
                    WHERE ser.course = ?1 AND ser.toolId = ?2 AND
2618
                        ser.refIdHighLevel = ?3 AND ser.refIdSecondLevel IS NULL
2619
                ')
2620
                ->setMaxResults(1)
2621
                ->execute([1 => $course, 2 => TOOL_QUIZ, 3 => $this->id]);
2622
2623
            // remove terms from db
2624
            require_once api_get_path(LIBRARY_PATH) .'specific_fields_manager.lib.php';
2625
            delete_all_values_for_item($course->getCode(), TOOL_QUIZ, $this->id);
2626
        }
2627
    }
2628
2629
    public function selectExpiredTime()
2630
    {
2631
        return $this->expired_time;
2632
    }
2633
2634
    /**
2635
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2636
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2637
     * Works with exercises in sessions
2638
     * @param bool $cleanLpTests
2639
     * @param string $cleanResultBeforeDate
2640
     *
2641
     * @return int quantity of user's exercises deleted
2642
     */
2643
    public function clean_results($cleanLpTests = false, $cleanResultBeforeDate = null)
2644
    {
2645
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2646
2647
        $em = Database::getManager();
2648
2649
        $sql_where = '  AND
2650
                        orig_lp_id = 0 AND
2651
                        orig_lp_item_id = 0';
2652
2653
        // if we want to delete results from LP too
2654
        if ($cleanLpTests) {
2655
            $sql_where = "";
2656
        }
2657
2658
        // if we want to delete attempts before date $cleanResultBeforeDate
2659
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2660
2661
        if (!empty($cleanResultBeforeDate)) {
2662
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2663
            if (api_is_valid_date($cleanResultBeforeDate)) {
2664
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2665
            } else {
2666
                return 0;
2667
            }
2668
        }
2669
2670
        $sql = "SELECT exe_id
2671
                FROM $table_track_e_exercises
2672
                WHERE
2673
                    c_id = ".api_get_course_int_id()." AND
2674
                    exe_exo_id = ".$this->id." AND
2675
                    session_id = ".api_get_session_id()." ".
2676
                    $sql_where;
2677
2678
        $result   = Database::query($sql);
2679
        $exe_list = Database::store_result($result);
2680
2681
        // deleting TRACK_E_ATTEMPT table
2682
        // check if exe in learning path or not
2683
        $i = 0;
2684
        if (is_array($exe_list) && count($exe_list) > 0) {
2685
            foreach ($exe_list as $item) {
2686
                $em
2687
                    ->createQuery('
2688
                        DELETE FROM ChamiloCoreBundle:TrackEAttempt tea
2689
                        WHERE tea.exeId = :exe
2690
                    ')
2691
                    ->execute([
2692
                        'exe' => $item['exe_id']
2693
                    ]);
2694
                $i++;
2695
            }
2696
        }
2697
2698
        $session_id = api_get_session_id();
2699
        // delete TRACK_E_EXERCISES table
2700
        $sql = "DELETE FROM $table_track_e_exercises
2701
                WHERE c_id = ".api_get_course_int_id()."
2702
                AND exe_exo_id = ".$this->id."
2703
                $sql_where
2704
                AND session_id = ".$session_id."";
2705
        Database::query($sql);
2706
2707
        Event::addEvent(
2708
            LOG_EXERCISE_RESULT_DELETE,
2709
            LOG_EXERCISE_ID,
2710
            $this->id,
2711
            null,
2712
            null,
2713
            api_get_course_int_id(),
2714
            $session_id
2715
        );
2716
2717
        return $i;
2718
    }
2719
2720
    /**
2721
     * Copies an exercise (duplicate all questions and answers)
2722
     */
2723
    public function copy_exercise()
2724
    {
2725
        $exercise_obj = $this;
2726
2727
        // force the creation of a new exercise
2728
        $exercise_obj->updateTitle($exercise_obj->selectTitle().' - '.get_lang('Copy'));
2729
        //Hides the new exercise
2730
        $exercise_obj->updateStatus(false);
2731
        $exercise_obj->updateId(0);
2732
        $exercise_obj->save();
2733
2734
        $new_exercise_id = $exercise_obj->selectId();
2735
        $question_list 	 = $exercise_obj->selectQuestionList();
2736
2737
        if (!empty($question_list)) {
2738
            //Question creation
2739
2740
            foreach ($question_list as $old_question_id) {
2741
                $old_question_obj = Question::read($old_question_id);
2742
                $new_id = $old_question_obj->duplicate();
2743
                if ($new_id) {
2744
                    $new_question_obj = Question::read($new_id);
2745
2746
                    if (isset($new_question_obj) && $new_question_obj) {
2747
                        $new_question_obj->addToList($new_exercise_id);
2748
                        // This should be moved to the duplicate function
2749
                        $new_answer_obj = new Answer($old_question_id);
2750
                        $new_answer_obj->read();
2751
                        $new_answer_obj->duplicate($new_id);
2752
                    }
2753
                }
2754
            }
2755
        }
2756
    }
2757
2758
    /**
2759
     * Changes the exercise id
2760
     *
2761
     * @param int $id - exercise id
2762
     */
2763
    private function updateId($id)
2764
    {
2765
        $this->id = $id;
2766
    }
2767
2768
    /**
2769
     * Changes the exercise status
2770
     *
2771
     * @param string $status - exercise status
2772
     */
2773
    function updateStatus($status)
2774
    {
2775
        $this->active = $status;
2776
    }
2777
2778
    /**
2779
     * @param int $lp_id
2780
     * @param int $lp_item_id
2781
     * @param int $lp_item_view_id
2782
     * @param string $status
2783
     * @return array
2784
     */
2785
    public function get_stat_track_exercise_info(
2786
        $lp_id = 0,
2787
        $lp_item_id = 0,
2788
        $lp_item_view_id = 0,
2789
        $status = 'incomplete'
2790
    ) {
2791
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2792
        if (empty($lp_id)) {
2793
            $lp_id = 0;
2794
        }
2795
        if (empty($lp_item_id)) {
2796
            $lp_item_id   = 0;
2797
        }
2798
        if (empty($lp_item_view_id)) {
2799
            $lp_item_view_id = 0;
2800
        }
2801
        $condition = ' WHERE exe_exo_id 	= ' . "'" . $this->id . "'" .' AND
2802
					   exe_user_id 			= ' . "'" . api_get_user_id() . "'" . ' AND
2803
					   c_id                 = ' . api_get_course_int_id() . ' AND
2804
					   status 				= ' . "'" . Database::escape_string($status). "'" . ' AND
2805
					   orig_lp_id 			= ' . "'" . $lp_id . "'" . ' AND
2806
					   orig_lp_item_id 		= ' . "'" . $lp_item_id . "'" . ' AND
2807
                       orig_lp_item_view_id = ' . "'" . $lp_item_view_id . "'" . ' AND
2808
					   session_id 			= ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case
2809
2810
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2811
2812
        $result = Database::query($sql_track);
2813
        $new_array = array();
2814
        if (Database::num_rows($result) > 0 ) {
2815
            $new_array = Database::fetch_array($result, 'ASSOC');
2816
            $new_array['num_exe'] = Database::num_rows($result);
2817
        }
2818
2819
        return $new_array;
2820
    }
2821
2822
    /**
2823
     * Saves a test attempt
2824
     *
2825
     * @param int  clock_expired_time
2826
     * @param int  int lp id
2827
     * @param int  int lp item id
2828
     * @param int  int lp item_view id
2829
     * @param float $weight
2830
     * @param array question list
2831
     */
2832
    public function save_stat_track_exercise_info(
2833
        $clock_expired_time = 0,
2834
        $safe_lp_id = 0,
2835
        $safe_lp_item_id = 0,
2836
        $safe_lp_item_view_id = 0,
2837
        $questionList = array(),
2838
        $weight = 0
2839
    ) {
2840
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2841
        $safe_lp_id = intval($safe_lp_id);
2842
        $safe_lp_item_id = intval($safe_lp_item_id);
2843
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
2844
2845
        if (empty($safe_lp_id)) {
2846
            $safe_lp_id = 0;
2847
        }
2848
        if (empty($safe_lp_item_id)) {
2849
            $safe_lp_item_id = 0;
2850
        }
2851
        if (empty($clock_expired_time)) {
2852
            $clock_expired_time = 0;
2853
        }
2854
2855
        $questionList = array_map('intval', $questionList);
2856
2857
        $params = array(
2858
            'exe_exo_id' => $this->id ,
2859
            'exe_user_id' => api_get_user_id(),
2860
            'c_id' => api_get_course_int_id(),
2861
            'status' =>  'incomplete',
2862
            'session_id'  => api_get_session_id(),
2863
            'data_tracking'  => implode(',', $questionList) ,
2864
            'start_date' => api_get_utc_datetime(),
2865
            'orig_lp_id' => $safe_lp_id,
2866
            'orig_lp_item_id'  => $safe_lp_item_id,
2867
            'orig_lp_item_view_id'  => $safe_lp_item_view_id,
2868
            'exe_weighting'=> $weight,
2869
            'user_ip' => api_get_real_ip()
2870
        );
2871
2872
        if ($this->expired_time != 0) {
2873
            $params['expired_time_control'] = $clock_expired_time;
2874
        }
2875
2876
        $id = Database::insert($track_exercises, $params);
2877
2878
        return $id;
2879
    }
2880
2881
    /**
2882
     * @param int $question_id
2883
     * @param int $questionNum
2884
     * @param array $questions_in_media
2885
     * @param string $currentAnswer
2886
     * @return string
2887
     */
2888
    public function show_button($question_id, $questionNum, $questions_in_media = array(), $currentAnswer = '')
2889
    {
2890
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
2891
2892
        $nbrQuestions = $this->get_count_question_list();
2893
2894
        $all_button = $html = $label = '';
2895
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']):null;
2896
2897
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
2898
            $urlTitle = get_lang('ContinueTest');
2899
2900
            if ($questionNum == count($this->questionList)) {
2901
                $urlTitle = get_lang('EndTest');
2902
            }
2903
2904
            $html .= Display::url(
2905
                $urlTitle,
2906
                'exercise_submit_modal.php?' . http_build_query([
2907
                    'learnpath_id' => $safe_lp_id,
2908
                    'learnpath_item_id' => $safe_lp_item_id,
2909
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
2910
                    'origin' => $origin,
2911
                    'hotspot' => $hotspot_get,
2912
                    'nbrQuestions' => $nbrQuestions,
2913
                    'num' => $questionNum,
2914
                    'exerciseType' => $this->type,
2915
                    'exerciseId' => $this->id
2916
                ]),
2917
                [
2918
                    'class' => 'ajax btn btn-default',
2919
                    'data-title' => $urlTitle,
2920
                    'data-size' => 'md'
2921
                ]
2922
            );
2923
            $html .='<br />';
2924
        } else {
2925
            // User
2926
            if (api_is_allowed_to_session_edit()) {
2927
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum) {
2928 View Code Duplication
                    if ($this->review_answers) {
2929
                        $label = get_lang('ReviewQuestions');
2930
                        $class = 'btn btn-success';
2931
                    } else {
2932
                        $label = get_lang('EndTest');
2933
                        $class = 'btn btn-warning';
2934
                    }
2935
                } else {
2936
                    $label = get_lang('NextQuestion');
2937
                    $class = 'btn btn-primary';
2938
                }
2939
				$class .= ' question-validate-btn'; // used to select it with jquery
2940
                if ($this->type == ONE_PER_PAGE) {
2941
                    if ($questionNum != 1) {
2942
                        $prev_question = $questionNum - 2;
2943
                        $all_button .= '<a href="javascript://" class="btn btn-default" onclick="previous_question_and_save('.$prev_question.', '.$question_id.' ); ">'.get_lang('PreviousQuestion').'</a>';
2944
                    }
2945
2946
                    //Next question
2947
                    if (!empty($questions_in_media)) {
2948
                        $questions_in_media = "['".implode("','",$questions_in_media)."']";
2949
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_question_list('.$questions_in_media.'); ">'.$label.'</a>';
2950
                    } else {
2951
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_now('.$question_id.', \'\', \''.$currentAnswer.'\'); ">'.$label.'</a>';
2952
                    }
2953
                    $all_button .= '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
2954
2955
                    $html .= $all_button;
2956
                } else {
2957 View Code Duplication
                    if ($this->review_answers) {
2958
                        $all_label = get_lang('ReviewQuestions');
2959
                        $class = 'btn btn-success';
2960
                    } else {
2961
                        $all_label = get_lang('EndTest');
2962
                        $class = 'btn btn-warning';
2963
                    }
2964
					$class .= ' question-validate-btn'; // used to select it with jquery
2965
                    $all_button = '&nbsp;<a href="javascript://" class="'.$class.'" onclick="validate_all(); ">'.$all_label.'</a>';
2966
                    $all_button .= '&nbsp;' . Display::span(null, ['id' => 'save_all_reponse']);
2967
                    $html .= $all_button;
2968
                }
2969
            }
2970
        }
2971
2972
        return $html;
2973
    }
2974
2975
    /**
2976
     * So the time control will work
2977
     *
2978
     * @param string $time_left
2979
     * @return string
2980
     */
2981
    public function show_time_control_js($time_left)
2982
    {
2983
        $time_left = intval($time_left);
2984
        return "<script>
2985
2986
            function get_expired_date_string(expired_time) {
2987
                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
2988
                var day, month, year, hours, minutes, seconds, date_string;
2989
                var obj_date = new Date(expired_time);
2990
                day     = obj_date.getDate();
2991
                if (day < 10) day = '0' + day;
2992
                    month   = obj_date.getMonth();
2993
                    year    = obj_date.getFullYear();
2994
                    hours   = obj_date.getHours();
2995
                if (hours < 10) hours = '0' + hours;
2996
                minutes = obj_date.getMinutes();
2997
                if (minutes < 10) minutes = '0' + minutes;
2998
                seconds = obj_date.getSeconds();
2999
                if (seconds < 10) seconds = '0' + seconds;
3000
                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
3001
                return date_string;
3002
            }
3003
3004
            function open_clock_warning() {
3005
                $('#clock_warning').dialog({
3006
                    modal:true,
3007
                    height:250,
3008
                    closeOnEscape: false,
3009
                    resizable: false,
3010
                    buttons: {
3011
                        '".addslashes(get_lang("EndTest"))."': function() {
3012
                            $('#clock_warning').dialog('close');
3013
                        }
3014
                    },
3015
                    close: function() {
3016
                        send_form();
3017
                    }
3018
                });
3019
                $('#clock_warning').dialog('open');
3020
3021
                $('#counter_to_redirect').epiclock({
3022
                    mode: $.epiclock.modes.countdown,
3023
                    offset: {seconds: 5},
3024
                    format: 's'
3025
                }).bind('timer', function () {
3026
                    send_form();
3027
                });
3028
3029
            }
3030
3031
            function send_form() {
3032
                if ($('#exercise_form').length) {
3033
                    $('#exercise_form').submit();
3034
                } else {
3035
                    //In reminder
3036
                    final_submit();
3037
                }
3038
            }
3039
3040
            function onExpiredTimeExercise() {
3041
                $('#wrapper-clock').hide();
3042
                $('#exercise_form').hide();
3043
                $('#expired-message-id').show();
3044
3045
                //Fixes bug #5263
3046
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3047
                open_clock_warning();
3048
            }
3049
3050
			$(document).ready(function() {
3051
3052
				var current_time = new Date().getTime();
3053
                var time_left    = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
3054
				var expired_time = current_time + (time_left*1000);
3055
				var expired_date = get_expired_date_string(expired_time);
3056
3057
                $('#exercise_clock_warning').epiclock({
3058
                    mode: $.epiclock.modes.countdown,
3059
                    offset: {seconds: time_left},
3060
                    format: 'x:i:s',
3061
                    renderer: 'minute'
3062
                }).bind('timer', function () {
3063
                    onExpiredTimeExercise();
3064
                });
3065
	       		$('#submit_save').click(function () {});
3066
	    });
3067
	    </script>";
3068
    }
3069
3070
    /**
3071
     * Lp javascript for hotspots
3072
     */
3073
    public function show_lp_javascript()
3074
    {
3075
        return '';
3076
    }
3077
3078
    /**
3079
     * This function was originally found in the exercise_show.php
3080
     * @param int       $exeId
3081
     * @param int       $questionId
3082
     * @param int       $choice the user selected
3083
     * @param string    $from  function is called from 'exercise_show' or 'exercise_result'
3084
     * @param array     $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates
3085
     * @param bool      $saved_results save results in the DB or just show the reponse
3086
     * @param bool      $from_database gets information from DB or from the current selection
3087
     * @param bool      $show_result show results or not
3088
     * @param int       $propagate_neg
3089
     * @param array     $hotspot_delineation_result
3090
     *
3091
     * @todo    reduce parameters of this function
3092
     * @return  string  html code
3093
     */
3094
    public function manage_answer(
3095
        $exeId,
3096
        $questionId,
3097
        $choice,
3098
        $from = 'exercise_show',
3099
        $exerciseResultCoordinates = array(),
3100
        $saved_results = true,
3101
        $from_database = false,
3102
        $show_result = true,
3103
        $propagate_neg = 0,
3104
        $hotspot_delineation_result = array()
3105
    ) {
3106
        global $debug;
3107
        //needed in order to use in the exercise_attempt() for the time
3108
        global $learnpath_id, $learnpath_item_id;
3109
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3110
3111
        $em = Database::getManager();
3112
3113
        $feedback_type = $this->selectFeedbackType();
3114
        $results_disabled = $this->selectResultsDisabled();
3115
3116
        if ($debug) {
3117
            error_log("<------ manage_answer ------> ");
3118
            error_log('exe_id: '.$exeId);
3119
            error_log('$from:  '.$from);
3120
            error_log('$saved_results: '.intval($saved_results));
3121
            error_log('$from_database: '.intval($from_database));
3122
            error_log('$show_result: '.$show_result);
3123
            error_log('$propagate_neg: '.$propagate_neg);
3124
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3125
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3126
            error_log('$learnpath_id: '.$learnpath_id);
3127
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3128
            error_log('$choice: '.print_r($choice, 1));
3129
        }
3130
3131
        $extra_data = array();
3132
        $final_overlap = 0;
3133
        $final_missing = 0;
3134
        $final_excess = 0;
3135
        $overlap_color = 0;
3136
        $missing_color = 0;
3137
        $excess_color = 0;
3138
        $threadhold1 = 0;
3139
        $threadhold2 = 0;
3140
        $threadhold3 = 0;
3141
3142
        $arrques = null;
3143
        $arrans  = null;
3144
3145
        $questionId = intval($questionId);
3146
        $exeId = intval($exeId);
3147
3148
        $em = Database::getManager();
3149
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3150
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3151
3152
        // Creates a temporary Question object
3153
        $course_id = $this->course_id;
3154
        $objQuestionTmp = Question::read($questionId, $course_id);
3155
3156
        if ($objQuestionTmp === false) {
3157
            return false;
3158
        }
3159
3160
        $questionName = $objQuestionTmp->selectTitle();
3161
        $questionWeighting = $objQuestionTmp->selectWeighting();
3162
        $answerType = $objQuestionTmp->selectType();
3163
        $quesId = $objQuestionTmp->selectId();
3164
        $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...
3165
3166
        $next = 1; //not for now
3167
3168
        // Extra information of the question
3169
        if (!empty($extra)) {
3170
            $extra = explode(':', $extra);
3171
            if ($debug) {
3172
                error_log(print_r($extra, 1));
3173
            }
3174
            // Fixes problems with negatives values using intval
3175
            $true_score = floatval(trim($extra[0]));
3176
            $false_score = floatval(trim($extra[1]));
3177
            $doubt_score = floatval(trim($extra[2]));
3178
        }
3179
3180
        $totalWeighting = 0;
3181
        $totalScore = 0;
3182
3183
        // Destruction of the Question object
3184
        unset($objQuestionTmp);
3185
3186
        // Construction of the Answer object
3187
        $objAnswerTmp = new Answer($questionId);
3188
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3189
3190
        if ($debug) {
3191
            error_log('Count of answers: '.$nbrAnswers);
3192
            error_log('$answerType: '.$answerType);
3193
        }
3194
3195
        if ($answerType == FREE_ANSWER ||
3196
            $answerType == ORAL_EXPRESSION ||
3197
            $answerType == CALCULATED_ANSWER
3198
        ) {
3199
            $nbrAnswers = 1;
3200
        }
3201
3202
        $nano = null;
3203
3204
        if ($answerType == ORAL_EXPRESSION) {
3205
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3206
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3207
3208
            $params = array();
3209
            $params['course_id'] = $course_id;
3210
            $params['session_id'] = api_get_session_id();
3211
            $params['user_id'] = isset($exe_info['exe_user_id'])? $exe_info['exe_user_id'] : api_get_user_id();
3212
            $params['exercise_id'] = isset($exe_info['exe_exo_id'])? $exe_info['exe_exo_id'] : $this->id;
3213
            $params['question_id'] = $questionId;
3214
            $params['exe_id'] = isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId;
3215
3216
            $nano = new Nanogong($params);
3217
3218
            //probably this attempt came in an exercise all question by page
3219
            if ($feedback_type == 0) {
3220
                $nano->replace_with_real_exe($exeId);
3221
            }
3222
        }
3223
3224
        $user_answer = '';
3225
3226
        // Get answer list for matching
3227
        $sql = "SELECT id_auto, id, answer
3228
                FROM $table_ans
3229
                WHERE c_id = $course_id AND question_id = $questionId";
3230
        $res_answer = Database::query($sql);
3231
3232
        $answerMatching = array();
3233
        while ($real_answer = Database::fetch_array($res_answer)) {
3234
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3235
        }
3236
3237
        $real_answers = array();
3238
        $quiz_question_options = Question::readQuestionOption(
3239
            $questionId,
3240
            $course_id
3241
        );
3242
3243
        $organs_at_risk_hit = 0;
3244
        $questionScore = 0;
3245
3246
        if ($debug) error_log('Start answer loop ');
3247
3248
        $answer_correct_array = array();
3249
3250
        $orderedHotspots = [];
3251
3252
        if ($answerType == HOT_SPOT) {
3253
            $orderedHotspots = $em
3254
                ->getRepository('ChamiloCoreBundle:TrackEHotspot')
3255
                ->findBy([
3256
                        'hotspotQuestionId' => $questionId,
3257
                        'cId' => $course_id,
3258
                        'hotspotExeId' => $exeId
3259
                    ],
3260
                    ['hotspotId' => 'ASC']
3261
                );
3262
        }
3263
3264
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3265
            $answer = $objAnswerTmp->selectAnswer($answerId);
3266
            $answerComment = $objAnswerTmp->selectComment($answerId);
3267
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3268
            $answerWeighting = (float)$objAnswerTmp->selectWeighting($answerId);
3269
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3270
3271
            $answer_correct_array[$answerId] = (bool)$answerCorrect;
3272
3273
            if ($debug) {
3274
                error_log("answer auto id: $answerAutoId ");
3275
                error_log("answer correct: $answerCorrect ");
3276
            }
3277
3278
            // Delineation
3279
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3280
            $answer_delineation_destination=$objAnswerTmp->selectDestination(1);
3281
3282
            switch ($answerType) {
3283
                // for unique answer
3284
                case UNIQUE_ANSWER:
3285
                case UNIQUE_ANSWER_IMAGE:
3286
                case UNIQUE_ANSWER_NO_OPTION:
3287
                    if ($from_database) {
3288
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3289
                                WHERE
3290
                                    exe_id = '".$exeId."' AND
3291
                                    question_id= '".$questionId."'";
3292
                        $result = Database::query($sql);
3293
                        $choice = Database::result($result,0,"answer");
3294
3295
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3296
                        if ($studentChoice) {
3297
                            $questionScore += $answerWeighting;
3298
                            $totalScore += $answerWeighting;
3299
                        }
3300
                    } else {
3301
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3302
                        if ($studentChoice) {
3303
                            $questionScore += $answerWeighting;
3304
                            $totalScore += $answerWeighting;
3305
                        }
3306
                    }
3307
                    break;
3308
                // for multiple answers
3309
                case MULTIPLE_ANSWER_TRUE_FALSE:
3310
                    if ($from_database) {
3311
                        $choice = array();
3312
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3313
                                WHERE
3314
                                    exe_id = $exeId AND
3315
                                    question_id = ".$questionId;
3316
3317
                        $result = Database::query($sql);
3318 View Code Duplication
                        while ($row = Database::fetch_array($result)) {
3319
                            $ind = $row['answer'];
3320
                            $values = explode(':', $ind);
3321
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3322
                            $option = isset($values[1]) ? $values[1] : '';
3323
                            $choice[$my_answer_id] = $option;
3324
                        }
3325
                    }
3326
3327
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3328
3329
                    if (!empty($studentChoice)) {
3330
                        if ($studentChoice == $answerCorrect) {
3331
                            $questionScore += $true_score;
3332
                        } else {
3333
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3334
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3335
                            ) {
3336
                                $questionScore += $doubt_score;
3337
                            } else {
3338
                                $questionScore += $false_score;
3339
                            }
3340
                        }
3341
                    } else {
3342
                        // If no result then the user just hit don't know
3343
                        $studentChoice = 3;
3344
                        $questionScore  +=  $doubt_score;
3345
                    }
3346
                    $totalScore = $questionScore;
3347
                    break;
3348 View Code Duplication
                case MULTIPLE_ANSWER: //2
3349
                    if ($from_database) {
3350
                        $choice = array();
3351
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3352
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3353
                        $resultans = Database::query($sql);
3354
                        while ($row = Database::fetch_array($resultans)) {
3355
                            $ind = $row['answer'];
3356
                            $choice[$ind] = 1;
3357
                        }
3358
3359
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3360
                        $real_answers[$answerId] = (bool)$studentChoice;
3361
3362
                        if ($studentChoice) {
3363
                            $questionScore  +=$answerWeighting;
3364
                        }
3365
                    } else {
3366
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3367
                        $real_answers[$answerId] = (bool)$studentChoice;
3368
3369
                        if (isset($studentChoice)) {
3370
                            $questionScore  += $answerWeighting;
3371
                        }
3372
                    }
3373
                    $totalScore += $answerWeighting;
3374
3375
                    if ($debug) error_log("studentChoice: $studentChoice");
3376
                    break;
3377 View Code Duplication
                case GLOBAL_MULTIPLE_ANSWER:
3378
                    if ($from_database) {
3379
                        $choice = array();
3380
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3381
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3382
                        $resultans = Database::query($sql);
3383
                        while ($row = Database::fetch_array($resultans)) {
3384
                            $ind = $row['answer'];
3385
                            $choice[$ind] = 1;
3386
                        }
3387
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3388
                        $real_answers[$answerId] = (bool)$studentChoice;
3389
                        if ($studentChoice) {
3390
                            $questionScore +=$answerWeighting;
3391
                        }
3392
                    } else {
3393
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3394
                        if (isset($studentChoice)) {
3395
                            $questionScore += $answerWeighting;
3396
                        }
3397
                        $real_answers[$answerId] = (bool)$studentChoice;
3398
                    }
3399
                    $totalScore += $answerWeighting;
3400
                    if ($debug) error_log("studentChoice: $studentChoice");
3401
                    break;
3402
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3403
                    if ($from_database) {
3404
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3405
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3406
                        $resultans = Database::query($sql);
3407 View Code Duplication
                        while ($row = Database::fetch_array($resultans)) {
3408
                            $ind = $row['answer'];
3409
                            $result = explode(':',$ind);
3410
                            if (isset($result[0])) {
3411
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3412
                                $option = isset($result[1]) ? $result[1] : '';
3413
                                $choice[$my_answer_id] = $option;
3414
                            }
3415
                        }
3416
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3417
3418
                        if ($answerCorrect == $studentChoice) {
3419
                            //$answerCorrect = 1;
3420
                            $real_answers[$answerId] = true;
3421
                        } else {
3422
                            //$answerCorrect = 0;
3423
                            $real_answers[$answerId] = false;
3424
                        }
3425
                    } else {
3426
                        $studentChoice = $choice[$answerAutoId];
3427
                        if ($answerCorrect == $studentChoice) {
3428
                            //$answerCorrect = 1;
3429
                            $real_answers[$answerId] = true;
3430
                        } else {
3431
                            //$answerCorrect = 0;
3432
                            $real_answers[$answerId] = false;
3433
                        }
3434
                    }
3435
                    break;
3436
                case MULTIPLE_ANSWER_COMBINATION:
3437
                    if ($from_database) {
3438
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3439
                                WHERE exe_id = $exeId AND question_id= $questionId";
3440
                        $resultans = Database::query($sql);
3441
                        while ($row = Database::fetch_array($resultans)) {
3442
                            $ind = $row['answer'];
3443
                            $choice[$ind] = 1;
3444
                        }
3445
3446
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3447
3448 View Code Duplication
                        if ($answerCorrect == 1) {
3449
                            if ($studentChoice) {
3450
                                $real_answers[$answerId] = true;
3451
                            } else {
3452
                                $real_answers[$answerId] = false;
3453
                            }
3454
                        } else {
3455
                            if ($studentChoice) {
3456
                                $real_answers[$answerId] = false;
3457
                            } else {
3458
                                $real_answers[$answerId] = true;
3459
                            }
3460
                        }
3461
                    } else {
3462
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3463
3464 View Code Duplication
                        if ($answerCorrect == 1) {
3465
                            if ($studentChoice) {
3466
                                $real_answers[$answerId] = true;
3467
                            } else {
3468
                                $real_answers[$answerId] = false;
3469
                            }
3470
                        } else {
3471
                            if ($studentChoice) {
3472
                                $real_answers[$answerId] = false;
3473
                            } else {
3474
                                $real_answers[$answerId] = true;
3475
                            }
3476
                        }
3477
                    }
3478
                    break;
3479
                case FILL_IN_BLANKS:
3480
                    $str = '';
3481
                    if ($from_database) {
3482
                        $sql = "SELECT answer
3483
                                    FROM $TBL_TRACK_ATTEMPT
3484
                                    WHERE
3485
                                        exe_id = $exeId AND
3486
                                        question_id= ".intval($questionId);
3487
                        $result = Database::query($sql);
3488
                        $str = Database::result($result, 0, 'answer');
3489
                    }
3490
3491
                    if ($saved_results == false && strpos($str, 'font color') !== false) {
3492
                        // the question is encoded like this
3493
                        // [A] B [C] D [E] F::10,10,10@1
3494
                        // number 1 before the "@" means that is a switchable fill in blank question
3495
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3496
                        // means that is a normal fill blank question
3497
                        // first we explode the "::"
3498
                        $pre_array = explode('::', $answer);
3499
3500
                        // is switchable fill blank or not
3501
                        $last = count($pre_array) - 1;
3502
                        $is_set_switchable = explode('@', $pre_array[$last]);
3503
                        $switchable_answer_set = false;
3504
                        if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3505
                            $switchable_answer_set = true;
3506
                        }
3507
                        $answer = '';
3508
                        for ($k = 0; $k < $last; $k++) {
3509
                            $answer .= $pre_array[$k];
3510
                        }
3511
                        // splits weightings that are joined with a comma
3512
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3513
                        // we save the answer because it will be modified
3514
                        $temp = $answer;
3515
                        $answer = '';
3516
                        $j = 0;
3517
                        //initialise answer tags
3518
                        $user_tags = $correct_tags = $real_text = array();
3519
                        // the loop will stop at the end of the text
3520 View Code Duplication
                        while (1) {
3521
                            // quits the loop if there are no more blanks (detect '[')
3522
                            if (($pos = api_strpos($temp, '[')) === false) {
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3569 can also be of type false; however, api_strpos() does only seem to accept string, did you maybe forget to handle an error condition?

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

Consider the follow example

<?php

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

    return false;
}

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

Loading history...
3523
                                // adds the end of the text
3524
                                $answer = $temp;
3525
                                $real_text[] = $answer;
3526
                                break; //no more "blanks", quit the loop
3527
                            }
3528
                            // adds the piece of text that is before the blank
3529
                            //and ends with '[' into a general storage array
3530
                            $real_text[] = api_substr($temp, 0, $pos +1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 3569 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

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

Consider the follow example

<?php

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

    return false;
}

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

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

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

Consider the follow example

<?php

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

    return false;
}

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

Loading history...
3532
                            //take the string remaining (after the last "[" we found)
3533
                            $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 3533 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...
3534
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3535
                            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 3533 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...
3536
                                // adds the end of the text
3537
                                $answer .= $temp;
3538
                                break;
3539
                            }
3540
                            if ($from_database) {
3541
                                $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3542
                                          WHERE
3543
                                            exe_id = '".$exeId."' AND
3544
                                            question_id= ".intval($questionId)."";
3545
                                $resfill = Database::query($queryfill);
3546
                                $str = Database::result($resfill, 0, 'answer');
3547
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3548
                                $str = str_replace('\r\n', '', $str);
3549
3550
                                $choice = $arr[1];
3551
                                if (isset($choice[$j])) {
3552
                                    $tmp = api_strrpos($choice[$j], ' / ');
3553
                                    $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 3552 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...
3554
                                    $choice[$j] = trim($choice[$j]);
3555
                                    // Needed to let characters ' and " to work as part of an answer
3556
                                    $choice[$j] = stripslashes($choice[$j]);
3557
                                } else {
3558
                                    $choice[$j] = null;
3559
                                }
3560
                            } else {
3561
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3562
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3563
                            }
3564
3565
                            $user_tags[] = $choice[$j];
3566
                            //put the contents of the [] answer tag into correct_tags[]
3567
                            $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 3533 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...
3568
                            $j++;
3569
                            $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 3569 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

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

Consider the follow example

<?php

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

    return false;
}

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

Loading history...
3570
                        }
3571
                        $answer = '';
3572
                        $real_correct_tags = $correct_tags;
3573
                        $chosen_list = array();
3574
3575
                        for ($i = 0; $i < count($real_correct_tags); $i++) {
3576
                            if ($i == 0) {
3577
                                $answer .= $real_text[0];
3578
                            }
3579
                            if (!$switchable_answer_set) {
3580
                                // Needed to parse ' and " characters
3581
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3582 View Code Duplication
                                if ($correct_tags[$i] == $user_tags[$i]) {
3583
                                    // gives the related weighting to the student
3584
                                    $questionScore += $answerWeighting[$i];
3585
                                    // increments total score
3586
                                    $totalScore += $answerWeighting[$i];
3587
                                    // adds the word in green at the end of the string
3588
                                    $answer .= $correct_tags[$i];
3589
                                } elseif (!empty($user_tags[$i])) {
3590
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3591
                                    // adds the word in red at the end of the string, and strikes it
3592
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3593
                                } else {
3594
                                    // adds a tabulation if no word has been typed by the student
3595
                                    $answer .= ''; // remove &nbsp; that causes issue
3596
                                }
3597
                            } else {
3598
                                // switchable fill in the blanks
3599
                                if (in_array($user_tags[$i], $correct_tags)) {
3600
                                    $chosen_list[] = $user_tags[$i];
3601
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3602
                                    // gives the related weighting to the student
3603
                                    $questionScore += $answerWeighting[$i];
3604
                                    // increments total score
3605
                                    $totalScore += $answerWeighting[$i];
3606
                                    // adds the word in green at the end of the string
3607
                                    $answer .= $user_tags[$i];
3608
                                } elseif (!empty ($user_tags[$i])) {
3609
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3610
                                    // adds the word in red at the end of the string, and strikes it
3611
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3612
                                } else {
3613
                                    // adds a tabulation if no word has been typed by the student
3614
                                    $answer .= '';  // remove &nbsp; that causes issue
3615
                                }
3616
                            }
3617
3618
                            // adds the correct word, followed by ] to close the blank
3619
                            $answer .= ' / <font color="green"><b>' . $real_correct_tags[$i] . '</b></font>]';
3620
                            if (isset($real_text[$i +1])) {
3621
                                $answer .= $real_text[$i +1];
3622
                            }
3623
                        }
3624
                    } else {
3625
                        // insert the student result in the track_e_attempt table, field answer
3626
                        // $answer is the answer like in the c_quiz_answer table for the question
3627
                        // student data are choice[]
3628
                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
3629
                            $answer
3630
                        );
3631
                        $switchableAnswerSet = $listCorrectAnswers["switchable"];
3632
                        $answerWeighting = $listCorrectAnswers["tabweighting"];
3633
                        // user choices is an array $choice
3634
3635
                        // get existing user data in n the BDD
3636
                        if ($from_database) {
3637
                            $sql = "SELECT answer
3638
                                    FROM $TBL_TRACK_ATTEMPT
3639
                                    WHERE
3640
                                        exe_id = $exeId AND
3641
                                        question_id= ".intval($questionId);
3642
                            $result = Database::query($sql);
3643
                            $str = Database::result($result, 0, 'answer');
3644
                            $listStudentResults = FillBlanks::getAnswerInfo(
3645
                                $str,
3646
                                true
3647
                            );
3648
                            $choice = $listStudentResults['studentanswer'];
3649
                        }
3650
3651
                        // loop other all blanks words
3652
                        if (!$switchableAnswerSet) {
3653
                            // not switchable answer, must be in the same place than teacher order
3654
                            for ($i = 0; $i < count(
3655
                                $listCorrectAnswers['tabwords']
3656
                            ); $i++) {
3657
                                $studentAnswer = isset($choice[$i]) ? trim(
3658
                                    $choice[$i]
3659
                                ) : '';
3660
3661
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3662
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3663
                                if (!$from_database) {
3664
                                    $studentAnswer = htmlentities(
3665
                                        api_utf8_encode($studentAnswer)
3666
                                    );
3667
                                }
3668
3669
                                $correctAnswer = $listCorrectAnswers['tabwords'][$i];
3670
                                $isAnswerCorrect = 0;
3671 View Code Duplication
                                if (FillBlanks::isGoodStudentAnswer(
3672
                                    $studentAnswer,
3673
                                    $correctAnswer
3674
                                )
3675
                                ) {
3676
                                    // gives the related weighting to the student
3677
                                    $questionScore += $answerWeighting[$i];
3678
                                    // increments total score
3679
                                    $totalScore += $answerWeighting[$i];
3680
                                    $isAnswerCorrect = 1;
3681
                                }
3682
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3683
                                $listCorrectAnswers['studentscore'][$i] = $isAnswerCorrect;
3684
                            }
3685
                        } else {
3686
                            // switchable answer
3687
                            $listStudentAnswerTemp = $choice;
3688
                            $listTeacherAnswerTemp = $listCorrectAnswers['tabwords'];
3689
                            // for every teacher answer, check if there is a student answer
3690
                            for ($i = 0; $i < count(
3691
                                $listStudentAnswerTemp
3692
                            ); $i++) {
3693
                                $studentAnswer = trim(
3694
                                    $listStudentAnswerTemp[$i]
3695
                                );
3696
                                $found = false;
3697
                                for ($j = 0; $j < count(
3698
                                    $listTeacherAnswerTemp
3699
                                ); $j++) {
3700
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3701
                                    if (!$found) {
3702 View Code Duplication
                                        if (FillBlanks::isGoodStudentAnswer(
3703
                                            $studentAnswer,
3704
                                            $correctAnswer
3705
                                        )
3706
                                        ) {
3707
                                            $questionScore += $answerWeighting[$i];
3708
                                            $totalScore += $answerWeighting[$i];
3709
                                            $listTeacherAnswerTemp[$j] = "";
3710
                                            $found = true;
3711
                                        }
3712
                                    }
3713
                                }
3714
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3715
                                if (!$found) {
3716
                                    $listCorrectAnswers['studentscore'][$i] = 0;
3717
                                } else {
3718
                                    $listCorrectAnswers['studentscore'][$i] = 1;
3719
                                }
3720
                            }
3721
                        }
3722
                        $answer = FillBlanks::getAnswerInStudentAttempt(
3723
                            $listCorrectAnswers
3724
                        );
3725
                    }
3726
3727
                    break;
3728
                // for calculated answer
3729
                case CALCULATED_ANSWER:
3730
                    $calculatedAnswer = Session::read('calculatedAnswerId');
3731
                    $answer = $objAnswerTmp->selectAnswer(
3732
                        $calculatedAnswer[$questionId]
3733
                    );
3734
                    $preArray = explode('@@', $answer);
3735
                    $last = count($preArray) - 1;
3736
                    $answer = '';
3737
                    for ($k = 0; $k < $last; $k++) {
3738
                        $answer .= $preArray[$k];
3739
                    }
3740
                    $answerWeighting = array($answerWeighting);
3741
                    // we save the answer because it will be modified
3742
                    $temp = $answer;
3743
                    $answer = '';
3744
                    $j = 0;
3745
                    //initialise answer tags
3746
                    $userTags = $correctTags = $realText = array();
3747
                    // the loop will stop at the end of the text
3748 View Code Duplication
                    while (1) {
3749
                        // quits the loop if there are no more blanks (detect '[')
3750
                        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 3795 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...
3751
                            // adds the end of the text
3752
                            $answer = $temp;
3753
                            $realText[] = $answer;
3754
                            break; //no more "blanks", quit the loop
3755
                        }
3756
                        // adds the piece of text that is before the blank
3757
                        //and ends with '[' into a general storage array
3758
                        $realText[] = api_substr($temp, 0, $pos +1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 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...
3759
                        $answer .= api_substr($temp, 0, $pos +1);
0 ignored issues
show
Security Bug introduced by
It seems like $temp defined by api_substr($temp, $pos + 1) on line 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...
3760
                        //take the string remaining (after the last "[" we found)
3761
                        $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 3761 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...
3762
                        // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3763
                        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 3761 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...
3764
                            // adds the end of the text
3765
                            $answer .= $temp;
3766
                            break;
3767
                        }
3768
                        if ($from_database) {
3769
                            $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3770
                                          WHERE
3771
                                            exe_id = '".$exeId."' AND
3772
                                            question_id= ".intval($questionId)."";
3773
                            $resfill = Database::query($queryfill);
3774
                            $str = Database::result($resfill, 0, 'answer');
3775
                            api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3776
                            $str = str_replace('\r\n', '', $str);
3777
                            $choice = $arr[1];
3778
                            if (isset($choice[$j])) {
3779
                                $tmp = api_strrpos($choice[$j], ' / ');
3780
                                $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 3779 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...
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 3761 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
                        $answer .= ' / <font color="green"><b>' . $realCorrectTags[$i] . '</b></font>]';
3822
                        if (isset($realText[$i +1])) {
3823
                            $answer .= $realText[$i +1];
3824
                        }
3825
                    }
3826
                    break;
3827
                // for free answer
3828
                case FREE_ANSWER:
3829
                    if ($from_database) {
3830
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3831
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3832
                        $resq = Database::query($query);
3833
                        $data = Database::fetch_array($resq);
3834
3835
                        $choice = $data['answer'];
3836
                        $choice = str_replace('\r\n', '', $choice);
3837
                        $choice = stripslashes($choice);
3838
                        $questionScore = $data['marks'];
3839
3840
                        if ($questionScore == -1) {
3841
                            $totalScore+= 0;
3842
                        } else {
3843
                            $totalScore+= $questionScore;
3844
                        }
3845
                        if ($questionScore == '') {
3846
                            $questionScore = 0;
3847
                        }
3848
                        $arrques = $questionName;
3849
                        $arrans  = $choice;
3850
                    } else {
3851
                        $studentChoice = $choice;
3852
                        if ($studentChoice) {
3853
                            //Fixing negative puntation see #2193
3854
                            $questionScore = 0;
3855
                            $totalScore += 0;
3856
                        }
3857
                    }
3858
                    break;
3859
                case ORAL_EXPRESSION:
3860
                    if ($from_database) {
3861
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3862
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3863
                        $resq   = Database::query($query);
3864
                        $choice = Database::result($resq,0,'answer');
3865
                        $choice = str_replace('\r\n', '', $choice);
3866
                        $choice = stripslashes($choice);
3867
                        $questionScore = Database::result($resq,0,"marks");
3868
                        if ($questionScore==-1) {
3869
                            $totalScore+=0;
3870
                        } else {
3871
                            $totalScore+=$questionScore;
3872
                        }
3873
                        $arrques = $questionName;
3874
                        $arrans  = $choice;
3875
                    } else {
3876
                        $studentChoice = $choice;
3877
                        if ($studentChoice) {
3878
                            //Fixing negative puntation see #2193
3879
                            $questionScore = 0;
3880
                            $totalScore += 0;
3881
                        }
3882
                    }
3883
                    break;
3884
                case DRAGGABLE:
3885
                    //no break
3886
                case MATCHING_DRAGGABLE:
3887
                    //no break
3888
                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...
3889
                    if ($from_database) {
3890
                        $sql = 'SELECT id, answer, id_auto
3891
                                FROM '.$table_ans.'
3892
                                WHERE
3893
                                    c_id = '.$course_id.' AND
3894
                                    question_id = "'.$questionId.'" AND
3895
                                    correct = 0';
3896
                        $res_answer = Database::query($sql);
3897
                        // Getting the real answer
3898
                        $real_list = array();
3899
                        while ($real_answer = Database::fetch_array($res_answer)) {
3900
                            $real_list[$real_answer['id_auto']] = $real_answer['answer'];
3901
                        }
3902
3903
                        $sql = 'SELECT id, answer, correct, id_auto, ponderation
3904
                                FROM '.$table_ans.'
3905
                                WHERE
3906
                                    c_id = '.$course_id.' AND
3907
                                    question_id="'.$questionId.'" AND
3908
                                    correct <> 0
3909
                                ORDER BY id_auto';
3910
                        $res_answers = Database::query($sql);
3911
3912
                        $questionScore = 0;
3913
3914
                        while ($a_answers = Database::fetch_array($res_answers)) {
3915
                            $i_answer_id = $a_answers['id']; //3
3916
                            $s_answer_label = $a_answers['answer'];  // your daddy - your mother
3917
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
3918
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
3919
3920
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3921
                                    WHERE
3922
                                        exe_id = '$exeId' AND
3923
                                        question_id = '$questionId' AND
3924
                                        position = '$i_answer_id_auto'";
3925
3926
                            $res_user_answer = Database::query($sql);
3927
3928
                            if (Database::num_rows($res_user_answer) > 0) {
3929
                                //  rich - good looking
3930
                                $s_user_answer = Database::result($res_user_answer, 0, 0);
3931
                            } else {
3932
                                $s_user_answer = 0;
3933
                            }
3934
3935
                            $i_answerWeighting = $a_answers['ponderation'];
3936
3937
                            $user_answer = '';
3938
                            if (!empty($s_user_answer)) {
3939
                                if ($answerType == DRAGGABLE) {
3940
                                    if ($s_user_answer == $i_answer_correct_answer) {
3941
                                        $questionScore += $i_answerWeighting;
3942
                                        $totalScore += $i_answerWeighting;
3943
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
3944
                                    } else {
3945
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3946
                                    }
3947
                                } else {
3948
                                    if ($s_user_answer == $i_answer_correct_answer) {
3949
                                        $questionScore += $i_answerWeighting;
3950
                                        $totalScore += $i_answerWeighting;
3951
3952
                                        if (isset($real_list[$i_answer_id])) {
3953
                                            $user_answer = Display::span($real_list[$i_answer_id]);
3954
                                        }
3955
                                    } else {
3956
                                        $user_answer = Display::span(
3957
                                            $real_list[$s_user_answer],
3958
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
3959
                                        );
3960
                                    }
3961
                                }
3962
                            } elseif ($answerType == DRAGGABLE) {
3963
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3964
                            }
3965
3966
                            if ($show_result) {
3967
                                echo '<tr>';
3968
                                echo '<td>' . $s_answer_label . '</td>';
3969
                                echo '<td>' . $user_answer;
3970
3971
                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
3972
                                    if (isset($real_list[$i_answer_correct_answer])) {
3973
                                        echo Display::span(
3974
                                            $real_list[$i_answer_correct_answer],
3975
                                            ['style' => 'color: #008000; font-weight: bold;']
3976
                                        );
3977
                                    }
3978
                                }
3979
                                echo '</td>';
3980
                                echo '</tr>';
3981
                            }
3982
                        }
3983
                        break(2); // break the switch and the "for" condition
3984
                    } else {
3985
                        if ($answerCorrect) {
3986
                            if (isset($choice[$answerAutoId]) &&
3987
                                $answerCorrect == $choice[$answerAutoId]
3988
                            ) {
3989
                                $questionScore += $answerWeighting;
3990
                                $totalScore += $answerWeighting;
3991
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
3992
                            } else {
3993
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
3994
                                    $user_answer = Display::span(
3995
                                        $answerMatching[$choice[$answerAutoId]],
3996
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
3997
                                    );
3998
                                }
3999
                            }
4000
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4001
                        }
4002
                        break;
4003
                    }
4004
                case HOT_SPOT:
4005
                    if ($from_database) {
4006
                        $studentChoice = $em
4007
                            ->getRepository('ChamiloCoreBundle:TrackEHotspot')
4008
                            ->findOneBy([
4009
                                'hotspotExeId' => $exeId,
4010
                                'hotspotQuestionId' => $questionId,
4011
                                'hotspotAnswerId' => $answerAutoId
4012
                            ]);
4013
                        $studentChoice = $studentChoice->getHotspotCorrect();
4014
4015
                        if ($studentChoice) {
4016
                            $questionScore  += $answerWeighting;
4017
                            $totalScore     += $answerWeighting;
4018
                        }
4019
                    } else {
4020
                        if (!isset($choice[$answerAutoId])) {
4021
                            $choice[$answerAutoId] = 0;
4022
                        } else {
4023
                            $studentChoice = $choice[$answerAutoId];
4024
4025
                            $choiceIsValid = false;
4026
4027
                            if (!empty($studentChoice)) {
4028
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4029
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4030
                                $choicePoint = Geometry::decodePoint($studentChoice);
4031
4032
                                switch ($hotspotType) {
4033
                                    case 'square':
4034
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4035
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4036
                                        break;
4037
4038
                                    case 'circle':
4039
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4040
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4041
                                        break;
4042
4043
                                    case 'poly':
4044
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4045
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4046
                                        break;
4047
                                }
4048
                            }
4049
4050
                            $choice[$answerAutoId] = 0;
4051
4052
                            if ($choiceIsValid) {
4053
                                $questionScore  += $answerWeighting;
4054
                                $totalScore     += $answerWeighting;
4055
                                $choice[$answerAutoId] = 1;
4056
                            }
4057
                        }
4058
                    }
4059
                    break;
4060
                // @todo never added to chamilo
4061
                //for hotspot with fixed order
4062
                case HOT_SPOT_ORDER :
4063
                    $studentChoice = $choice['order'][$answerId];
4064
                    if ($studentChoice == $answerId) {
4065
                        $questionScore  += $answerWeighting;
4066
                        $totalScore     += $answerWeighting;
4067
                        $studentChoice = true;
4068
                    } else {
4069
                        $studentChoice = false;
4070
                    }
4071
                    break;
4072
                // for hotspot with delineation
4073
                case HOT_SPOT_DELINEATION :
4074
                    if ($from_database) {
4075
                        // getting the user answer
4076
                        $row = $em
4077
                            ->getRepository('ChamiloCoreBundle:TrackEHotspot')
4078
                            ->findOneBy([
4079
                                'hotspotExeId' => $exeId,
4080
                                'hotspotQuestionId' => $questionId,
4081
                                'hotspotAnswerId' => 1
4082
                            ]);
4083
4084
                        $choice = $row->getHotspotCorrect();
4085
                        $user_answer = $row->getHotspotCoordinate();
4086
4087
                        // THIS is very important otherwise the poly_compile will throw an error!!
4088
                        // round-up the coordinates
4089
                        $coords = explode('/',$user_answer);
4090
                        $user_array = '';
4091 View Code Duplication
                        foreach ($coords as $coord) {
4092
                            list($x,$y) = explode(';',$coord);
4093
                            $user_array .= round($x).';'.round($y).'/';
4094
                        }
4095
                        $user_array = substr($user_array,0,-1);
4096
                    } else {
4097
                        if (!empty($studentChoice)) {
4098
                            $newquestionList[] = $questionId;
4099
                        }
4100
4101
                        if ($answerId === 1) {
4102
                            $studentChoice = $choice[$answerId];
4103
                            $questionScore += $answerWeighting;
4104
4105
                            if ($hotspot_delineation_result[1]==1) {
4106
                                $totalScore += $answerWeighting; //adding the total
4107
                            }
4108
                        }
4109
                    }
4110
                    $_SESSION['hotspot_coord'][1]	= $delineation_cord;
4111
                    $_SESSION['hotspot_dest'][1]	= $answer_delineation_destination;
4112
                    break;
4113
            } // end switch Answertype
4114
4115
            if ($show_result) {
4116
                if ($debug) error_log('show result '.$show_result);
4117
                if ($from == 'exercise_result') {
4118
                    if ($debug) error_log('Showing questions $from '.$from);
4119
                    //display answers (if not matching type, or if the answer is correct)
4120
                    if (
4121
                        !in_array(
4122
                            $answerType,
4123
                            [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]
4124
                        ) ||
4125
                        $answerCorrect
4126
                    ) {
4127
                        if (
4128
                            in_array(
4129
                                $answerType,
4130
                                array(
4131
                                    UNIQUE_ANSWER,
4132
                                    UNIQUE_ANSWER_IMAGE,
4133
                                    UNIQUE_ANSWER_NO_OPTION,
4134
                                    MULTIPLE_ANSWER,
4135
                                    MULTIPLE_ANSWER_COMBINATION,
4136
                                    GLOBAL_MULTIPLE_ANSWER
4137
                                )
4138
                            )
4139
                        ) {
4140
                            //if ($origin != 'learnpath') {
4141
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4142
                                $feedback_type,
4143
                                $answerType,
4144
                                $studentChoice,
4145
                                $answer,
4146
                                $answerComment,
4147
                                $answerCorrect,
4148
                                0,
4149
                                0,
4150
                                0,
4151
                                $results_disabled
4152
                            );
4153
                            //}
4154
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4155
                            //if ($origin!='learnpath') {
4156
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4157
                                $feedback_type,
4158
                                $answerType,
4159
                                $studentChoice,
4160
                                $answer,
4161
                                $answerComment,
4162
                                $answerCorrect,
4163
                                0,
4164
                                $questionId,
4165
                                0,
4166
                                $results_disabled
4167
                            );
4168
                            //}
4169
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ) {
4170
                            //	if ($origin!='learnpath') {
4171
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4172
                                $feedback_type,
4173
                                $answerType,
4174
                                $studentChoice,
4175
                                $answer,
4176
                                $answerComment,
4177
                                $answerCorrect,
4178
                                0,
4179
                                0,
4180
                                0,
4181
                                $results_disabled
4182
                            );
4183
                            //}
4184
                        } elseif ($answerType == FILL_IN_BLANKS) {
4185
                            //if ($origin!='learnpath') {
4186
                            ExerciseShowFunctions::display_fill_in_blanks_answer($feedback_type, $answer,0,0, $results_disabled);
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3114 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...
4187
                            //	}
4188
                        } elseif ($answerType == CALCULATED_ANSWER) {
4189
                            //if ($origin!='learnpath') {
4190
                            ExerciseShowFunctions::display_calculated_answer($feedback_type, $answer,0,0);
4191
                            //  }
4192
                        } elseif ($answerType == FREE_ANSWER) {
4193
                            //if($origin != 'learnpath') {
4194
                            ExerciseShowFunctions::display_free_answer(
4195
                                $feedback_type,
4196
                                $choice,
4197
                                $exeId,
4198
                                $questionId,
4199
                                $questionScore
4200
                            );
4201
                            //}
4202
                        } elseif ($answerType == ORAL_EXPRESSION) {
4203
                            // to store the details of open questions in an array to be used in mail
4204
                            //if ($origin != 'learnpath') {
4205
                            ExerciseShowFunctions::display_oral_expression_answer(
4206
                                $feedback_type,
4207
                                $choice,
4208
                                0,
4209
                                0,
4210
                                $nano);
4211
                            //}
4212
                        } elseif ($answerType == HOT_SPOT) {
4213
                            //if ($origin != 'learnpath') {
4214
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4215
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4216
                                    break;
4217
                                }
4218
                            }
4219
4220
                            ExerciseShowFunctions::display_hotspot_answer(
4221
                                $feedback_type,
4222
                                ++$correctAnswerId,
4223
                                $answer,
4224
                                $studentChoice,
4225
                                $answerComment,
4226
                                $results_disabled,
4227
                                $answerId
4228
                            );
4229
                            //	}
4230
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4231
                            //if ($origin != 'learnpath') {
4232
                            ExerciseShowFunctions::display_hotspot_order_answer(
4233
                                $feedback_type,
4234
                                $answerId,
4235
                                $answer,
4236
                                $studentChoice,
4237
                                $answerComment
4238
                            );
4239
                            //}
4240
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4241
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4242
4243
                            //round-up the coordinates
4244
                            $coords = explode('/',$user_answer);
4245
                            $user_array = '';
4246 View Code Duplication
                            foreach ($coords as $coord) {
4247
                                list($x,$y) = explode(';',$coord);
4248
                                $user_array .= round($x).';'.round($y).'/';
4249
                            }
4250
                            $user_array = substr($user_array,0,-1);
4251
4252 View Code Duplication
                            if ($next) {
4253
4254
                                $user_answer = $user_array;
4255
4256
                                // we compare only the delineation not the other points
4257
                                $answer_question = $_SESSION['hotspot_coord'][1];
4258
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4259
4260
                                //calculating the area
4261
                                $poly_user = convert_coordinates($user_answer, '/');
4262
                                $poly_answer = convert_coordinates($answer_question, '|');
4263
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4264
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4265
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4266
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4267
4268
                                $overlap = $poly_results['both'];
4269
                                $poly_answer_area = $poly_results['s1'];
4270
                                $poly_user_area = $poly_results['s2'];
4271
                                $missing = $poly_results['s1Only'];
4272
                                $excess = $poly_results['s2Only'];
4273
4274
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4275
                                // //this is an area in pixels
4276
                                if ($debug > 0) {
4277
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4278
                                }
4279
4280
                                if ($overlap < 1) {
4281
                                    //shortcut to avoid complicated calculations
4282
                                    $final_overlap = 0;
4283
                                    $final_missing = 100;
4284
                                    $final_excess = 100;
4285
                                } else {
4286
                                    // the final overlap is the percentage of the initial polygon
4287
                                    // that is overlapped by the user's polygon
4288
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4289
                                    if ($debug > 1) {
4290
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4291
                                    }
4292
                                    // the final missing area is the percentage of the initial polygon
4293
                                    // that is not overlapped by the user's polygon
4294
                                    $final_missing = 100 - $final_overlap;
4295
                                    if ($debug > 1) {
4296
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4297
                                    }
4298
                                    // the final excess area is the percentage of the initial polygon's size
4299
                                    // that is covered by the user's polygon outside of the initial polygon
4300
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4301
                                    if ($debug > 1) {
4302
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4303
                                    }
4304
                                }
4305
4306
                                //checking the destination parameters parsing the "@@"
4307
                                $destination_items= explode('@@', $answerDestination);
4308
                                $threadhold_total = $destination_items[0];
4309
                                $threadhold_items=explode(';',$threadhold_total);
4310
                                $threadhold1 = $threadhold_items[0]; // overlap
4311
                                $threadhold2 = $threadhold_items[1]; // excess
4312
                                $threadhold3 = $threadhold_items[2];	 //missing
4313
4314
                                // if is delineation
4315
                                if ($answerId===1) {
4316
                                    //setting colors
4317
                                    if ($final_overlap>=$threadhold1) {
4318
                                        $overlap_color=true; //echo 'a';
4319
                                    }
4320
                                    //echo $excess.'-'.$threadhold2;
4321
                                    if ($final_excess<=$threadhold2) {
4322
                                        $excess_color=true; //echo 'b';
4323
                                    }
4324
                                    //echo '--------'.$missing.'-'.$threadhold3;
4325
                                    if ($final_missing<=$threadhold3) {
4326
                                        $missing_color=true; //echo 'c';
4327
                                    }
4328
4329
                                    // if pass
4330
                                    if (
4331
                                        $final_overlap >= $threadhold1 &&
4332
                                        $final_missing <= $threadhold3 &&
4333
                                        $final_excess <= $threadhold2
4334
                                    ) {
4335
                                        $next=1; //go to the oars
4336
                                        $result_comment=get_lang('Acceptable');
4337
                                        $final_answer = 1;	// do not update with  update_exercise_attempt
4338
                                    } else {
4339
                                        $next=0;
4340
                                        $result_comment=get_lang('Unacceptable');
4341
                                        $comment=$answerDestination=$objAnswerTmp->selectComment(1);
4342
                                        $answerDestination=$objAnswerTmp->selectDestination(1);
4343
                                        //checking the destination parameters parsing the "@@"
4344
                                        $destination_items= explode('@@', $answerDestination);
4345
                                    }
4346
                                } elseif($answerId>1) {
4347
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4348
                                        if ($debug>0) {
4349
                                            error_log(__LINE__.' - answerId is of type noerror',0);
4350
                                        }
4351
                                        //type no error shouldn't be treated
4352
                                        $next = 1;
4353
                                        continue;
4354
                                    }
4355
                                    if ($debug>0) {
4356
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR',0);
4357
                                    }
4358
                                    //check the intersection between the oar and the user
4359
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4360
                                    //echo 'official';print_r($x_list);print_r($y_list);
4361
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4362
                                    $inter= $result['success'];
4363
4364
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4365
                                    $delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4366
4367
                                    $poly_answer = convert_coordinates($delineation_cord,'|');
4368
                                    $max_coord = poly_get_max($poly_user,$poly_answer);
4369
                                    $poly_answer_compiled = poly_compile($poly_answer,$max_coord);
4370
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4371
4372
                                    if ($overlap == false) {
4373
                                        //all good, no overlap
4374
                                        $next = 1;
4375
                                        continue;
4376
                                    } else {
4377
                                        if ($debug>0) {
4378
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit',0);
4379
                                        }
4380
                                        $organs_at_risk_hit++;
4381
                                        //show the feedback
4382
                                        $next=0;
4383
                                        $comment=$answerDestination=$objAnswerTmp->selectComment($answerId);
4384
                                        $answerDestination=$objAnswerTmp->selectDestination($answerId);
4385
4386
                                        $destination_items= explode('@@', $answerDestination);
4387
                                        $try_hotspot=$destination_items[1];
4388
                                        $lp_hotspot=$destination_items[2];
4389
                                        $select_question_hotspot=$destination_items[3];
4390
                                        $url_hotspot=$destination_items[4];
4391
                                    }
4392
                                }
4393
                            } else {	// the first delineation feedback
4394
                                if ($debug>0) {
4395
                                    error_log(__LINE__.' first',0);
4396
                                }
4397
                            }
4398
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4399
                            echo '<tr>';
4400
                            echo Display::tag('td', $answerMatching[$answerId]);
4401
                            echo Display::tag(
4402
                                'td',
4403
                                "$user_answer / " . Display::tag(
4404
                                    'strong',
4405
                                    $answerMatching[$answerCorrect],
4406
                                    ['style' => 'color: #008000; font-weight: bold;']
4407
                                )
4408
                            );
4409
                            echo '</tr>';
4410
                        }
4411
                    }
4412
                } else {
4413
                    if ($debug) error_log('Showing questions $from '.$from);
4414
4415
                    switch ($answerType) {
4416
                        case UNIQUE_ANSWER:
4417
                        case UNIQUE_ANSWER_IMAGE:
4418
                        case UNIQUE_ANSWER_NO_OPTION:
4419
                        case MULTIPLE_ANSWER:
4420
                        case GLOBAL_MULTIPLE_ANSWER :
4421 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION:
4422
                            if ($answerId == 1) {
4423
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4424
                                    $feedback_type,
4425
                                    $answerType,
4426
                                    $studentChoice,
4427
                                    $answer,
4428
                                    $answerComment,
4429
                                    $answerCorrect,
4430
                                    $exeId,
4431
                                    $questionId,
4432
                                    $answerId,
4433
                                    $results_disabled
4434
                                );
4435
                            } else {
4436
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4437
                                    $feedback_type,
4438
                                    $answerType,
4439
                                    $studentChoice,
4440
                                    $answer,
4441
                                    $answerComment,
4442
                                    $answerCorrect,
4443
                                    $exeId,
4444
                                    $questionId,
4445
                                    "",
4446
                                    $results_disabled
4447
                                );
4448
                            }
4449
                            break;
4450 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4451
                            if ($answerId == 1) {
4452
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4453
                                    $feedback_type,
4454
                                    $answerType,
4455
                                    $studentChoice,
4456
                                    $answer,
4457
                                    $answerComment,
4458
                                    $answerCorrect,
4459
                                    $exeId,
4460
                                    $questionId,
4461
                                    $answerId,
4462
                                    $results_disabled
4463
                                );
4464
                            } else {
4465
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4466
                                    $feedback_type,
4467
                                    $answerType,
4468
                                    $studentChoice,
4469
                                    $answer,
4470
                                    $answerComment,
4471
                                    $answerCorrect,
4472
                                    $exeId,
4473
                                    $questionId,
4474
                                    "",
4475
                                    $results_disabled
4476
                                );
4477
                            }
4478
                            break;
4479 View Code Duplication
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4480
                            if ($answerId == 1) {
4481
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4482
                                    $feedback_type,
4483
                                    $answerType,
4484
                                    $studentChoice,
4485
                                    $answer,
4486
                                    $answerComment,
4487
                                    $answerCorrect,
4488
                                    $exeId,
4489
                                    $questionId,
4490
                                    $answerId,
4491
                                    $results_disabled
4492
                                );
4493
                            } else {
4494
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4495
                                    $feedback_type,
4496
                                    $answerType,
4497
                                    $studentChoice,
4498
                                    $answer,
4499
                                    $answerComment,
4500
                                    $answerCorrect,
4501
                                    $exeId,
4502
                                    $questionId,
4503
                                    "",
4504
                                    $results_disabled
4505
                                );
4506
                            }
4507
                            break;
4508
                        case FILL_IN_BLANKS:
4509
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4510
                                $feedback_type,
4511
                                $answer,
4512
                                $exeId,
4513
                                $questionId,
4514
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3114 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...
4515
                                $str
4516
                            );
4517
                            break;
4518
                        case CALCULATED_ANSWER:
4519
                            ExerciseShowFunctions::display_calculated_answer(
4520
                                $feedback_type,
4521
                                $answer,
4522
                                $exeId,
4523
                                $questionId
4524
                            );
4525
                            break;
4526
                        case FREE_ANSWER:
4527
                            echo ExerciseShowFunctions::display_free_answer(
4528
                                $feedback_type,
4529
                                $choice,
4530
                                $exeId,
4531
                                $questionId,
4532
                                $questionScore
4533
                            );
4534
                            break;
4535
                        case ORAL_EXPRESSION:
4536
                            echo '<tr>
4537
                                <td valign="top">' . ExerciseShowFunctions::display_oral_expression_answer(
4538
                                    $feedback_type,
4539
                                    $choice,
4540
                                    $exeId,
4541
                                    $questionId,
4542
                                    $nano
4543
                                ) . '</td>
4544
                                </tr>
4545
                                </table>';
4546
                            break;
4547
                        case HOT_SPOT:
4548
                            ExerciseShowFunctions::display_hotspot_answer(
0 ignored issues
show
Bug introduced by
The call to display_hotspot_answer() misses a required argument $orderColor.

This check looks for function calls that miss required arguments.

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