Passed
Push — 1.10.x ( 6b51fa...7f42c3 )
by
unknown
49:18
created

Exercise::edit_question_to_remind()   C

Complexity

Conditions 11
Paths 11

Size

Total Lines 47
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 47
rs 5.2653
cc 11
eloc 33
nc 11
nop 3

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1375
    }
1376
1377
1378
    /**
1379
     * @param int $value
1380
     */
1381
    public function updateEndButton($value)
1382
    {
1383
        $this->endButton = intval($value);
1384
    }
1385
1386
    /**
1387
     * @param string $value
1388
     */
1389
    public function setOnSuccessMessage($value)
1390
    {
1391
        $this->onSuccessMessage = $value;
1392
    }
1393
1394
    /**
1395
     * @param string $value
1396
     */
1397
    public function setOnFailedMessage($value)
1398
    {
1399
        $this->onFailedMessage = $value;
1400
    }
1401
1402
    /**
1403
     * @param $value
1404
     */
1405
    public function setModelType($value)
1406
    {
1407
        $this->modelType = intval($value);
1408
    }
1409
1410
    /**
1411
     * @param intval $value
1412
     */
1413
    public function setQuestionSelectionType($value)
1414
    {
1415
        $this->questionSelectionType = intval($value);
1416
    }
1417
1418
    /**
1419
     * @return int
1420
     */
1421
    public function getQuestionSelectionType()
1422
    {
1423
        return $this->questionSelectionType;
1424
    }
1425
1426
    /**
1427
     * @param array $categories
1428
     */
1429
    public function updateCategories($categories)
1430
    {
1431
        if (!empty($categories)) {
1432
            $categories = array_map('intval', $categories);
1433
            $this->categories = $categories;
1434
        }
1435
    }
1436
1437
    /**
1438
     * changes the exercise sound file
1439
     *
1440
     * @author Olivier Brouckaert
1441
     * @param string $sound - exercise sound file
1442
     * @param string $delete - ask to delete the file
1443
     */
1444
    public function updateSound($sound,$delete)
1445
    {
1446
        global $audioPath, $documentPath;
1447
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1448
1449
        if ($sound['size'] && (strstr($sound['type'],'audio') || strstr($sound['type'],'video'))) {
1450
            $this->sound=$sound['name'];
1451
1452
            if (@move_uploaded_file($sound['tmp_name'],$audioPath.'/'.$this->sound)) {
1453
                $query = "SELECT 1 FROM $TBL_DOCUMENT
1454
                        WHERE c_id = ".$this->course_id." AND path='".str_replace($documentPath,'',$audioPath).'/'.$this->sound."'";
1455
                $result=Database::query($query);
1456
1457
                if (!Database::num_rows($result)) {
1458
                    $id = add_document(
1459
                        $this->course,
1460
                        str_replace($documentPath,'',$audioPath).'/'.$this->sound,
1461
                        'file',
1462
                        $sound['size'],
1463
                        $sound['name']
1464
                    );
1465
                    api_item_property_update(
1466
                        $this->course,
1467
                        TOOL_DOCUMENT,
1468
                        $id,
1469
                        'DocumentAdded',
1470
                        api_get_user_id()
1471
                    );
1472
                    item_property_update_on_folder(
1473
                        $this->course,
1474
                        str_replace($documentPath, '', $audioPath),
1475
                        api_get_user_id()
1476
                    );
1477
                }
1478
            }
1479
        } elseif($delete && is_file($audioPath.'/'.$this->sound)) {
1480
            $this->sound='';
1481
        }
1482
    }
1483
1484
    /**
1485
     * changes the exercise type
1486
     *
1487
     * @author Olivier Brouckaert
1488
     * @param integer $type - exercise type
1489
     */
1490
    public function updateType($type)
1491
    {
1492
        $this->type=$type;
1493
    }
1494
1495
    /**
1496
     * sets to 0 if questions are not selected randomly
1497
     * if questions are selected randomly, sets the draws
1498
     *
1499
     * @author Olivier Brouckaert
1500
     * @param integer $random - 0 if not random, otherwise the draws
1501
     */
1502
    public function setRandom($random)
1503
    {
1504
        /*if ($random == 'all') {
1505
            $random = $this->selectNbrQuestions();
1506
        }*/
1507
        $this->random = $random;
1508
    }
1509
1510
    /**
1511
     * sets to 0 if answers are not selected randomly
1512
     * if answers are selected randomly
1513
     * @author Juan Carlos Rana
1514
     * @param integer $random_answers - random answers
1515
     */
1516
    public function updateRandomAnswers($random_answers)
1517
    {
1518
        $this->random_answers = $random_answers;
1519
    }
1520
1521
    /**
1522
     * enables the exercise
1523
     *
1524
     * @author Olivier Brouckaert
1525
     */
1526
    public function enable()
1527
    {
1528
        $this->active=1;
1529
    }
1530
1531
    /**
1532
     * disables the exercise
1533
     *
1534
     * @author Olivier Brouckaert
1535
     */
1536
    public function disable()
1537
    {
1538
        $this->active=0;
1539
    }
1540
1541
    /**
1542
     * Set disable results
1543
     */
1544
    public function disable_results()
1545
    {
1546
        $this->results_disabled = true;
1547
    }
1548
1549
    /**
1550
     * Enable results
1551
     */
1552
    public function enable_results()
1553
    {
1554
        $this->results_disabled = false;
1555
    }
1556
1557
    /**
1558
     * @param int $results_disabled
1559
     */
1560
    public function updateResultsDisabled($results_disabled)
1561
    {
1562
        $this->results_disabled = intval($results_disabled);
1563
    }
1564
1565
    /**
1566
     * updates the exercise in the data base
1567
     *
1568
     * @author Olivier Brouckaert
1569
     */
1570
    public function save($type_e = '')
1571
    {
1572
        $_course = $this->course;
1573
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1574
1575
        $id = $this->id;
1576
        $exercise = $this->exercise;
1577
        $description = $this->description;
1578
        $sound = $this->sound;
1579
        $type = $this->type;
1580
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1581
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1582
        $random = $this->random;
1583
        $random_answers = $this->random_answers;
1584
        $active = $this->active;
1585
        $propagate_neg = $this->propagate_neg;
1586
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1587
        $randomByCat = intval($this->randomByCat);
1588
        $text_when_finished = $this->text_when_finished;
1589
        $display_category_name = intval($this->display_category_name);
1590
        $pass_percentage = intval($this->pass_percentage);
1591
        $session_id = $this->sessionId;
1592
1593
        //If direct we do not show results
1594
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1595
            $results_disabled = 0;
1596
        } else {
1597
            $results_disabled = intval($this->results_disabled);
1598
        }
1599
1600
        $expired_time = intval($this->expired_time);
1601
1602
        // Exercise already exists
1603
        if ($id) {
1604
            // we prepare date in the database using the api_get_utc_datetime() function
1605 View Code Duplication
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
1606
                $start_time = $this->start_time;
1607
            } else {
1608
                $start_time = '0000-00-00 00:00:00';
1609
            }
1610
1611 View Code Duplication
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
1612
                $end_time = $this->end_time;
1613
            } else {
1614
                $end_time = '0000-00-00 00:00:00';
1615
            }
1616
1617
            $params = [
1618
                'title' => $exercise,
1619
                'description' => $description,
1620
            ];
1621
1622
            $paramsExtra = [];
1623
            if ($type_e != 'simple') {
1624
                $paramsExtra = [
1625
                    'sound' => $sound,
1626
                    'type' => $type,
1627
                    'random' => $random,
1628
                    'random_answers' => $random_answers,
1629
                    'active' => $active,
1630
                    'feedback_type' => $feedback_type,
1631
                    'start_time' => $start_time,
1632
                    'end_time' => $end_time,
1633
                    'max_attempt' => $attempts,
1634
                    'expired_time' => $expired_time,
1635
                    'propagate_neg' => $propagate_neg,
1636
                    'review_answers' => $review_answers,
1637
                    'random_by_category' => $randomByCat,
1638
                    'text_when_finished' => $text_when_finished,
1639
                    'display_category_name' => $display_category_name,
1640
                    'pass_percentage' => $pass_percentage,
1641
                    'results_disabled' => $results_disabled,
1642
                ];
1643
            }
1644
1645
            $params = array_merge($params, $paramsExtra);
1646
1647
            if ($this->specialCategoryOrders) {
1648
                $params['question_selection_type'] = intval($this->getQuestionSelectionType());
1649
            }
1650
1651
            Database::update(
1652
                $TBL_EXERCISES,
1653
                $params,
1654
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1655
            );
1656
1657
            // update into the item_property table
1658
            api_item_property_update(
1659
                $_course,
1660
                TOOL_QUIZ,
1661
                $id,
1662
                'QuizUpdated',
1663
                api_get_user_id()
1664
            );
1665
1666
            if (api_get_setting('search_enabled')=='true') {
1667
                $this->search_engine_edit();
1668
            }
1669
        } else {
1670
            // Creates a new exercise
1671
1672
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1673
            // for date because, bellow, we call function api_set_default_visibility()
1674
            // In this function, api_set_default_visibility,
1675
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1676
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1677 View Code Duplication
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
1678
                $start_time = $this->start_time;
1679
            } else {
1680
                $start_time = '0000-00-00 00:00:00';
1681
            }
1682
1683 View Code Duplication
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
1684
                $end_time = $this->end_time;
1685
            } else {
1686
                $end_time = '0000-00-00 00:00:00';
1687
            }
1688
1689
            $params = [
1690
                'c_id' => $this->course_id,
1691
                'start_time' => $start_time,
1692
                'end_time' => $end_time,
1693
                'title' => $exercise,
1694
                'description' => $description,
1695
                'sound' => $sound,
1696
                'type' => $type,
1697
                'random' => $random,
1698
                'random_answers' => $random_answers,
1699
                'active' => $active,
1700
                'results_disabled' => $results_disabled,
1701
                'max_attempt' => $attempts,
1702
                'feedback_type' => $feedback_type,
1703
                'expired_time' => $expired_time,
1704
                'session_id' => $session_id,
1705
                'review_answers' => $review_answers,
1706
                'random_by_category' => $randomByCat,
1707
                'text_when_finished' => $text_when_finished,
1708
                'display_category_name' => $display_category_name,
1709
                'pass_percentage' => $pass_percentage
1710
            ];
1711
1712
            $this->id = Database::insert($TBL_EXERCISES, $params);
1713
1714
            if ($this->id) {
1715
1716
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1717
                Database::query($sql);
1718
1719
                if ($this->quizRelCategoryTable) {
1720
                    $sql = "UPDATE $TBL_EXERCICES
0 ignored issues
show
Bug introduced by
The variable $TBL_EXERCICES does not exist. Did you mean $TBL_EXERCISES?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

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