Test Setup Failed
Push — master ( ec638a...cb9435 )
by Julito
51:10
created

Exercise::showQuestion()   F

Complexity

Conditions 132
Paths > 20000

Size

Total Lines 874
Code Lines 478

Duplication

Lines 209
Ratio 23.91 %

Importance

Changes 0
Metric Value
cc 132
eloc 478
nc 429496.7295
nop 12
dl 209
loc 874
rs 2
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A Exercise::returnQuestionListByAttempt() 0 4 1
F Exercise::displayQuestionListByAttempt() 27 276 39
B Exercise::get_question_ribbon() 0 37 5
B Exercise::getQuestionWithCategories() 0 26 2
C Exercise::get_max_score() 0 55 14
A Exercise::get_formated_title() 0 4 1
A Exercise::get_formated_title_variable() 0 4 1
A Exercise::format_title() 0 4 1
A Exercise::format_title_variable() 0 4 1
A Exercise::getExercisesByCourseSession() 23 23 2
B Exercise::getExerciseAndResult() 0 39 5
C Exercise::getNextQuestionId() 0 71 16
B Exercise::getPositionInCompressedQuestionList() 0 23 6
C Exercise::getCorrectAnswersInAllAttempts() 0 47 9

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use ChamiloSession as Session;
5
use Chamilo\CoreBundle\Entity\TrackEHotspot;
6
7
/**
8
 * Class Exercise
9
 *
10
 * Allows to instantiate an object of type Exercise
11
 * @package chamilo.exercise
12
 * @todo use doctrine object, use getters and setters correctly
13
 * @author Olivier Brouckaert
14
 * @author Julio Montoya Cleaning exercises
15
 * Modified by Hubert Borderiou #294
16
 */
17
class Exercise
18
{
19
    public $id;
20
    public $name;
21
    public $title;
22
    public $exercise;
23
    public $description;
24
    public $sound;
25
    public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
26
    public $random;
27
    public $random_answers;
28
    public $active;
29
    public $timeLimit;
30
    public $attempts;
31
    public $feedback_type;
32
    public $end_time;
33
    public $start_time;
34
    public $questionList;  // array with the list of this exercise's questions
35
    /* including question list of the media */
36
    public $questionListUncompressed;
37
    public $results_disabled;
38
    public $expired_time;
39
    public $course;
40
    public $course_id;
41
    public $propagate_neg;
42
    public $saveCorrectAnswers;
43
    public $review_answers;
44
    public $randomByCat;
45
    public $text_when_finished;
46
    public $display_category_name;
47
    public $pass_percentage;
48
    public $edit_exercise_in_lp = false;
49
    public $is_gradebook_locked = false;
50
    public $exercise_was_added_in_lp = false;
51
    public $lpList = array();
52
    public $force_edit_exercise_in_lp = false;
53
    public $categories;
54
    public $categories_grouping = true;
55
    public $endButton = 0;
56
    public $categoryWithQuestionList;
57
    public $mediaList;
58
    public $loadQuestionAJAX = false;
59
    // Notification send to the teacher.
60
    public $emailNotificationTemplate = null;
61
    // Notification send to the student.
62
    public $emailNotificationTemplateToUser = null;
63
    public $countQuestions = 0;
64
    public $fastEdition = false;
65
    public $modelType = 1;
66
    public $questionSelectionType = EX_Q_SELECTION_ORDERED;
67
    public $hideQuestionTitle = 0;
68
    public $scoreTypeModel = 0;
69
    public $categoryMinusOne = true; // Shows the category -1: See BT#6540
70
    public $globalCategoryId = null;
71
    public $onSuccessMessage = null;
72
    public $onFailedMessage = null;
73
    public $emailAlert;
74
    public $notifyUserByEmail = '';
75
    public $sessionId = 0;
76
77
    /**
78
     * Constructor of the class
79
     *
80
     * @author Olivier Brouckaert
81
     */
82
    public function __construct($course_id = null)
83
    {
84
        $this->id = 0;
85
        $this->exercise = '';
86
        $this->description = '';
87
        $this->sound = '';
88
        $this->type = ALL_ON_ONE_PAGE;
89
        $this->random = 0;
90
        $this->random_answers = 0;
91
        $this->active = 1;
92
        $this->questionList = array();
93
        $this->timeLimit = 0;
94
        $this->end_time = '';
95
        $this->start_time = '';
96
        $this->results_disabled = 1;
97
        $this->expired_time = 0;
98
        $this->propagate_neg = 0;
99
        $this->saveCorrectAnswers = 0;
100
        $this->review_answers = false;
101
        $this->randomByCat = 0;
102
        $this->text_when_finished = '';
103
        $this->display_category_name = 0;
104
        $this->pass_percentage = '';
105
        $this->modelType = 1;
106
        $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
107
        $this->endButton = 0;
108
        $this->scoreTypeModel = 0;
109
        $this->globalCategoryId = null;
110
111
        if (!empty($course_id)) {
112
            $course_info = api_get_course_info_by_id($course_id);
113
        } else {
114
            $course_info = api_get_course_info();
115
        }
116
        $this->course_id = $course_info['real_id'];
117
        $this->course = $course_info;
118
    }
119
120
    /**
121
     * Reads exercise information from the data base
122
     *
123
     * @author Olivier Brouckaert
124
     * @param integer $id - exercise Id
125
     * @param bool $parseQuestionList
126
     *
127
     * @return boolean - true if exercise exists, otherwise false
128
     */
129
    public function read($id, $parseQuestionList = true)
130
    {
131
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
132
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
133
134
        $id  = (int)$id;
135
        if (empty($this->course_id)) {
136
137
            return false;
138
        }
139
        $sql = "SELECT * FROM $TBL_EXERCISES 
140
                WHERE c_id = ".$this->course_id." AND id = ".$id;
141
        $result = Database::query($sql);
142
143
        // if the exercise has been found
144
        if ($object = Database::fetch_object($result)) {
145
            $this->id = $id;
146
            $this->exercise = $object->title;
147
            $this->name = $object->title;
148
            $this->title = $object->title;
149
            $this->description = $object->description;
150
            $this->sound = $object->sound;
151
            $this->type = $object->type;
152
            if (empty($this->type)) {
153
                $this->type = ONE_PER_PAGE;
154
            }
155
            $this->random = $object->random;
156
            $this->random_answers = $object->random_answers;
157
            $this->active = $object->active;
158
            $this->results_disabled = $object->results_disabled;
159
            $this->attempts = $object->max_attempt;
160
            $this->feedback_type = $object->feedback_type;
161
            $this->propagate_neg = $object->propagate_neg;
162
            $this->saveCorrectAnswers = $object->save_correct_answers;
163
            $this->randomByCat = $object->random_by_category;
164
            $this->text_when_finished = $object->text_when_finished;
165
            $this->display_category_name = $object->display_category_name;
166
            $this->pass_percentage = $object->pass_percentage;
167
            $this->sessionId = $object->session_id;
168
            $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
169
            $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
170
            $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
171
            $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null;
172
            $this->hideQuestionTitle = isset($object->hide_question_title) ? (int)$object->hide_question_title : 0;
173
174
            $sql = "SELECT lp_id, max_score
175
                    FROM $table_lp_item
176
                    WHERE   c_id = {$this->course_id} AND
177
                            item_type = '".TOOL_QUIZ."' AND
178
                            path = '".$id."'";
179
            $result = Database::query($sql);
180
181
            if (Database::num_rows($result) > 0) {
182
                $this->exercise_was_added_in_lp = true;
183
                $this->lpList = Database::store_result($result, 'ASSOC');
184
            }
185
186
            $this->force_edit_exercise_in_lp = api_get_configuration_value('force_edit_exercise_in_lp');
187
188
            if ($this->exercise_was_added_in_lp) {
189
                $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
190
            } else {
191
                $this->edit_exercise_in_lp = true;
192
            }
193
194
            if (!empty($object->end_time)) {
195
                $this->end_time = $object->end_time;
196
            }
197
            if (!empty($object->start_time)) {
198
                $this->start_time = $object->start_time;
199
            }
200
201
            // Control time
202
            $this->expired_time = $object->expired_time;
203
204
            // Checking if question_order is correctly set
205
            if ($parseQuestionList) {
206
                $this->setQuestionList(true);
207
            }
208
209
            //overload questions list with recorded questions list
210
            //load questions only for exercises of type 'one question per page'
211
            //this is needed only is there is no questions
212
            /*
213
			// @todo not sure were in the code this is used somebody mess with the exercise tool
214
			// @todo don't know who add that config and why $_configuration['live_exercise_tracking']
215
			global $_configuration, $questionList;
216
			if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST' && defined('QUESTION_LIST_ALREADY_LOGGED') &&
217
			isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']) {
218
				$this->questionList = $questionList;
219
			}*/
220
            return true;
221
        }
222
223
        return false;
224
    }
225
226
    /**
227
     * @return string
228
     */
229
    public function getCutTitle()
230
    {
231
        return cut($this->exercise, EXERCISE_MAX_NAME_SIZE);
232
    }
233
234
    /**
235
     * returns the exercise ID
236
     *
237
     * @author Olivier Brouckaert
238
     * @return int - exercise ID
239
     */
240
    public function selectId()
241
    {
242
        return $this->id;
243
    }
244
245
    /**
246
     * returns the exercise title
247
     *
248
     * @author Olivier Brouckaert
249
     * @return string - exercise title
250
     */
251
    public function selectTitle()
252
    {
253
        return $this->exercise;
254
    }
255
256
    /**
257
     * returns the number of attempts setted
258
     *
259
     * @return int - exercise attempts
260
     */
261
    public function selectAttempts()
262
    {
263
        return $this->attempts;
264
    }
265
266
    /** returns the number of FeedbackType  *
267
     *  0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback
268
     * @return int - exercise attempts
269
     */
270
    public function selectFeedbackType()
271
    {
272
        return $this->feedback_type;
273
    }
274
275
    /**
276
     * returns the time limit
277
     */
278
    public function selectTimeLimit()
279
    {
280
        return $this->timeLimit;
281
    }
282
283
    /**
284
     * returns the exercise description
285
     *
286
     * @author Olivier Brouckaert
287
     * @return string - exercise description
288
     */
289
    public function selectDescription()
290
    {
291
        return $this->description;
292
    }
293
294
    /**
295
     * returns the exercise sound file
296
     *
297
     * @author Olivier Brouckaert
298
     * @return string - exercise description
299
     */
300
    public function selectSound()
301
    {
302
        return $this->sound;
303
    }
304
305
    /**
306
     * returns the exercise type
307
     *
308
     * @author Olivier Brouckaert
309
     * @return integer - exercise type
310
     */
311
    public function selectType()
312
    {
313
        return $this->type;
314
    }
315
316
    /**
317
     * @return int
318
     */
319
    public function getModelType()
320
    {
321
        return $this->modelType;
322
    }
323
324
    /**
325
     * @return int
326
     */
327
    public function selectEndButton()
328
    {
329
        return $this->endButton;
330
    }
331
332
    /**
333
     * @return string
334
     */
335
    public function getOnSuccessMessage()
336
    {
337
        return $this->onSuccessMessage;
338
    }
339
340
    /**
341
     * @return string
342
     */
343
    public function getOnFailedMessage()
344
    {
345
        return $this->onFailedMessage;
346
    }
347
348
    /**
349
     * @author hubert borderiou 30-11-11
350
     * @return integer : do we display the question category name for students
351
     */
352
    public function selectDisplayCategoryName()
353
    {
354
        return $this->display_category_name;
355
    }
356
357
    /**
358
     * @return int
359
     */
360
    public function selectPassPercentage()
361
    {
362
        return $this->pass_percentage;
363
    }
364
365
    /**
366
     *
367
     * Modify object to update the switch display_category_name
368
     * @author hubert borderiou 30-11-11
369
     * @param int $in_txt is an integer 0 or 1
370
     */
371
    public function updateDisplayCategoryName($in_txt)
372
    {
373
        $this->display_category_name = $in_txt;
374
    }
375
376
    /**
377
     * @author hubert borderiou 28-11-11
378
     * @return string html text : the text to display ay the end of the test.
379
     */
380
    public function selectTextWhenFinished()
381
    {
382
        return $this->text_when_finished;
383
    }
384
385
    /**
386
     * @author hubert borderiou 28-11-11
387
     * @return string  html text : update the text to display ay the end of the test.
388
     */
389
    public function updateTextWhenFinished($in_txt)
390
    {
391
        $this->text_when_finished = $in_txt;
392
    }
393
394
    /**
395
     * return 1 or 2 if randomByCat
396
     * @author hubert borderiou
397
     * @return integer - quiz random by category
398
     */
399
    public function selectRandomByCat()
400
    {
401
        return $this->randomByCat;
402
    }
403
404
    /**
405
     * return 0 if no random by cat
406
     * return 1 if random by cat, categories shuffled
407
     * return 2 if random by cat, categories sorted by alphabetic order
408
     * @author hubert borderiou
409
     * @return integer - quiz random by category
410
     */
411
    public function isRandomByCat()
412
    {
413
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
414
        if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
415
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
416
        } else if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
417
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
418
        }
419
420
        return $res;
421
    }
422
423
    /**
424
     * return nothing
425
     * update randomByCat value for object
426
     * @param int $random
427
     *
428
     * @author hubert borderiou
429
     */
430
    public function updateRandomByCat($random)
431
    {
432
        if (in_array(
433
            $random,
434
            array(
435
                EXERCISE_CATEGORY_RANDOM_SHUFFLED,
436
                EXERCISE_CATEGORY_RANDOM_ORDERED,
437
                EXERCISE_CATEGORY_RANDOM_DISABLED,
438
            )
439
        )) {
440
            $this->randomByCat = $random;
441
        } else {
442
            $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
443
        }
444
    }
445
446
    /**
447
     * Tells if questions are selected randomly, and if so returns the draws
448
     *
449
     * @author Carlos Vargas
450
     * @return integer - results disabled exercise
451
     */
452
    public function selectResultsDisabled()
453
    {
454
        return $this->results_disabled;
455
    }
456
457
    /**
458
     * tells if questions are selected randomly, and if so returns the draws
459
     *
460
     * @author Olivier Brouckaert
461
     * @return integer - 0 if not random, otherwise the draws
462
     */
463
    public function isRandom()
464
    {
465
        if ($this->random > 0 || $this->random == -1) {
466
            return true;
467
        } else {
468
            return false;
469
        }
470
    }
471
472
    /**
473
     * returns random answers status.
474
     *
475
     * @author Juan Carlos Rana
476
     */
477
    public function selectRandomAnswers()
478
    {
479
        return $this->random_answers;
480
    }
481
482
    /**
483
     * Same as isRandom() but has a name applied to values different than 0 or 1
484
     */
485
    public function getShuffle()
486
    {
487
        return $this->random;
488
    }
489
490
    /**
491
     * returns the exercise status (1 = enabled ; 0 = disabled)
492
     *
493
     * @author Olivier Brouckaert
494
     * @return boolean - true if enabled, otherwise false
495
     */
496
    public function selectStatus()
497
    {
498
        return $this->active;
499
    }
500
501
    /**
502
     * If false the question list will be managed as always if true the question will be filtered
503
     * depending of the exercise settings (table c_quiz_rel_category)
504
     * @param bool $status active or inactive grouping
505
     **/
506
    public function setCategoriesGrouping($status)
507
    {
508
        $this->categories_grouping = (bool) $status;
509
    }
510
511
    /**
512
     * @return int
513
     */
514
    public function getHideQuestionTitle()
515
    {
516
        return $this->hideQuestionTitle;
517
    }
518
519
    /**
520
     * @param $value
521
     */
522
    public function setHideQuestionTitle($value)
523
    {
524
        $this->hideQuestionTitle = (int) $value;
525
    }
526
527
    /**
528
     * @return int
529
     */
530
    public function getScoreTypeModel()
531
    {
532
        return $this->scoreTypeModel;
533
    }
534
535
    /**
536
     * @param int $value
537
     */
538
    public function setScoreTypeModel($value)
539
    {
540
        $this->scoreTypeModel = (int) $value;
541
    }
542
543
    /**
544
     * @return int
545
     */
546
    public function getGlobalCategoryId()
547
    {
548
        return $this->globalCategoryId;
549
    }
550
551
    /**
552
     * @param int $value
553
     */
554
    public function setGlobalCategoryId($value)
555
    {
556
        if (is_array($value) && isset($value[0])) {
557
            $value = $value[0];
558
        }
559
        $this->globalCategoryId = (int) $value;
560
    }
561
562
    /**
563
     *
564
     * @param int $start
565
     * @param int $limit
566
     * @param int $sidx
567
     * @param string $sord
568
     * @param array $where_condition
569
     * @param array $extraFields
570
     */
571
    public function getQuestionListPagination($start, $limit, $sidx, $sord, $where_condition = array(), $extraFields = array())
572
    {
573
        if (!empty($this->id)) {
574
            $category_list = TestCategory::getListOfCategoriesNameForTest(
575
                $this->id,
576
                false
577
            );
578
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
579
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
580
581
            $sql = "SELECT q.iid
582
                    FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS  q
583
                        ON (e.question_id = q.id AND e.c_id = ".$this->course_id." )
584
					WHERE e.exercice_id	= '".Database::escape_string($this->id)."'
585
					";
586
587
            $orderCondition = "ORDER BY question_order";
588
589
            if (!empty($sidx) && !empty($sord)) {
590
                if ($sidx == 'question') {
591
                    if (in_array(strtolower($sord), array('desc', 'asc'))) {
592
                        $orderCondition = " ORDER BY q.$sidx $sord";
593
                    }
594
                }
595
            }
596
597
            $sql .= $orderCondition;
598
            $limitCondition = null;
599 View Code Duplication
            if (isset($start) && isset($limit)) {
600
                $start = intval($start);
601
                $limit = intval($limit);
602
                $limitCondition = " LIMIT $start, $limit";
603
            }
604
            $sql .= $limitCondition;
605
            $result = Database::query($sql);
606
            $questions = array();
607
            if (Database::num_rows($result)) {
608
                if (!empty($extraFields)) {
609
                    $extraFieldValue = new ExtraFieldValue('question');
610
                }
611
                while ($question = Database::fetch_array($result, 'ASSOC')) {
612
                    /** @var Question $objQuestionTmp */
613
                    $objQuestionTmp = Question::read($question['iid']);
614
                    $category_labels = TestCategory::return_category_labels(
615
                        $objQuestionTmp->category_list,
616
                        $category_list
617
                    );
618
619
                    if (empty($category_labels)) {
620
                        $category_labels = "-";
621
                    }
622
623
                    // Question type
624
                    list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html();
625
626
                    $question_media = null;
627
                    if (!empty($objQuestionTmp->parent_id)) {
628
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
629
                        $question_media  = Question::getMediaLabel($objQuestionMedia->question);
630
                    }
631
632
                    $questionType = Display::tag(
633
                        'div',
634
                        Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM).$question_media
635
                    );
636
637
                    $question = array(
638
                        'id' => $question['iid'],
639
                        'question' => $objQuestionTmp->selectTitle(),
640
                        'type' => $questionType,
641
                        'category' => Display::tag('div', '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'),
642
                        'score' => $objQuestionTmp->selectWeighting(),
643
                        'level' => $objQuestionTmp->level
644
                    );
645
                    if (!empty($extraFields)) {
646
                        foreach ($extraFields as $extraField) {
647
                            $value = $extraFieldValue->get_values_by_handler_and_field_id($question['id'], $extraField['id']);
648
                            $stringValue = null;
649
                            if ($value) {
650
                                $stringValue = $value['field_value'];
651
                            }
652
                            $question[$extraField['field_variable']] = $stringValue;
653
                        }
654
                    }
655
                    $questions[] = $question;
656
                }
657
            }
658
            return $questions;
659
        }
660
    }
661
662
    /**
663
     * Get question count per exercise from DB (any special treatment)
664
     * @return int
665
     */
666 View Code Duplication
    public function getQuestionCount()
667
    {
668
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
669
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
670
        $sql = "SELECT count(q.id) as count
671
                FROM $TBL_EXERCICE_QUESTION e 
672
                INNER JOIN $TBL_QUESTIONS q
673
                ON (e.question_id = q.id AND e.c_id = q.c_id)
674
                WHERE 
675
                    e.c_id = {$this->course_id} AND 
676
                    e.exercice_id	= ".Database::escape_string($this->id);
677
        $result = Database::query($sql);
678
679
        $count = 0;
680
        if (Database::num_rows($result)) {
681
            $row = Database::fetch_array($result);
682
            $count = $row['count'];
683
        }
684
685
        return $count;
686
    }
687
688
    /**
689
     * @return array
690
     */
691 View Code Duplication
    public function getQuestionOrderedListByName()
692
    {
693
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
694
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
695
696
        // Getting question list from the order (question list drag n drop interface ).
697
        $sql = "SELECT e.question_id
698
                FROM $TBL_EXERCICE_QUESTION e 
699
                INNER JOIN $TBL_QUESTIONS q
700
                ON (e.question_id= q.id AND e.c_id = q.c_id)
701
                WHERE 
702
                    e.c_id = {$this->course_id} AND 
703
                    e.exercice_id = '".Database::escape_string($this->id)."'
704
                ORDER BY q.question";
705
        $result = Database::query($sql);
706
        $list = array();
707
        if (Database::num_rows($result)) {
708
            $list = Database::store_result($result, 'ASSOC');
709
        }
710
        return $list;
711
    }
712
713
    /**
714
     * Gets the question list ordered by the question_order setting (drag and drop)
715
     * @return array
716
     */
717
    private function getQuestionOrderedList()
718
    {
719
        $questionList = array();
720
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
721
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
722
723
        // Getting question_order to verify that the question
724
        // list is correct and all question_order's were set
725
        $sql = "SELECT DISTINCT e.question_order
726
                FROM $TBL_EXERCICE_QUESTION e
727
                INNER JOIN $TBL_QUESTIONS q
728
                ON (e.question_id = q.id AND e.c_id = q.c_id)
729
                WHERE
730
                  e.c_id = {$this->course_id} AND
731
                  e.exercice_id	= ".Database::escape_string($this->id);
732
733
        $result = Database::query($sql);
734
        $count_question_orders = Database::num_rows($result);
735
736
        // Getting question list from the order (question list drag n drop interface ).
737
        $sql = "SELECT DISTINCT e.question_id, e.question_order
738
                FROM $TBL_EXERCICE_QUESTION e
739
                INNER JOIN $TBL_QUESTIONS q
740
                ON (e.question_id = q.id AND e.c_id = q.c_id)
741
                WHERE
742
                    e.c_id = {$this->course_id} AND
743
                    e.exercice_id	= '".Database::escape_string($this->id)."'
744
                ORDER BY question_order";
745
746
        $result = Database::query($sql);
747
748
        // Fills the array with the question ID for this exercise
749
        // the key of the array is the question position
750
        $temp_question_list = array();
751
752
        $counter = 1;
753
        while ($new_object = Database::fetch_object($result)) {
754
            // Correct order.
755
            $questionList[$new_object->question_order] = $new_object->question_id;
756
            // Just in case we save the order in other array
757
            $temp_question_list[$counter] = $new_object->question_id;
758
            $counter++;
759
        }
760
761
        if (!empty($temp_question_list)) {
762
            /* If both array don't match it means that question_order was not correctly set
763
               for all questions using the default mysql order */
764
            if (count($temp_question_list) != $count_question_orders) {
765
                $questionList = $temp_question_list;
766
            }
767
        }
768
769
        return $questionList;
770
    }
771
772
    /**
773
     * Select N values from the questions per category array
774
     *
775
     * @param array $categoriesAddedInExercise
776
     * @param array $question_list
777
     * @param array $questions_by_category per category
778
     * @param bool $flatResult
779
     * @param bool $randomizeQuestions
780
     *
781
     * @return array
782
     */
783
    private function pickQuestionsPerCategory(
784
        $categoriesAddedInExercise,
785
        $question_list,
786
        & $questions_by_category,
787
        $flatResult = true,
788
        $randomizeQuestions = false
789
    ) {
790
        $addAll = true;
791
        $categoryCountArray = array();
792
793
        // Getting how many questions will be selected per category.
794
        if (!empty($categoriesAddedInExercise)) {
795
            $addAll = false;
796
            // Parsing question according the category rel exercise settings
797
            foreach ($categoriesAddedInExercise as $category_info) {
798
                $category_id = $category_info['category_id'];
799
                if (isset($questions_by_category[$category_id])) {
800
                    // How many question will be picked from this category.
801
                    $count = $category_info['count_questions'];
802
                    // -1 means all questions
803
                    if ($count == -1) {
804
                        $categoryCountArray[$category_id] = 999;
805
                    } else {
806
                        $categoryCountArray[$category_id] = $count;
807
                    }
808
                }
809
            }
810
        }
811
812
        if (!empty($questions_by_category)) {
813
            $temp_question_list = array();
814
            foreach ($questions_by_category as $category_id => & $categoryQuestionList) {
815
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
816
                    if (isset($categoryCountArray[$category_id])) {
817
                        $numberOfQuestions = $categoryCountArray[$category_id];
818
                    } else {
819
                        $numberOfQuestions = 0;
820
                    }
821
                }
822
823
                if ($addAll) {
824
                    $numberOfQuestions = 999;
825
                }
826
827
                if (!empty($numberOfQuestions)) {
828
                    $elements = TestCategory::getNElementsFromArray(
829
                        $categoryQuestionList,
830
                        $numberOfQuestions,
831
                        $randomizeQuestions
832
                    );
833
834
                    if (!empty($elements)) {
835
                        $temp_question_list[$category_id] = $elements;
836
                        $categoryQuestionList = $elements;
837
                    }
838
                }
839
            }
840
841
            if (!empty($temp_question_list)) {
842
                if ($flatResult) {
843
                    $temp_question_list = array_flatten($temp_question_list);
844
                }
845
                $question_list = $temp_question_list;
846
            }
847
        }
848
849
        return $question_list;
850
    }
851
852
    /**
853
     * Selecting question list depending in the exercise-category
854
     * relationship (category table in exercise settings)
855
     *
856
     * @param array $question_list
857
     * @param int $questionSelectionType
858
     * @return array
859
     */
860
    public function getQuestionListWithCategoryListFilteredByCategorySettings($question_list, $questionSelectionType)
861
    {
862
        $result = array(
863
            'question_list' => array(),
864
            'category_with_questions_list' => array()
865
        );
866
867
        // Order/random categories
868
        $cat = new TestCategory();
869
870
        // Setting category order.
871
        switch ($questionSelectionType) {
872
            case EX_Q_SELECTION_ORDERED: // 1
873
            case EX_Q_SELECTION_RANDOM:  // 2
874
                // This options are not allowed here.
875
                break;
876 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
877
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
878
                    $this,
879
                    $this->course['real_id'],
880
                    'title ASC',
881
                    false,
882
                    true
883
                );
884
885
                $questions_by_category = TestCategory::getQuestionsByCat(
886
                    $this->id,
887
                    $question_list,
888
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...itle ASC', false, true) on line 877 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...
889
                );
890
891
                $question_list = $this->pickQuestionsPerCategory(
892
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...itle ASC', false, true) on line 877 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
893
                    $question_list,
894
                    $questions_by_category,
895
                    true,
896
                    false
897
                );
898
                break;
899
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
900 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
901
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
902
                    $this,
903
                    $this->course['real_id'],
904
                    null,
905
                    true,
906
                    true
907
                );
908
                $questions_by_category = TestCategory::getQuestionsByCat(
909
                    $this->id,
910
                    $question_list,
911
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 901 can also be of type boolean; however, TestCategory::getQuestionsByCat() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
912
                );
913
                $question_list = $this->pickQuestionsPerCategory(
914
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 901 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
915
                    $question_list,
916
                    $questions_by_category,
917
                    true,
918
                    false
919
                );
920
                break;
921 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
922
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
923
                    $this,
924
                    $this->course['real_id'],
925
                    'title DESC',
926
                    false,
927
                    true
928
                );
929
                $questions_by_category = TestCategory::getQuestionsByCat(
930
                    $this->id,
931
                    $question_list,
932
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 922 can also be of type boolean; however, TestCategory::getQuestionsByCat() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
933
                );
934
                $question_list = $this->pickQuestionsPerCategory(
935
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...tle DESC', false, true) on line 922 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
936
                    $question_list,
937
                    $questions_by_category,
938
                    true,
939
                    true
940
                );
941
                break;
942
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
943 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
944
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
945
                    $this,
946
                    $this->course['real_id'],
947
                    null,
948
                    true,
949
                    true
950
                );
951
952
                $questions_by_category = TestCategory::getQuestionsByCat(
953
                    $this->id,
954
                    $question_list,
955
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 944 can also be of type boolean; however, TestCategory::getQuestionsByCat() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
956
                );
957
958
                $question_list = $this->pickQuestionsPerCategory(
959
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis...id'], null, true, true) on line 944 can also be of type boolean; however, Exercise::pickQuestionsPerCategory() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
960
                    $question_list,
961
                    $questions_by_category,
962
                    true,
963
                    true
964
                );
965
                break;
966
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
967
                break;
968
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
969
                break;
970 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
971
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
972
                    $this,
973
                    $this->course['real_id'],
974
                    'root ASC, lft ASC',
975
                    false,
976
                    true
977
                );
978
                $questions_by_category = TestCategory::getQuestionsByCat(
979
                    $this->id,
980
                    $question_list,
981
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 971 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...
982
                );
983
                $question_list = $this->pickQuestionsPerCategory(
984
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 971 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...
985
                    $question_list,
986
                    $questions_by_category,
987
                    true,
988
                    false
989
                );
990
                break;
991 View Code Duplication
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
992
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
993
                    $this,
994
                    $this->course['real_id'],
995
                    'root, lft ASC',
996
                    false,
997
                    true
998
                );
999
                $questions_by_category = TestCategory::getQuestionsByCat(
1000
                    $this->id,
1001
                    $question_list,
1002
                    $categoriesAddedInExercise
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 992 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...
1003
                );
1004
                $question_list = $this->pickQuestionsPerCategory(
1005
                    $categoriesAddedInExercise,
0 ignored issues
show
Bug introduced by
It seems like $categoriesAddedInExercise defined by $cat->getCategoryExercis... lft ASC', false, true) on line 992 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...
1006
                    $question_list,
1007
                    $questions_by_category,
1008
                    true,
1009
                    true
1010
                );
1011
                break;
1012
        }
1013
1014
        $result['question_list'] = isset($question_list) ? $question_list : array();
1015
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : array();
1016
1017
        // Adding category info in the category list with question list:
1018
        if (!empty($questions_by_category)) {
1019
            $newCategoryList = array();
1020
            foreach ($questions_by_category as $categoryId => $questionList) {
1021
                $cat = new TestCategory();
1022
                $cat = $cat->getCategory($categoryId);
1023
1024
                $cat = (array)$cat;
1025
                $cat['iid'] = $cat['id'];
1026
                $categoryParentInfo = null;
1027
                // Parent is not set no loop here
1028
                if (!empty($cat['parent_id'])) {
1029
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
1030
                        $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...
1031
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
1032
                    } else {
1033
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
1034
                    }
1035
                    $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...
1036
                    $index = 0;
1037
                    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...
1038
                        //$index = 1;
1039
                    }
1040
                    /** @var \Chamilo\Entity\CQuizCategory $categoryParent*/
1041
1042
                    foreach ($path as $categoryParent) {
1043
                        $visibility = $categoryParent->getVisibility();
1044
1045
                        if ($visibility == 0) {
1046
                            $categoryParentId = $categoryId;
1047
                            $categoryTitle = $cat['title'];
1048
                            if (count($path) > 1) {
1049
                                continue;
1050
                            }
1051
                        } else {
1052
                            $categoryParentId = $categoryParent->getIid();
1053
                            $categoryTitle = $categoryParent->getTitle();
1054
                        }
1055
1056
                        $categoryParentInfo['id'] = $categoryParentId;
1057
                        $categoryParentInfo['iid'] = $categoryParentId;
1058
                        $categoryParentInfo['parent_path'] = null;
1059
                        $categoryParentInfo['title'] = $categoryTitle;
1060
                        $categoryParentInfo['name'] = $categoryTitle;
1061
                        $categoryParentInfo['parent_id'] = null;
1062
                        break;
1063
                    }
1064
                }
1065
                $cat['parent_info'] = $categoryParentInfo;
1066
                $newCategoryList[$categoryId] = array(
1067
                    'category' => $cat,
1068
                    'question_list' => $questionList
1069
                );
1070
            }
1071
1072
            $result['category_with_questions_list'] = $newCategoryList;
1073
        }
1074
1075
        return $result;
1076
    }
1077
1078
    /**
1079
     * returns the array with the question ID list
1080
     * @param   bool    $from_db    Whether the results should be fetched in the database or just from memory
1081
     * @param   bool    $adminView  Whether we should return all questions (admin view) or just a list limited by the max number of random questions
1082
     * @author Olivier Brouckaert
1083
     * @return array - question ID list
1084
     */
1085
    public function selectQuestionList($from_db = false, $adminView = false)
1086
    {
1087
        if ($from_db && !empty($this->id)) {
1088
            $nbQuestions = $this->getQuestionCount();
1089
            $questionSelectionType = $this->getQuestionSelectionType();
1090
1091
            switch ($questionSelectionType) {
1092
                case EX_Q_SELECTION_ORDERED:
1093
                    $questionList = $this->getQuestionOrderedList();
1094
                    break;
1095
                case EX_Q_SELECTION_RANDOM:
1096
                    // Not a random exercise, or if there are not at least 2 questions
1097
                    if ($this->random == 0 || $nbQuestions < 2) {
1098
                        $questionList = $this->getQuestionOrderedList();
1099
                    } else {
1100
                        $questionList = $this->selectRandomList($adminView);
1101
                    }
1102
                    break;
1103
                default:
1104
                    $questionList = $this->getQuestionOrderedList();
1105
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1106
                        $questionList,
1107
                        $questionSelectionType
1108
                    );
1109
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1110
                    $questionList = $result['question_list'];
1111
                    break;
1112
            }
1113
1114
            return $questionList;
1115
        }
1116
1117
        return $this->questionList;
1118
    }
1119
1120
    /**
1121
     * returns the number of questions in this exercise
1122
     *
1123
     * @author Olivier Brouckaert
1124
     * @return integer - number of questions
1125
     */
1126
    public function selectNbrQuestions()
1127
    {
1128
        return sizeof($this->questionList);
1129
    }
1130
1131
    /**
1132
     * @return int
1133
     */
1134
    public function selectPropagateNeg()
1135
    {
1136
        return $this->propagate_neg;
1137
    }
1138
1139
    /**
1140
     * @return int
1141
     */
1142
    public function selectSaveCorrectAnswers()
1143
    {
1144
        return $this->saveCorrectAnswers;
1145
    }
1146
1147
    /**
1148
     * Selects questions randomly in the question list
1149
     *
1150
     * @author Olivier Brouckaert
1151
     * @author Hubert Borderiou 15 nov 2011
1152
     * @param    bool    $adminView  Whether we should return all questions (admin view) or just a list limited by the max number of random questions
1153
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1154
     *					 without randomizing, otherwise, returns the list with questions selected randomly
1155
     */
1156
    public function selectRandomList($adminView = false)
1157
    {
1158
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1159
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1160
1161
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1162
1163
        $randomLimit = "ORDER BY RAND() LIMIT $random";
1164
        // Random all questions so no limit
1165
        if ($random == -1 or $adminView === true) {
1166
            // If viewing it as admin for edition, don't show it randomly, use title + id
1167
            $randomLimit = 'ORDER BY e.question_order';
1168
        }
1169
1170
        $sql = "SELECT e.question_id
1171
                FROM $TBL_EXERCISE_QUESTION e 
1172
                INNER JOIN $TBL_QUESTIONS q
1173
                ON (e.question_id= q.id AND e.c_id = q.c_id)
1174
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
1175
                $randomLimit ";
1176
        $result = Database::query($sql);
1177
        $questionList = array();
1178
        while ($row = Database::fetch_object($result)) {
1179
            $questionList[] = $row->question_id;
1180
        }
1181
1182
        return $questionList;
1183
    }
1184
1185
    /**
1186
     * returns 'true' if the question ID is in the question list
1187
     *
1188
     * @author Olivier Brouckaert
1189
     * @param integer $questionId - question ID
1190
     * @return boolean - true if in the list, otherwise false
1191
     */
1192
    public function isInList($questionId)
1193
    {
1194
        if (is_array($this->questionList)) {
1195
            return in_array($questionId, $this->questionList);
1196
        } else {
1197
            return false;
1198
        }
1199
    }
1200
1201
    /**
1202
     * changes the exercise title
1203
     *
1204
     * @author Olivier Brouckaert
1205
     * @param string $title - exercise title
1206
     */
1207
    public function updateTitle($title)
1208
    {
1209
        $this->exercise=$title;
1210
    }
1211
1212
    /**
1213
     * changes the exercise max attempts
1214
     *
1215
     * @param int $attempts - exercise max attempts
1216
     */
1217
    public function updateAttempts($attempts)
1218
    {
1219
        $this->attempts=$attempts;
1220
    }
1221
1222
    /**
1223
     * changes the exercise feedback type
1224
     *
1225
     * @param int $feedback_type
1226
     */
1227
    public function updateFeedbackType($feedback_type)
1228
    {
1229
        $this->feedback_type=$feedback_type;
1230
    }
1231
1232
    /**
1233
     * changes the exercise description
1234
     *
1235
     * @author Olivier Brouckaert
1236
     * @param string $description - exercise description
1237
     */
1238
    public function updateDescription($description)
1239
    {
1240
        $this->description=$description;
1241
    }
1242
1243
    /**
1244
     * changes the exercise expired_time
1245
     *
1246
     * @author Isaac flores
1247
     * @param int $expired_time The expired time of the quiz
1248
     */
1249
    public function updateExpiredTime($expired_time)
1250
    {
1251
        $this->expired_time = $expired_time;
1252
    }
1253
1254
    /**
1255
     * @param $value
1256
     */
1257
    public function updatePropagateNegative($value)
1258
    {
1259
        $this->propagate_neg = $value;
1260
    }
1261
1262
    /**
1263
     * @param $value int
1264
     */
1265
    public function updateSaveCorrectAnswers($value)
1266
    {
1267
        $this->saveCorrectAnswers = $value;
1268
    }
1269
1270
    /**
1271
     * @param $value
1272
     */
1273
    public function updateReviewAnswers($value)
1274
    {
1275
        $this->review_answers = isset($value) && $value ? true : false;
1276
    }
1277
1278
    /**
1279
     * @param $value
1280
     */
1281
    public function updatePassPercentage($value)
1282
    {
1283
        $this->pass_percentage = $value;
1284
    }
1285
1286
    /**
1287
     * @param string $text
1288
     */
1289
    public function updateEmailNotificationTemplate($text)
1290
    {
1291
        $this->emailNotificationTemplate = $text;
1292
    }
1293
1294
    /**
1295
     * @param string $text
1296
     */
1297
    public function updateEmailNotificationTemplateToUser($text)
1298
    {
1299
        $this->emailNotificationTemplateToUser = $text;
1300
    }
1301
1302
    /**
1303
     * @param string $value
1304
     */
1305
    public function setNotifyUserByEmail($value)
1306
    {
1307
        $this->notifyUserByEmail = $value;
1308
    }
1309
1310
    /**
1311
     * @param int $value
1312
     */
1313
    public function updateEndButton($value)
1314
    {
1315
        $this->endButton = (int) $value;
1316
    }
1317
1318
    /**
1319
     * @param string $value
1320
     */
1321
    public function setOnSuccessMessage($value)
1322
    {
1323
        $this->onSuccessMessage = $value;
1324
    }
1325
1326
    /**
1327
     * @param string $value
1328
     */
1329
    public function setOnFailedMessage($value)
1330
    {
1331
        $this->onFailedMessage = $value;
1332
    }
1333
1334
    /**
1335
     * @param $value
1336
     */
1337
    public function setModelType($value)
1338
    {
1339
        $this->modelType = intval($value);
1340
    }
1341
1342
    /**
1343
     * @param int $value
1344
     */
1345
    public function setQuestionSelectionType($value)
1346
    {
1347
        $this->questionSelectionType = intval($value);
1348
    }
1349
1350
    /**
1351
     * @return int
1352
     */
1353
    public function getQuestionSelectionType()
1354
    {
1355
        return $this->questionSelectionType;
1356
    }
1357
1358
    /**
1359
     * @param array $categories
1360
     */
1361
    public function updateCategories($categories)
1362
    {
1363
        if (!empty($categories)) {
1364
            $categories = array_map('intval', $categories);
1365
            $this->categories = $categories;
1366
        }
1367
    }
1368
1369
    /**
1370
     * changes the exercise sound file
1371
     *
1372
     * @author Olivier Brouckaert
1373
     * @param string $sound - exercise sound file
1374
     * @param string $delete - ask to delete the file
1375
     */
1376
    public function updateSound($sound,$delete)
1377
    {
1378
        global $audioPath, $documentPath;
1379
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1380
1381
        if ($sound['size'] && (strstr($sound['type'],'audio') || strstr($sound['type'],'video'))) {
1382
            $this->sound = $sound['name'];
1383
1384
            if (@move_uploaded_file($sound['tmp_name'],$audioPath.'/'.$this->sound)) {
1385
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1386
                        WHERE 
1387
                            c_id = ".$this->course_id." AND 
1388
                            path='".str_replace($documentPath,'',$audioPath).'/'.$this->sound."'";
1389
                $result = Database::query($sql);
1390
1391
                if (!Database::num_rows($result)) {
1392
                    $id = add_document(
1393
                        $this->course,
1394
                        str_replace($documentPath,'',$audioPath).'/'.$this->sound,
1395
                        'file',
1396
                        $sound['size'],
1397
                        $sound['name']
1398
                    );
1399
                    api_item_property_update(
1400
                        $this->course,
1401
                        TOOL_DOCUMENT,
1402
                        $id,
1403
                        'DocumentAdded',
1404
                        api_get_user_id()
1405
                    );
1406
                    item_property_update_on_folder(
1407
                        $this->course,
1408
                        str_replace($documentPath, '', $audioPath),
1409
                        api_get_user_id()
1410
                    );
1411
                }
1412
            }
1413
        } elseif ($delete && is_file($audioPath.'/'.$this->sound)) {
1414
            $this->sound = '';
1415
        }
1416
    }
1417
1418
    /**
1419
     * changes the exercise type
1420
     *
1421
     * @author Olivier Brouckaert
1422
     * @param integer $type - exercise type
1423
     */
1424
    public function updateType($type)
1425
    {
1426
        $this->type = $type;
1427
    }
1428
1429
    /**
1430
     * sets to 0 if questions are not selected randomly
1431
     * if questions are selected randomly, sets the draws
1432
     *
1433
     * @author Olivier Brouckaert
1434
     * @param integer $random - 0 if not random, otherwise the draws
1435
     */
1436
    public function setRandom($random)
1437
    {
1438
        /*if ($random == 'all') {
1439
            $random = $this->selectNbrQuestions();
1440
        }*/
1441
        $this->random = $random;
1442
    }
1443
1444
    /**
1445
     * sets to 0 if answers are not selected randomly
1446
     * if answers are selected randomly
1447
     * @author Juan Carlos Rana
1448
     * @param integer $random_answers - random answers
1449
     */
1450
    public function updateRandomAnswers($random_answers)
1451
    {
1452
        $this->random_answers = $random_answers;
1453
    }
1454
1455
    /**
1456
     * enables the exercise
1457
     *
1458
     * @author Olivier Brouckaert
1459
     */
1460
    public function enable()
1461
    {
1462
        $this->active=1;
1463
    }
1464
1465
    /**
1466
     * disables the exercise
1467
     *
1468
     * @author Olivier Brouckaert
1469
     */
1470
    public function disable()
1471
    {
1472
        $this->active=0;
1473
    }
1474
1475
    /**
1476
     * Set disable results
1477
     */
1478
    public function disable_results()
1479
    {
1480
        $this->results_disabled = true;
1481
    }
1482
1483
    /**
1484
     * Enable results
1485
     */
1486
    public function enable_results()
1487
    {
1488
        $this->results_disabled = false;
1489
    }
1490
1491
    /**
1492
     * @param int $results_disabled
1493
     */
1494
    public function updateResultsDisabled($results_disabled)
1495
    {
1496
        $this->results_disabled = intval($results_disabled);
1497
    }
1498
1499
    /**
1500
     * updates the exercise in the data base
1501
     *
1502
     * @author Olivier Brouckaert
1503
     */
1504
    public function save($type_e = '')
1505
    {
1506
        $_course = $this->course;
1507
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1508
1509
        $id = $this->id;
1510
        $exercise = $this->exercise;
1511
        $description = $this->description;
1512
        $sound = $this->sound;
1513
        $type = $this->type;
1514
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1515
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1516
        $random = $this->random;
1517
        $random_answers = $this->random_answers;
1518
        $active = $this->active;
1519
        $propagate_neg = (int) $this->propagate_neg;
1520
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0;
1521
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1522
        $randomByCat = intval($this->randomByCat);
1523
        $text_when_finished = $this->text_when_finished;
1524
        $display_category_name = intval($this->display_category_name);
1525
        $pass_percentage = intval($this->pass_percentage);
1526
        $session_id = $this->sessionId;
1527
1528
        //If direct we do not show results
1529
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1530
            $results_disabled = 0;
1531
        } else {
1532
            $results_disabled = intval($this->results_disabled);
1533
        }
1534
1535
        $expired_time = intval($this->expired_time);
1536
1537
        // Exercise already exists
1538
        if ($id) {
1539
            // we prepare date in the database using the api_get_utc_datetime() function
1540
            if (!empty($this->start_time)) {
1541
                $start_time = $this->start_time;
1542
            } else {
1543
                $start_time = null;
1544
            }
1545
1546
            if (!empty($this->end_time)) {
1547
                $end_time = $this->end_time;
1548
            } else {
1549
                $end_time = null;
1550
            }
1551
1552
            $params = [
1553
                'title' => $exercise,
1554
                'description' => $description,
1555
            ];
1556
1557
            $paramsExtra = [];
1558
            if ($type_e != 'simple') {
1559
                $paramsExtra = [
1560
                    'sound' => $sound,
1561
                    'type' => $type,
1562
                    'random' => $random,
1563
                    'random_answers' => $random_answers,
1564
                    'active' => $active,
1565
                    'feedback_type' => $feedback_type,
1566
                    'start_time' => $start_time,
1567
                    'end_time' => $end_time,
1568
                    'max_attempt' => $attempts,
1569
                    'expired_time' => $expired_time,
1570
                    'propagate_neg' => $propagate_neg,
1571
                    'save_correct_answers' => $saveCorrectAnswers,
1572
                    'review_answers' => $review_answers,
1573
                    'random_by_category' => $randomByCat,
1574
                    'text_when_finished' => $text_when_finished,
1575
                    'display_category_name' => $display_category_name,
1576
                    'pass_percentage' => $pass_percentage,
1577
                    'results_disabled' => $results_disabled,
1578
                    'question_selection_type' => $this->getQuestionSelectionType(),
1579
                    'hide_question_title' => $this->getHideQuestionTitle()
1580
                ];
1581
            }
1582
1583
            $params = array_merge($params, $paramsExtra);
1584
1585
            Database::update(
1586
                $TBL_EXERCISES,
1587
                $params,
1588
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1589
            );
1590
1591
            // update into the item_property table
1592
            api_item_property_update(
1593
                $_course,
1594
                TOOL_QUIZ,
1595
                $id,
1596
                'QuizUpdated',
1597
                api_get_user_id()
1598
            );
1599
1600
            if (api_get_setting('search_enabled')=='true') {
1601
                $this->search_engine_edit();
1602
            }
1603
        } else {
1604
            // Creates a new exercise
1605
1606
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1607
            // for date because, bellow, we call function api_set_default_visibility()
1608
            // In this function, api_set_default_visibility,
1609
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1610
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1611
            if (!empty($this->start_time)) {
1612
                $start_time = $this->start_time;
1613
            } else {
1614
                $start_time = null;
1615
            }
1616
1617
            if (!empty($this->end_time)) {
1618
                $end_time = $this->end_time;
1619
            } else {
1620
                $end_time = null;
1621
            }
1622
1623
            $params = [
1624
                'c_id' => $this->course_id,
1625
                'start_time' => $start_time,
1626
                'end_time' => $end_time,
1627
                'title' => $exercise,
1628
                'description' => $description,
1629
                'sound' => $sound,
1630
                'type' => $type,
1631
                'random' => $random,
1632
                'random_answers' => $random_answers,
1633
                'active' => $active,
1634
                'results_disabled' => $results_disabled,
1635
                'max_attempt' => $attempts,
1636
                'feedback_type' => $feedback_type,
1637
                'expired_time' => $expired_time,
1638
                'session_id' => $session_id,
1639
                'review_answers' => $review_answers,
1640
                'random_by_category' => $randomByCat,
1641
                'text_when_finished' => $text_when_finished,
1642
                'display_category_name' => $display_category_name,
1643
                'pass_percentage' => $pass_percentage,
1644
                'save_correct_answers' => (int) $saveCorrectAnswers,
1645
                'propagate_neg' => $propagate_neg,
1646
                'hide_question_title' => $this->getHideQuestionTitle()
1647
            ];
1648
1649
            $this->id = Database::insert($TBL_EXERCISES, $params);
1650
1651
            if ($this->id) {
1652
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1653
                Database::query($sql);
1654
1655
                $sql = "UPDATE $TBL_EXERCISES
1656
                        SET question_selection_type= ".intval($this->getQuestionSelectionType())."
1657
                        WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1658
                Database::query($sql);
1659
1660
                // insert into the item_property table
1661
                api_item_property_update(
1662
                    $this->course,
1663
                    TOOL_QUIZ,
1664
                    $this->id,
1665
                    'QuizAdded',
1666
                    api_get_user_id()
1667
                );
1668
1669
                // This function save the quiz again, carefull about start_time
1670
                // and end_time if you remove this line (see above)
1671
                api_set_default_visibility(
1672
                    $this->id,
1673
                    TOOL_QUIZ,
1674
                    null,
1675
                    $this->course
1676
                );
1677
1678
                if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1679
                    $this->search_engine_save();
1680
                }
1681
            }
1682
        }
1683
1684
        $this->save_categories_in_exercise($this->categories);
1685
1686
        // Updates the question position
1687
        $this->update_question_positions();
1688
    }
1689
1690
    /**
1691
     * Updates question position
1692
     */
1693
    public function update_question_positions()
1694
    {
1695
        $quiz_question_table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1696
        //Fixes #3483 when updating order
1697
        $question_list = $this->selectQuestionList(true);
1698
        if (!empty($question_list)) {
1699
            foreach ($question_list as $position => $questionId) {
1700
                $sql = "UPDATE $quiz_question_table SET
1701
                        question_order ='".intval($position)."'
1702
                        WHERE
1703
                            c_id = ".$this->course_id." AND
1704
                            question_id = ".intval($questionId)." AND
1705
                            exercice_id=".intval($this->id);
1706
                Database::query($sql);
1707
            }
1708
        }
1709
    }
1710
1711
    /**
1712
     * Adds a question into the question list
1713
     *
1714
     * @author Olivier Brouckaert
1715
     * @param integer $questionId - question ID
1716
     * @return boolean - true if the question has been added, otherwise false
1717
     */
1718
    public function addToList($questionId)
1719
    {
1720
        // checks if the question ID is not in the list
1721
        if (!$this->isInList($questionId)) {
1722
            // selects the max position
1723
            if (!$this->selectNbrQuestions()) {
1724
                $pos = 1;
1725
            } else {
1726
                if (is_array($this->questionList)) {
1727
                    $pos = max(array_keys($this->questionList)) + 1;
1728
                }
1729
            }
1730
            $this->questionList[$pos] = $questionId;
1731
1732
            return true;
1733
        }
1734
1735
        return false;
1736
    }
1737
1738
    /**
1739
     * removes a question from the question list
1740
     *
1741
     * @author Olivier Brouckaert
1742
     * @param integer $questionId - question ID
1743
     * @return boolean - true if the question has been removed, otherwise false
1744
     */
1745
    public function removeFromList($questionId)
1746
    {
1747
        // searches the position of the question ID in the list
1748
        $pos = array_search($questionId, $this->questionList);
1749
        // question not found
1750
        if ($pos === false) {
1751
            return false;
1752
        } else {
1753
            // dont reduce the number of random question if we use random by category option, or if
1754
            // random all questions
1755
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1756
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1757
                    $this->random -= 1;
1758
                    $this->save();
1759
                }
1760
            }
1761
            // deletes the position from the array containing the wanted question ID
1762
            unset($this->questionList[$pos]);
1763
1764
            return true;
1765
        }
1766
    }
1767
1768
    /**
1769
     * deletes the exercise from the database
1770
     * Notice : leaves the question in the data base
1771
     *
1772
     * @author Olivier Brouckaert
1773
     */
1774
    public function delete()
1775
    {
1776
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1777
        $sql = "UPDATE $TBL_EXERCISES SET active='-1'
1778
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
1779
        Database::query($sql);
1780
1781
        api_item_property_update(
1782
            $this->course,
1783
            TOOL_QUIZ,
1784
            $this->id,
1785
            'QuizDeleted',
1786
            api_get_user_id()
1787
        );
1788
        api_item_property_update(
1789
            $this->course,
1790
            TOOL_QUIZ,
1791
            $this->id,
1792
            'delete',
1793
            api_get_user_id()
1794
        );
1795
1796
        if (api_get_setting('search_enabled')=='true' &&
1797
            extension_loaded('xapian')
1798
        ) {
1799
            $this->search_engine_delete();
1800
        }
1801
    }
1802
1803
    /**
1804
     * Creates the form to create / edit an exercise
1805
     * @param FormValidator $form
1806
     */
1807
    public function createForm($form, $type = 'full')
1808
    {
1809
        if (empty($type)) {
1810
            $type = 'full';
1811
        }
1812
1813
        // form title
1814
        if (!empty($_GET['exerciseId'])) {
1815
            $form_title = get_lang('ModifyExercise');
1816
        } else {
1817
            $form_title = get_lang('NewEx');
1818
        }
1819
1820
        $form->addElement('header', $form_title);
1821
1822
        // Title.
1823
        if (api_get_configuration_value('save_titles_like_html')) {
1824
            $form->addHtmlEditor(
1825
                'exerciseTitle',
1826
                get_lang('ExerciseName'),
1827
                false,
1828
                false,
1829
                ['ToolbarSet' => 'Minimal']
1830
            );
1831
        } else {
1832
            $form->addElement(
1833
                'text',
1834
                'exerciseTitle',
1835
                get_lang('ExerciseName'),
1836
                array('id' => 'exercise_title')
1837
            );
1838
        }
1839
1840
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1841
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1842
1843
        $editor_config = array(
1844
            'ToolbarSet' => 'TestQuestionDescription',
1845
            'Width' => '100%',
1846
            'Height' => '150',
1847
        );
1848
        if (is_array($type)) {
1849
            $editor_config = array_merge($editor_config, $type);
1850
        }
1851
1852
        $form->addHtmlEditor(
1853
            'exerciseDescription',
1854
            get_lang('ExerciseDescription'),
1855
            false,
1856
            false,
1857
            $editor_config
1858
        );
1859
1860
        if ($type == 'full') {
1861
            //Can't modify a DirectFeedback question
1862
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1863
                // feedback type
1864
                $radios_feedback = array();
1865
                $radios_feedback[] = $form->createElement(
1866
                    'radio',
1867
                    'exerciseFeedbackType',
1868
                    null,
1869
                    get_lang('ExerciseAtTheEndOfTheTest'),
1870
                    '0',
1871
                    array(
1872
                        'id' => 'exerciseType_0',
1873
                        'onclick' => 'check_feedback()',
1874
                    )
1875
                );
1876
1877 View Code Duplication
                if (api_get_setting('enable_quiz_scenario') == 'true') {
1878
                    //Can't convert a question from one feedback to another if there is more than 1 question already added
1879
                    if ($this->selectNbrQuestions() == 0) {
1880
                        $radios_feedback[] = $form->createElement(
1881
                            'radio',
1882
                            'exerciseFeedbackType',
1883
                            null,
1884
                            get_lang('DirectFeedback'),
1885
                            '1',
1886
                            array(
1887
                                'id' => 'exerciseType_1',
1888
                                'onclick' => 'check_direct_feedback()',
1889
                            )
1890
                        );
1891
                    }
1892
                }
1893
1894
                $radios_feedback[] = $form->createElement(
1895
                    'radio',
1896
                    'exerciseFeedbackType',
1897
                    null,
1898
                    get_lang('NoFeedback'),
1899
                    '2',
1900
                    array('id' => 'exerciseType_2')
1901
                );
1902
                $form->addGroup(
1903
                    $radios_feedback,
1904
                    null,
1905
                    array(
1906
                        get_lang('FeedbackType'),
1907
                        get_lang('FeedbackDisplayOptions'),
1908
                    )
1909
                );
1910
1911
                // Type of results display on the final page
1912
                $radios_results_disabled = array();
1913
                $radios_results_disabled[] = $form->createElement(
1914
                    'radio',
1915
                    'results_disabled',
1916
                    null,
1917
                    get_lang('ShowScoreAndRightAnswer'),
1918
                    '0',
1919
                    array('id' => 'result_disabled_0')
1920
                );
1921
                $radios_results_disabled[] = $form->createElement(
1922
                    'radio',
1923
                    'results_disabled',
1924
                    null,
1925
                    get_lang('DoNotShowScoreNorRightAnswer'),
1926
                    '1',
1927
                    array('id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()')
1928
                );
1929
                $radios_results_disabled[] = $form->createElement(
1930
                    'radio',
1931
                    'results_disabled',
1932
                    null,
1933
                    get_lang('OnlyShowScore'),
1934
                    '2',
1935
                    array('id' => 'result_disabled_2')
1936
                );
1937
1938
                $radios_results_disabled[] = $form->createElement(
1939
                    'radio',
1940
                    'results_disabled',
1941
                    null,
1942
                    get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
1943
                    '4',
1944
                    array('id' => 'result_disabled_4')
1945
                );
1946
1947
                $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'));
1948
1949
                // Type of questions disposition on page
1950
                $radios = array();
1951
1952
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1953
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1954
1955
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
1956
1957
            } else {
1958
                // if is Directfeedback but has not questions we can allow to modify the question type
1959
                if ($this->selectNbrQuestions() == 0) {
1960
                    // feedback type
1961
                    $radios_feedback = array();
1962
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('ExerciseAtTheEndOfTheTest'),'0',array('id' =>'exerciseType_0', 'onclick' => 'check_feedback()'));
1963
1964 View Code Duplication
                    if (api_get_setting('enable_quiz_scenario') == 'true') {
1965
                        $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('DirectFeedback'), '1', array('id' =>'exerciseType_1' , 'onclick' => 'check_direct_feedback()'));
1966
                    }
1967
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('NoFeedback'),'2',array('id' =>'exerciseType_2'));
1968
                    $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
1969
1970
                    //$form->addElement('select', 'exerciseFeedbackType',get_lang('FeedbackType'),$feedback_option,'onchange="javascript:feedbackselection()"');
1971
                    $radios_results_disabled = array();
1972
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1973
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
1974
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
1975
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
1976
1977
                    // Type of questions disposition on page
1978
                    $radios = array();
1979
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1');
1980
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2');
1981
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
1982
                } else {
1983
                    //Show options freeze
1984
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
1985
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
1986
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
1987
                    $result_disable_group = $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'));
1988
                    $result_disable_group->freeze();
1989
1990
                    //we force the options to the DirectFeedback exercisetype
1991
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
1992
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
1993
1994
                    // Type of questions disposition on page
1995
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1996
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1997
1998
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
1999
                    $type_group->freeze();
2000
                }
2001
            }
2002
2003
            $option = array(
2004
                EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2005
                //  Defined by user
2006
                EX_Q_SELECTION_RANDOM => get_lang('Random'),
2007
                // 1-10, All
2008
                'per_categories' => '--------'.get_lang('UsingCategories').'----------',
2009
                // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2010
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang('OrderedCategoriesAlphabeticallyWithQuestionsOrdered'),
2011
                // A 123 B 456 C 78 (0, 1, all)
2012
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang('RandomCategoriesWithQuestionsOrdered'),
2013
                // C 78 B 456 A 123
2014
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang('OrderedCategoriesAlphabeticallyWithRandomQuestions'),
2015
                // A 321 B 654 C 87
2016
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang('RandomCategoriesWithRandomQuestions'),
2017
                // C 87 B 654 A 321
2018
2019
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2020
                /*    B 456 C 78 A 123
2021
                        456 78 123
2022
                        123 456 78
2023
                */
2024
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2025
                /*
2026
                    A 123 B 456 C 78
2027
                    B 456 C 78 A 123
2028
                    B 654 C 87 A 321
2029
                    654 87 321
2030
                    165 842 73
2031
                */
2032
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2033
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2034
            );
2035
2036
            $form->addElement(
2037
                'select',
2038
                'question_selection_type',
2039
                array(get_lang('QuestionSelection')),
2040
                $option,
2041
                array(
2042
                    'id' => 'questionSelection',
2043
                    'onchange' => 'checkQuestionSelection()'
2044
                )
2045
            );
2046
2047
            $displayMatrix = 'none';
2048
            $displayRandom = 'none';
2049
            $selectionType = $this->getQuestionSelectionType();
2050
            switch ($selectionType) {
2051
                case EX_Q_SELECTION_RANDOM:
2052
                    $displayRandom = 'block';
2053
                    break;
2054
                case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2055
                    $displayMatrix = 'block';
2056
                    break;
2057
            }
2058
2059
            $form->addElement(
2060
                'html',
2061
                '<div id="hidden_random" style="display:'.$displayRandom.'">'
2062
            );
2063
            // Number of random question.
2064
            $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
2065
            $option = range(0, $max);
2066
            $option[0] = get_lang('No');
2067
            $option[-1] = get_lang('AllQuestionsShort');
2068
            $form->addElement(
2069
                'select',
2070
                'randomQuestions',
2071
                array(
2072
                    get_lang('RandomQuestions'),
2073
                    get_lang('RandomQuestionsHelp')
2074
                ),
2075
                $option,
2076
                array('id' => 'randomQuestions')
2077
            );
2078
            $form->addElement('html', '</div>');
2079
2080
            $form->addElement(
2081
                'html',
2082
                '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2083
            );
2084
2085
            // Category selection.
2086
            $cat = new TestCategory();
2087
            $cat_form = $cat->returnCategoryForm($this);
2088
            if (empty($cat_form)) {
2089
                $cat_form = '<span class="label label-warning">' . get_lang('NoCategoriesDefined') . '</span>';
2090
            }
2091
            $form->addElement('label', null, $cat_form);
2092
            $form->addElement('html', '</div>');
2093
2094
            // Category name.
2095
            $radio_display_cat_name = array(
2096
                $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2097
                $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0')
2098
            );
2099
            $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'));
2100
2101
            // Random answers.
2102
            $radios_random_answers = array(
2103
                $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2104
                $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0')
2105
            );
2106
            $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'));
2107
2108
            // Hide question title.
2109
            $group = array(
2110
                $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2111
                $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0')
2112
            );
2113
            $form->addGroup($group, null, get_lang('HideQuestionTitle'));
2114
2115
            // Attempts
2116
            $attempt_option = range(0, 10);
2117
            $attempt_option[0] = get_lang('Infinite');
2118
2119
            $form->addElement(
2120
                'select',
2121
                'exerciseAttempts',
2122
                get_lang('ExerciseAttempts'),
2123
                $attempt_option,
2124
                ['id' => 'exerciseAttempts']
2125
            );
2126
2127
            // Exercise time limit
2128
            $form->addElement('checkbox', 'activate_start_date_check',null, get_lang('EnableStartTime'), array('onclick' => 'activate_start_date()'));
2129
2130
            $var = self::selectTimeLimit();
2131
2132
            if (!empty($this->start_time)) {
2133
                $form->addElement('html', '<div id="start_date_div" style="display:block;">');
2134
            } else {
2135
                $form->addElement('html', '<div id="start_date_div" style="display:none;">');
2136
            }
2137
2138
            $form->addElement('date_time_picker', 'start_time');
2139
            $form->addElement('html','</div>');
2140
            $form->addElement('checkbox', 'activate_end_date_check', null , get_lang('EnableEndTime'), array('onclick' => 'activate_end_date()'));
2141
2142
            if (!empty($this->end_time)) {
2143
                $form->addElement('html', '<div id="end_date_div" style="display:block;">');
2144
            } else {
2145
                $form->addElement('html', '<div id="end_date_div" style="display:none;">');
2146
            }
2147
2148
            $form->addElement('date_time_picker', 'end_time');
2149
            $form->addElement('html','</div>');
2150
2151
            //$check_option=$this->selectType();
2152
            $diplay = 'block';
2153
            $form->addElement('checkbox', 'propagate_neg', null, get_lang('PropagateNegativeResults'));
2154
            $form->addCheckBox(
2155
                'save_correct_answers',
2156
                null,
2157
                get_lang('SaveTheCorrectAnswersForTheNextAttempt')
2158
            );
2159
            $form->addElement('html','<div class="clear">&nbsp;</div>');
2160
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2161
2162
            $form->addElement('html','<div id="divtimecontrol"  style="display:'.$diplay.';">');
2163
2164
            //Timer control
2165
            //$time_hours_option = range(0,12);
2166
            //$time_minutes_option = range(0,59);
2167
            $form->addElement(
2168
                'checkbox',
2169
                'enabletimercontrol',
2170
                null,
2171
                get_lang('EnableTimerControl'),
2172
                array(
2173
                    'onclick' => 'option_time_expired()',
2174
                    'id' => 'enabletimercontrol',
2175
                    'onload' => 'check_load_time()',
2176
                )
2177
            );
2178
2179
            $expired_date = (int)$this->selectExpiredTime();
2180
2181
            if (($expired_date!='0')) {
2182
                $form->addElement('html','<div id="timercontrol" style="display:block;">');
2183
            } else {
2184
                $form->addElement('html','<div id="timercontrol" style="display:none;">');
2185
            }
2186
            $form->addText(
2187
                'enabletimercontroltotalminutes',
2188
                get_lang('ExerciseTotalDurationInMinutes'),
2189
                false,
2190
                [
2191
                    'id' => 'enabletimercontroltotalminutes',
2192
                    'cols-size' => [2, 2, 8]
2193
                ]
2194
            );
2195
            $form->addElement('html','</div>');
2196
            $form->addElement(
2197
                'text',
2198
                'pass_percentage',
2199
                array(get_lang('PassPercentage'), null, '%'),
2200
                array('id' => 'pass_percentage')
2201
            );
2202
2203
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2204
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2205
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2206
2207
            // add the text_when_finished textbox
2208
            $form->addHtmlEditor(
2209
                'text_when_finished',
2210
                get_lang('TextWhenFinished'),
2211
                false,
2212
                false,
2213
                $editor_config
2214
            );
2215
2216
            $defaults = array();
2217
            if (api_get_setting('search_enabled') === 'true') {
2218
                require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2219
2220
                $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument'));
2221
                $form->addSelectLanguage('language', get_lang('SearchFeatureDocumentLanguage'));
2222
2223
                $specific_fields = get_specific_field_list();
2224
2225
                foreach ($specific_fields as $specific_field) {
2226
                    $form->addElement ('text', $specific_field['code'], $specific_field['name']);
2227
                    $filter = array(
2228
                        'c_id' => api_get_course_int_id(),
2229
                        'field_id' => $specific_field['id'],
2230
                        'ref_id' => $this->id,
2231
                        'tool_id' => "'" . TOOL_QUIZ . "'"
2232
                    );
2233
                    $values = get_specific_field_values_list($filter, array('value'));
2234
                    if ( !empty($values) ) {
2235
                        $arr_str_values = array();
2236
                        foreach ($values as $value) {
2237
                            $arr_str_values[] = $value['value'];
2238
                        }
2239
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2240
                    }
2241
                }
2242
            }
2243
2244
            $form->addElement('html','</div>');  //End advanced setting
2245
            $form->addElement('html','</div>');
2246
        }
2247
2248
        // submit
2249
        if (isset($_GET['exerciseId'])) {
2250
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2251
        } else {
2252
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2253
        }
2254
2255
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2256
2257
        if ($type == 'full') {
2258
            // rules
2259
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2260
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2261
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2262
        }
2263
2264
        // defaults
2265
        if ($type=='full') {
2266
            if ($this->id > 0) {
2267
                if ($this->random > $this->selectNbrQuestions()) {
2268
                    $defaults['randomQuestions'] =  $this->selectNbrQuestions();
2269
                } else {
2270
                    $defaults['randomQuestions'] = $this->random;
2271
                }
2272
2273
                $defaults['randomAnswers'] = $this->selectRandomAnswers();
2274
                $defaults['exerciseType'] = $this->selectType();
2275
                $defaults['exerciseTitle'] = $this->get_formated_title();
2276
                $defaults['exerciseDescription'] = $this->selectDescription();
2277
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2278
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2279
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2280
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2281
                $defaults['save_correct_answers'] = $this->selectSaveCorrectAnswers();
2282
                $defaults['review_answers'] = $this->review_answers;
2283
                $defaults['randomByCat'] = $this->selectRandomByCat();
2284
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2285
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2286
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2287
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2288
                $defaults['hide_question_title'] = $this->getHideQuestionTitle();
2289
2290
                if (!empty($this->start_time)) {
2291
                    $defaults['activate_start_date_check'] = 1;
2292
                }
2293
                if (!empty($this->end_time)) {
2294
                    $defaults['activate_end_date_check'] = 1;
2295
                }
2296
2297
                $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2298
                $defaults['end_time'] = !empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time()+84600);
2299
2300
                // Get expired time
2301
                if ($this->expired_time != '0') {
2302
                    $defaults['enabletimercontrol'] = 1;
2303
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2304
                } else {
2305
                    $defaults['enabletimercontroltotalminutes'] = 0;
2306
                }
2307
            } else {
2308
                $defaults['exerciseType'] = 2;
2309
                $defaults['exerciseAttempts'] = 0;
2310
                $defaults['randomQuestions'] = 0;
2311
                $defaults['randomAnswers'] = 0;
2312
                $defaults['exerciseDescription'] = '';
2313
                $defaults['exerciseFeedbackType'] = 0;
2314
                $defaults['results_disabled'] = 0;
2315
                $defaults['randomByCat'] = 0;
2316
                $defaults['text_when_finished'] = '';
2317
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2318
                $defaults['display_category_name'] = 1;
2319
                $defaults['end_time']   = date('Y-m-d 12:00:00', time()+84600);
2320
                $defaults['pass_percentage'] = '';
2321
                $defaults['end_button'] = $this->selectEndButton();
2322
                $defaults['question_selection_type'] = 1;
2323
                $defaults['hide_question_title'] = 0;
2324
                $defaults['on_success_message'] = null;
2325
                $defaults['on_failed_message'] = null;
2326
            }
2327
        } else {
2328
            $defaults['exerciseTitle'] = $this->selectTitle();
2329
            $defaults['exerciseDescription'] = $this->selectDescription();
2330
        }
2331
        if (api_get_setting('search_enabled') === 'true') {
2332
            $defaults['index_document'] = 'checked="checked"';
2333
        }
2334
        $form->setDefaults($defaults);
2335
2336
        // Freeze some elements.
2337
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2338
            $elementsToFreeze = array(
2339
                'randomQuestions',
2340
                //'randomByCat',
2341
                'exerciseAttempts',
2342
                'propagate_neg',
2343
                'enabletimercontrol',
2344
                'review_answers'
2345
            );
2346
2347
            foreach ($elementsToFreeze as $elementName) {
2348
                /** @var HTML_QuickForm_element $element */
2349
                $element = $form->getElement($elementName);
2350
                $element->freeze();
2351
            }
2352
        }
2353
    }
2354
2355
    /**
2356
     * function which process the creation of exercises
2357
     * @param FormValidator $form
2358
     * @param string
2359
     */
2360
    public function processCreation($form, $type = '')
2361
    {
2362
        $this->updateTitle(self::format_title_variable($form->getSubmitValue('exerciseTitle')));
2363
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2364
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2365
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2366
        $this->updateType($form->getSubmitValue('exerciseType'));
2367
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2368
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2369
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2370
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2371
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2372
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2373
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2374
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2375
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2376
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2377
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2378
        $this->updateCategories($form->getSubmitValue('category'));
2379
        $this->updateEndButton($form->getSubmitValue('end_button'));
2380
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2381
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2382
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2383
        $this->updateEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2384
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2385
        $this->setModelType($form->getSubmitValue('model_type'));
2386
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2387
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2388
        $this->sessionId = api_get_session_id();
2389
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2390
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2391
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2392
2393
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2394
            $start_time = $form->getSubmitValue('start_time');
2395
            $this->start_time = api_get_utc_datetime($start_time);
2396
        } else {
2397
            $this->start_time = null;
2398
        }
2399
2400
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2401
            $end_time = $form->getSubmitValue('end_time');
2402
            $this->end_time = api_get_utc_datetime($end_time);
2403
        } else {
2404
            $this->end_time = null;
2405
        }
2406
2407
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2408
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2409
            if ($this->expired_time == 0) {
2410
                $this->expired_time = $expired_total_time;
2411
            }
2412
        } else {
2413
            $this->expired_time = 0;
2414
        }
2415
2416
        if ($form->getSubmitValue('randomAnswers') == 1) {
2417
            $this->random_answers=1;
2418
        } else {
2419
            $this->random_answers=0;
2420
        }
2421
        $this->save($type);
2422
    }
2423
2424
    function search_engine_save()
2425
    {
2426
        if ($_POST['index_document'] != 1) {
2427
            return;
2428
        }
2429
        $course_id = api_get_course_id();
2430
2431
        require_once api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php';
2432
        require_once api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php';
2433
        require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2434
2435
        $specific_fields = get_specific_field_list();
2436
        $ic_slide = new IndexableChunk();
2437
2438
        $all_specific_terms = '';
2439 View Code Duplication
        foreach ($specific_fields as $specific_field) {
2440
            if (isset($_REQUEST[$specific_field['code']])) {
2441
                $sterms = trim($_REQUEST[$specific_field['code']]);
2442
                if (!empty($sterms)) {
2443
                    $all_specific_terms .= ' '. $sterms;
2444
                    $sterms = explode(',', $sterms);
2445
                    foreach ($sterms as $sterm) {
2446
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2447
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2448
                    }
2449
                }
2450
            }
2451
        }
2452
2453
        // build the chunk to index
2454
        $ic_slide->addValue("title", $this->exercise);
2455
        $ic_slide->addCourseId($course_id);
2456
        $ic_slide->addToolId(TOOL_QUIZ);
2457
        $xapian_data = array(
2458
            SE_COURSE_ID => $course_id,
2459
            SE_TOOL_ID => TOOL_QUIZ,
2460
            SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2461
            SE_USER => (int)api_get_user_id(),
2462
        );
2463
        $ic_slide->xapian_data = serialize($xapian_data);
2464
        $exercise_description = $all_specific_terms .' '. $this->description;
2465
        $ic_slide->addValue("content", $exercise_description);
2466
2467
        $di = new ChamiloIndexer();
2468
        isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2469
        $di->connectDb(NULL, NULL, $lang);
2470
        $di->addChunk($ic_slide);
2471
2472
        //index and return search engine document id
2473
        $did = $di->index();
2474
        if ($did) {
2475
            // save it to db
2476
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2477
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2478
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2479
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2480
            Database::query($sql);
2481
        }
2482
    }
2483
2484
    function search_engine_edit()
2485
    {
2486
        // update search enchine and its values table if enabled
2487
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian')) {
2488
            $course_id = api_get_course_id();
2489
2490
            // actually, it consists on delete terms from db,
2491
            // insert new ones, create a new search engine document, and remove the old one
2492
            // get search_did
2493
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2494
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2495
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2496
            $res = Database::query($sql);
2497
2498
            if (Database::num_rows($res) > 0) {
2499
                require_once(api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php');
2500
                require_once(api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php');
2501
                require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
2502
2503
                $se_ref = Database::fetch_array($res);
2504
                $specific_fields = get_specific_field_list();
2505
                $ic_slide = new IndexableChunk();
2506
2507
                $all_specific_terms = '';
2508
                foreach ($specific_fields as $specific_field) {
2509
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2510
                    if (isset($_REQUEST[$specific_field['code']])) {
2511
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2512
                        $all_specific_terms .= ' '. $sterms;
2513
                        $sterms = explode(',', $sterms);
2514
                        foreach ($sterms as $sterm) {
2515
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2516
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2517
                        }
2518
                    }
2519
                }
2520
2521
                // build the chunk to index
2522
                $ic_slide->addValue("title", $this->exercise);
2523
                $ic_slide->addCourseId($course_id);
2524
                $ic_slide->addToolId(TOOL_QUIZ);
2525
                $xapian_data = array(
2526
                    SE_COURSE_ID => $course_id,
2527
                    SE_TOOL_ID => TOOL_QUIZ,
2528
                    SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2529
                    SE_USER => (int)api_get_user_id(),
2530
                );
2531
                $ic_slide->xapian_data = serialize($xapian_data);
2532
                $exercise_description = $all_specific_terms .' '. $this->description;
2533
                $ic_slide->addValue("content", $exercise_description);
2534
2535
                $di = new ChamiloIndexer();
2536
                isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2537
                $di->connectDb(NULL, NULL, $lang);
2538
                $di->remove_document((int)$se_ref['search_did']);
2539
                $di->addChunk($ic_slide);
2540
2541
                //index and return search engine document id
2542
                $did = $di->index();
2543
                if ($did) {
2544
                    // save it to db
2545
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2546
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2547
                    Database::query($sql);
2548
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2549
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2550
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2551
                    Database::query($sql);
2552
                }
2553
            } else {
2554
                $this->search_engine_save();
2555
            }
2556
        }
2557
    }
2558
2559
    function search_engine_delete()
2560
    {
2561
        // remove from search engine if enabled
2562
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian') ) {
2563
            $course_id = api_get_course_id();
2564
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2565
            $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';
2566
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2567
            $res = Database::query($sql);
2568
            if (Database::num_rows($res) > 0) {
2569
                $row = Database::fetch_array($res);
2570
                require_once(api_get_path(LIBRARY_PATH) .'search/ChamiloIndexer.class.php');
2571
                $di = new ChamiloIndexer();
2572
                $di->remove_document((int)$row['search_did']);
2573
                unset($di);
2574
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2575
                foreach ( $this->questionList as $question_i) {
2576
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2577
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2578
                    $qres = Database::query($sql);
2579
                    if (Database::num_rows($qres) > 0) {
2580
                        $qrow = Database::fetch_array($qres);
2581
                        $objQuestion = Question::getInstance($qrow['type']);
2582
                        $objQuestion = Question::read((int)$question_i);
2583
                        $objQuestion->search_engine_edit($this->id, FALSE, TRUE);
2584
                        unset($objQuestion);
2585
                    }
2586
                }
2587
            }
2588
            $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';
2589
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2590
            Database::query($sql);
2591
2592
            // remove terms from db
2593
            require_once api_get_path(LIBRARY_PATH) .'specific_fields_manager.lib.php';
2594
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2595
        }
2596
    }
2597
2598
    public function selectExpiredTime()
2599
    {
2600
        return $this->expired_time;
2601
    }
2602
2603
    /**
2604
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2605
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2606
     * Works with exercises in sessions
2607
     * @param bool $cleanLpTests
2608
     * @param string $cleanResultBeforeDate
2609
     *
2610
     * @return int quantity of user's exercises deleted
2611
     */
2612
    public function clean_results($cleanLpTests = false, $cleanResultBeforeDate = null)
2613
    {
2614
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2615
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2616
2617
        $sql_where = '  AND
2618
                        orig_lp_id = 0 AND
2619
                        orig_lp_item_id = 0';
2620
2621
        // if we want to delete results from LP too
2622
        if ($cleanLpTests) {
2623
            $sql_where = "";
2624
        }
2625
2626
        // if we want to delete attempts before date $cleanResultBeforeDate
2627
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2628
2629
        if (!empty($cleanResultBeforeDate)) {
2630
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2631
            if (api_is_valid_date($cleanResultBeforeDate)) {
2632
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2633
            } else {
2634
                return 0;
2635
            }
2636
        }
2637
2638
        $sql = "SELECT exe_id
2639
                FROM $table_track_e_exercises
2640
                WHERE
2641
                    c_id = ".api_get_course_int_id()." AND
2642
                    exe_exo_id = ".$this->id." AND
2643
                    session_id = ".api_get_session_id()." ".
2644
                    $sql_where;
2645
2646
        $result   = Database::query($sql);
2647
        $exe_list = Database::store_result($result);
2648
2649
        // deleting TRACK_E_ATTEMPT table
2650
        // check if exe in learning path or not
2651
        $i = 0;
2652
        if (is_array($exe_list) && count($exe_list) > 0) {
2653
            foreach ($exe_list as $item) {
2654
                $sql = "DELETE FROM $table_track_e_attempt
2655
                        WHERE exe_id = '".$item['exe_id']."'";
2656
                Database::query($sql);
2657
                $i++;
2658
            }
2659
        }
2660
2661
        $session_id = api_get_session_id();
2662
        // delete TRACK_E_EXERCISES table
2663
        $sql = "DELETE FROM $table_track_e_exercises
2664
                WHERE c_id = ".api_get_course_int_id()."
2665
                AND exe_exo_id = ".$this->id."
2666
                $sql_where
2667
                AND session_id = ".$session_id."";
2668
        Database::query($sql);
2669
2670
        Event::addEvent(
2671
            LOG_EXERCISE_RESULT_DELETE,
2672
            LOG_EXERCISE_ID,
2673
            $this->id,
2674
            null,
2675
            null,
2676
            api_get_course_int_id(),
2677
            $session_id
2678
        );
2679
2680
        return $i;
2681
    }
2682
2683
    /**
2684
     * Copies an exercise (duplicate all questions and answers)
2685
     */
2686
    public function copy_exercise()
2687
    {
2688
        $exercise_obj = $this;
2689
2690
        // force the creation of a new exercise
2691
        $exercise_obj->updateTitle($exercise_obj->selectTitle().' - '.get_lang('Copy'));
2692
        //Hides the new exercise
2693
        $exercise_obj->updateStatus(false);
2694
        $exercise_obj->updateId(0);
2695
        $exercise_obj->save();
2696
2697
        $new_exercise_id = $exercise_obj->selectId();
2698
        $question_list 	 = $exercise_obj->selectQuestionList();
2699
2700
        if (!empty($question_list)) {
2701
            //Question creation
2702
2703
            foreach ($question_list as $old_question_id) {
2704
                $old_question_obj = Question::read($old_question_id);
2705
                $new_id = $old_question_obj->duplicate();
2706
                if ($new_id) {
2707
                    $new_question_obj = Question::read($new_id);
2708
2709
                    if (isset($new_question_obj) && $new_question_obj) {
2710
                        $new_question_obj->addToList($new_exercise_id);
2711
                        // This should be moved to the duplicate function
2712
                        $new_answer_obj = new Answer($old_question_id);
2713
                        $new_answer_obj->read();
2714
                        $new_answer_obj->duplicate($new_question_obj);
2715
                    }
2716
                }
2717
            }
2718
        }
2719
    }
2720
2721
    /**
2722
     * Changes the exercise id
2723
     *
2724
     * @param int $id - exercise id
2725
     */
2726
    private function updateId($id)
2727
    {
2728
        $this->id = $id;
2729
    }
2730
2731
    /**
2732
     * Changes the exercise status
2733
     *
2734
     * @param string $status - exercise status
2735
     */
2736
    function updateStatus($status)
2737
    {
2738
        $this->active = $status;
2739
    }
2740
2741
    /**
2742
     * @param int $lp_id
2743
     * @param int $lp_item_id
2744
     * @param int $lp_item_view_id
2745
     * @param string $status
2746
     * @return array
2747
     */
2748
    public function get_stat_track_exercise_info(
2749
        $lp_id = 0,
2750
        $lp_item_id = 0,
2751
        $lp_item_view_id = 0,
2752
        $status = 'incomplete'
2753
    ) {
2754
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2755
        if (empty($lp_id)) {
2756
            $lp_id = 0;
2757
        }
2758
        if (empty($lp_item_id)) {
2759
            $lp_item_id = 0;
2760
        }
2761
        if (empty($lp_item_view_id)) {
2762
            $lp_item_view_id = 0;
2763
        }
2764
        $condition = ' WHERE exe_exo_id 	= ' . "'" . $this->id . "'" .' AND
2765
					   exe_user_id 			= ' . "'" . api_get_user_id() . "'" . ' AND
2766
					   c_id                 = ' . api_get_course_int_id() . ' AND
2767
					   status 				= ' . "'" . Database::escape_string($status). "'" . ' AND
2768
					   orig_lp_id 			= ' . "'" . $lp_id . "'" . ' AND
2769
					   orig_lp_item_id 		= ' . "'" . $lp_item_id . "'" . ' AND
2770
                       orig_lp_item_view_id = ' . "'" . $lp_item_view_id . "'" . ' AND
2771
					   session_id 			= ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case
2772
2773
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2774
2775
        $result = Database::query($sql_track);
2776
        $new_array = array();
2777
        if (Database::num_rows($result) > 0 ) {
2778
            $new_array = Database::fetch_array($result, 'ASSOC');
2779
            $new_array['num_exe'] = Database::num_rows($result);
2780
        }
2781
2782
        return $new_array;
2783
    }
2784
2785
    /**
2786
     * Saves a test attempt
2787
     *
2788
     * @param int  $clock_expired_time clock_expired_time
2789
     * @param int  int lp id
2790
     * @param int  int lp item id
2791
     * @param int  int lp item_view id
2792
     * @param array $questionList
2793
     * @param float $weight
2794
     *
2795
     * @return int
2796
     */
2797
    public function save_stat_track_exercise_info(
2798
        $clock_expired_time = 0,
2799
        $safe_lp_id = 0,
2800
        $safe_lp_item_id = 0,
2801
        $safe_lp_item_view_id = 0,
2802
        $questionList = array(),
2803
        $weight = 0
2804
    ) {
2805
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2806
        $safe_lp_id = intval($safe_lp_id);
2807
        $safe_lp_item_id = intval($safe_lp_item_id);
2808
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
2809
2810
        if (empty($safe_lp_id)) {
2811
            $safe_lp_id = 0;
2812
        }
2813
        if (empty($safe_lp_item_id)) {
2814
            $safe_lp_item_id = 0;
2815
        }
2816
        if (empty($clock_expired_time)) {
2817
            $clock_expired_time = null;
2818
        }
2819
2820
        $questionList = array_map('intval', $questionList);
2821
2822
        $params = array(
2823
            'exe_exo_id' => $this->id ,
2824
            'exe_user_id' => api_get_user_id(),
2825
            'c_id' => api_get_course_int_id(),
2826
            'status' =>  'incomplete',
2827
            'session_id'  => api_get_session_id(),
2828
            'data_tracking'  => implode(',', $questionList) ,
2829
            'start_date' => api_get_utc_datetime(),
2830
            'orig_lp_id' => $safe_lp_id,
2831
            'orig_lp_item_id'  => $safe_lp_item_id,
2832
            'orig_lp_item_view_id'  => $safe_lp_item_view_id,
2833
            'exe_weighting'=> $weight,
2834
            'user_ip' => api_get_real_ip(),
2835
            'exe_date' => api_get_utc_datetime(),
2836
            'exe_result' => 0,
2837
            'steps_counter' => 0,
2838
            'exe_duration' => 0,
2839
            'expired_time_control' => $clock_expired_time,
2840
            'questions_to_check' => ''
2841
        );
2842
2843
        $id = Database::insert($track_exercises, $params);
2844
2845
        return $id;
2846
    }
2847
2848
    /**
2849
     * @param int $question_id
2850
     * @param int $questionNum
2851
     * @param array $questions_in_media
2852
     * @param string $currentAnswer
2853
     * @return string
2854
     */
2855
    public function show_button($question_id, $questionNum, $questions_in_media = array(), $currentAnswer = '')
2856
    {
2857
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
2858
2859
        $nbrQuestions = $this->get_count_question_list();
2860
2861
        $all_button = [];
2862
        $html = $label = '';
2863
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']):null;
2864
2865
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
2866
            $urlTitle = get_lang('ContinueTest');
2867
2868
            if ($questionNum == count($this->questionList)) {
2869
                $urlTitle = get_lang('EndTest');
2870
            }
2871
2872
            $html .= Display::url(
2873
                $urlTitle,
2874
                'exercise_submit_modal.php?' . http_build_query([
2875
                    'learnpath_id' => $safe_lp_id,
2876
                    'learnpath_item_id' => $safe_lp_item_id,
2877
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
2878
                    'origin' => $origin,
2879
                    'hotspot' => $hotspot_get,
2880
                    'nbrQuestions' => $nbrQuestions,
2881
                    'num' => $questionNum,
2882
                    'exerciseType' => $this->type,
2883
                    'exerciseId' => $this->id
2884
                ]),
2885
                [
2886
                    'class' => 'ajax btn btn-default',
2887
                    'data-title' => $urlTitle,
2888
                    'data-size' => 'md'
2889
                ]
2890
            );
2891
            $html .='<br />';
2892
        } else {
2893
            // User
2894
            if (api_is_allowed_to_session_edit()) {
2895
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum) {
2896 View Code Duplication
                    if ($this->review_answers) {
2897
                        $label = get_lang('ReviewQuestions');
2898
                        $class = 'btn btn-success';
2899
                    } else {
2900
                        $label = get_lang('EndTest');
2901
                        $class = 'btn btn-warning';
2902
                    }
2903
                } else {
2904
                    $label = get_lang('NextQuestion');
2905
                    $class = 'btn btn-primary';
2906
                }
2907
				$class .= ' question-validate-btn'; // used to select it with jquery
2908
                if ($this->type == ONE_PER_PAGE) {
2909
                    if ($questionNum != 1) {
2910
                        $prev_question = $questionNum - 2;
2911
                        $all_button[] = Display::button(
2912
                            'previous_question_and_save',
2913
                            get_lang('PreviousQuestion'),
2914
                            [
2915
                                'type' => 'button',
2916
                                'class' => 'btn btn-default',
2917
                                'data-prev' => $prev_question,
2918
                                'data-question' => $question_id
2919
                            ]
2920
                        );
2921
                    }
2922
2923
                    //Next question
2924
                    if (!empty($questions_in_media)) {
2925
                        $all_button[] = Display::button(
2926
                            'save_question_list',
2927
                            $label,
2928
                            [
2929
                                'type' => 'button',
2930
                                'class' => $class,
2931
                                'data-list' => implode(",", $questions_in_media)
2932
                            ]
2933
                        );
2934
                    } else {
2935
                        $all_button[] = Display::button(
2936
                            'save_now',
2937
                            $label,
2938
                            ['type' => 'button', 'class' => $class, 'data-question' => $question_id]
2939
                        );
2940
                    }
2941
                    $all_button[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
2942
2943
                    $html .= implode(PHP_EOL, $all_button);
2944
                } else {
2945 View Code Duplication
                    if ($this->review_answers) {
2946
                        $all_label = get_lang('ReviewQuestions');
2947
                        $class = 'btn btn-success';
2948
                    } else {
2949
                        $all_label = get_lang('EndTest');
2950
                        $class = 'btn btn-warning';
2951
                    }
2952
					$class .= ' question-validate-btn'; // used to select it with jquery
2953
                    $all_button[] = Display::button(
2954
                        'validate_all',
2955
                        $all_label,
2956
                        ['type' => 'button', 'class' => $class]
2957
                    );
2958
                    $all_button[] = '&nbsp;' . Display::span(null, ['id' => 'save_all_reponse']);
2959
                    $html .= implode(PHP_EOL, $all_button);
2960
                }
2961
            }
2962
        }
2963
2964
        return $html;
2965
    }
2966
2967
    /**
2968
     * So the time control will work
2969
     *
2970
     * @param string $time_left
2971
     * @return string
2972
     */
2973
    public function show_time_control_js($time_left)
2974
    {
2975
        $time_left = intval($time_left);
2976
        return "<script>
2977
2978
            function get_expired_date_string(expired_time) {
2979
                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
2980
                var day, month, year, hours, minutes, seconds, date_string;
2981
                var obj_date = new Date(expired_time);
2982
                day     = obj_date.getDate();
2983
                if (day < 10) day = '0' + day;
2984
                    month   = obj_date.getMonth();
2985
                    year    = obj_date.getFullYear();
2986
                    hours   = obj_date.getHours();
2987
                if (hours < 10) hours = '0' + hours;
2988
                minutes = obj_date.getMinutes();
2989
                if (minutes < 10) minutes = '0' + minutes;
2990
                seconds = obj_date.getSeconds();
2991
                if (seconds < 10) seconds = '0' + seconds;
2992
                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
2993
                return date_string;
2994
            }
2995
2996
            function open_clock_warning() {
2997
                $('#clock_warning').dialog({
2998
                    modal:true,
2999
                    height:250,
3000
                    closeOnEscape: false,
3001
                    resizable: false,
3002
                    buttons: {
3003
                        '".addslashes(get_lang("EndTest"))."': function() {
3004
                            $('#clock_warning').dialog('close');
3005
                        }
3006
                    },
3007
                    close: function() {
3008
                        send_form();
3009
                    }
3010
                });
3011
                $('#clock_warning').dialog('open');
3012
3013
                $('#counter_to_redirect').epiclock({
3014
                    mode: $.epiclock.modes.countdown,
3015
                    offset: {seconds: 5},
3016
                    format: 's'
3017
                }).bind('timer', function () {
3018
                    send_form();
3019
                });
3020
            }
3021
3022
            function send_form() {
3023
                if ($('#exercise_form').length) {
3024
                    $('#exercise_form').submit();
3025
                } else {
3026
                    // In exercise_reminder.php
3027
                    final_submit();
3028
                }
3029
            }
3030
3031
            function onExpiredTimeExercise() {
3032
                $('#wrapper-clock').hide();
3033
                //$('#exercise_form').hide();
3034
                $('#expired-message-id').show();
3035
3036
                //Fixes bug #5263
3037
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3038
                open_clock_warning();
3039
            }
3040
3041
			$(document).ready(function() {
3042
				var current_time = new Date().getTime();
3043
                var time_left    = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
3044
				var expired_time = current_time + (time_left*1000);
3045
				var expired_date = get_expired_date_string(expired_time);
3046
3047
                $('#exercise_clock_warning').epiclock({
3048
                    mode: $.epiclock.modes.countdown,
3049
                    offset: {seconds: time_left},
3050
                    format: 'x:i:s',
3051
                    renderer: 'minute'
3052
                }).bind('timer', function () {
3053
                    onExpiredTimeExercise();
3054
                });
3055
	       		$('#submit_save').click(function () {});
3056
	    });
3057
	    </script>";
3058
    }
3059
3060
    /**
3061
     * Lp javascript for hotspots
3062
     */
3063
    public function show_lp_javascript()
3064
    {
3065
        return '';
3066
    }
3067
3068
    /**
3069
     * This function was originally found in the exercise_show.php
3070
     * @param int $exeId
3071
     * @param int $questionId
3072
     * @param int $choice the user selected
3073
     * @param string $from  function is called from 'exercise_show' or 'exercise_result'
3074
     * @param array $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates
3075
     * @param bool $saved_results save results in the DB or just show the reponse
3076
     * @param bool $from_database gets information from DB or from the current selection
3077
     * @param bool $show_result show results or not
3078
     * @param int $propagate_neg
3079
     * @param array $hotspot_delineation_result
3080
     * @param bool $showTotalScoreAndUserChoicesInLastAttempt
3081
     * @todo    reduce parameters of this function
3082
     * @return  string  html code
3083
     */
3084
    public function manage_answer(
3085
        $exeId,
3086
        $questionId,
3087
        $choice,
3088
        $from = 'exercise_show',
3089
        $exerciseResultCoordinates = [],
3090
        $saved_results = true,
3091
        $from_database = false,
3092
        $show_result = true,
3093
        $propagate_neg = 0,
3094
        $hotspot_delineation_result = [],
3095
        $showTotalScoreAndUserChoicesInLastAttempt = true
3096
    ) {
3097
        global $debug;
3098
        //needed in order to use in the exercise_attempt() for the time
3099
        global $learnpath_id, $learnpath_item_id;
3100
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3101
        $em = Database::getManager();
3102
        $feedback_type = $this->selectFeedbackType();
3103
        $results_disabled = $this->selectResultsDisabled();
3104
3105
        if ($debug) {
3106
            error_log("<------ manage_answer ------> ");
3107
            error_log('exe_id: '.$exeId);
3108
            error_log('$from:  '.$from);
3109
            error_log('$saved_results: '.intval($saved_results));
3110
            error_log('$from_database: '.intval($from_database));
3111
            error_log('$show_result: '.$show_result);
3112
            error_log('$propagate_neg: '.$propagate_neg);
3113
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3114
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3115
            error_log('$learnpath_id: '.$learnpath_id);
3116
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3117
            error_log('$choice: '.print_r($choice, 1));
3118
        }
3119
3120
        $final_overlap = 0;
3121
        $final_missing = 0;
3122
        $final_excess = 0;
3123
        $overlap_color = 0;
3124
        $missing_color = 0;
3125
        $excess_color = 0;
3126
        $threadhold1 = 0;
3127
        $threadhold2 = 0;
3128
        $threadhold3 = 0;
3129
        $arrques = null;
3130
        $arrans  = null;
3131
        $questionId = intval($questionId);
3132
        $exeId = intval($exeId);
3133
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3134
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3135
3136
        // Creates a temporary Question object
3137
        $course_id = $this->course_id;
3138
        $objQuestionTmp = Question::read($questionId, $course_id);
3139
3140
        if ($objQuestionTmp === false) {
3141
            return false;
3142
        }
3143
3144
        $questionName = $objQuestionTmp->selectTitle();
3145
        $questionWeighting = $objQuestionTmp->selectWeighting();
3146
        $answerType = $objQuestionTmp->selectType();
3147
        $quesId = $objQuestionTmp->selectId();
3148
        $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...
3149
        $next = 1; //not for now
3150
        $totalWeighting = 0;
3151
        $totalScore = 0;
3152
3153
        // Extra information of the question
3154
        if (!empty($extra)) {
3155
            $extra = explode(':', $extra);
3156
            if ($debug) {
3157
                error_log(print_r($extra, 1));
3158
            }
3159
            // Fixes problems with negatives values using intval
3160
            $true_score = floatval(trim($extra[0]));
3161
            $false_score = floatval(trim($extra[1]));
3162
            $doubt_score = floatval(trim($extra[2]));
3163
        }
3164
3165
        // Construction of the Answer object
3166
        $objAnswerTmp = new Answer($questionId);
3167
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3168
3169
        if ($debug) {
3170
            error_log('Count of answers: '.$nbrAnswers);
3171
            error_log('$answerType: '.$answerType);
3172
        }
3173
3174
        if ($answerType == FREE_ANSWER ||
3175
            $answerType == ORAL_EXPRESSION ||
3176
            $answerType == CALCULATED_ANSWER ||
3177
            $answerType == ANNOTATION
3178
        ) {
3179
            $nbrAnswers = 1;
3180
        }
3181
3182
        if ($answerType == ORAL_EXPRESSION) {
3183
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3184
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3185
3186
            $objQuestionTmp->initFile(
3187
                api_get_session_id(),
3188
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3189
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3190
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3191
            );
3192
3193
            //probably this attempt came in an exercise all question by page
3194
            if ($feedback_type == 0) {
3195
                $objQuestionTmp->replaceWithRealExe($exeId);
3196
            }
3197
        }
3198
3199
        $user_answer = '';
3200
        // Get answer list for matching
3201
        $sql = "SELECT id_auto, id, answer
3202
                FROM $table_ans
3203
                WHERE c_id = $course_id AND question_id = $questionId";
3204
        $res_answer = Database::query($sql);
3205
3206
        $answerMatching = array();
3207
        while ($real_answer = Database::fetch_array($res_answer)) {
3208
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3209
        }
3210
3211
        $real_answers = array();
3212
        $quiz_question_options = Question::readQuestionOption(
3213
            $questionId,
3214
            $course_id
3215
        );
3216
3217
        $organs_at_risk_hit = 0;
3218
        $questionScore = 0;
3219
        $answer_correct_array = array();
3220
        $orderedHotspots = [];
3221
3222
        if ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
3223
            $orderedHotspots = $em
3224
                ->getRepository('ChamiloCoreBundle:TrackEHotspot')
3225
                ->findBy([
3226
                        'hotspotQuestionId' => $questionId,
3227
                        'cId' => $course_id,
3228
                        'hotspotExeId' => $exeId
3229
                    ],
3230
                    ['hotspotAnswerId' => 'ASC']
3231
                );
3232
        }
3233
3234
        if ($debug) error_log('Start answer loop ');
3235
3236
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3237
            $answer = $objAnswerTmp->selectAnswer($answerId);
3238
            $answerComment = $objAnswerTmp->selectComment($answerId);
3239
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3240
            $answerWeighting = (float)$objAnswerTmp->selectWeighting($answerId);
3241
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3242
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? $objAnswerTmp->iid[$answerId] : '';
3243
            $answer_correct_array[$answerId] = (bool)$answerCorrect;
3244
3245
            if ($debug) {
3246
                error_log("answer auto id: $answerAutoId ");
3247
                error_log("answer correct: $answerCorrect ");
3248
            }
3249
3250
            // Delineation
3251
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3252
            $answer_delineation_destination=$objAnswerTmp->selectDestination(1);
3253
3254
            switch ($answerType) {
3255
                // for unique answer
3256
                case UNIQUE_ANSWER:
3257
                case UNIQUE_ANSWER_IMAGE:
3258
                case UNIQUE_ANSWER_NO_OPTION:
3259
                    if ($from_database) {
3260
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3261
                                WHERE
3262
                                    exe_id = '".$exeId."' AND
3263
                                    question_id= '".$questionId."'";
3264
                        $result = Database::query($sql);
3265
                        $choice = Database::result($result,0,"answer");
3266
3267
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3268
                        if ($studentChoice) {
3269
                            $questionScore += $answerWeighting;
3270
                            $totalScore += $answerWeighting;
3271
                        }
3272
                    } else {
3273
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3274
                        if ($studentChoice) {
3275
                            $questionScore += $answerWeighting;
3276
                            $totalScore += $answerWeighting;
3277
                        }
3278
                    }
3279
                    break;
3280
                // for multiple answers
3281
                case MULTIPLE_ANSWER_TRUE_FALSE:
3282
                    if ($from_database) {
3283
                        $choice = array();
3284
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3285
                                WHERE
3286
                                    exe_id = $exeId AND
3287
                                    question_id = ".$questionId;
3288
3289
                        $result = Database::query($sql);
3290 View Code Duplication
                        while ($row = Database::fetch_array($result)) {
3291
                            $ind = $row['answer'];
3292
                            $values = explode(':', $ind);
3293
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3294
                            $option = isset($values[1]) ? $values[1] : '';
3295
                            $choice[$my_answer_id] = $option;
3296
                        }
3297
                    }
3298
3299
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3300
3301
                    if (!empty($studentChoice)) {
3302
                        if ($studentChoice == $answerCorrect) {
3303
                            $questionScore += $true_score;
3304
                        } else {
3305
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3306
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3307
                            ) {
3308
                                $questionScore += $doubt_score;
3309
                            } else {
3310
                                $questionScore += $false_score;
3311
                            }
3312
                        }
3313
                    } else {
3314
                        // If no result then the user just hit don't know
3315
                        $studentChoice = 3;
3316
                        $questionScore  +=  $doubt_score;
3317
                    }
3318
                    $totalScore = $questionScore;
3319
                    break;
3320 View Code Duplication
                case MULTIPLE_ANSWER: //2
3321
                    if ($from_database) {
3322
                        $choice = array();
3323
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3324
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3325
                        $resultans = Database::query($sql);
3326
                        while ($row = Database::fetch_array($resultans)) {
3327
                            $ind = $row['answer'];
3328
                            $choice[$ind] = 1;
3329
                        }
3330
3331
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3332
                        $real_answers[$answerId] = (bool)$studentChoice;
3333
3334
                        if ($studentChoice) {
3335
                            $questionScore  +=$answerWeighting;
3336
                        }
3337
                    } else {
3338
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3339
                        $real_answers[$answerId] = (bool)$studentChoice;
3340
3341
                        if (isset($studentChoice)) {
3342
                            $questionScore  += $answerWeighting;
3343
                        }
3344
                    }
3345
                    $totalScore += $answerWeighting;
3346
3347
                    if ($debug) error_log("studentChoice: $studentChoice");
3348
                    break;
3349 View Code Duplication
                case GLOBAL_MULTIPLE_ANSWER:
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
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3360
                        $real_answers[$answerId] = (bool)$studentChoice;
3361
                        if ($studentChoice) {
3362
                            $questionScore +=$answerWeighting;
3363
                        }
3364
                    } else {
3365
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3366
                        if (isset($studentChoice)) {
3367
                            $questionScore += $answerWeighting;
3368
                        }
3369
                        $real_answers[$answerId] = (bool)$studentChoice;
3370
                    }
3371
                    $totalScore += $answerWeighting;
3372
                    if ($debug) error_log("studentChoice: $studentChoice");
3373
                    break;
3374
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3375
                    if ($from_database) {
3376
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3377
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3378
                        $resultans = Database::query($sql);
3379 View Code Duplication
                        while ($row = Database::fetch_array($resultans)) {
3380
                            $ind = $row['answer'];
3381
                            $result = explode(':',$ind);
3382
                            if (isset($result[0])) {
3383
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3384
                                $option = isset($result[1]) ? $result[1] : '';
3385
                                $choice[$my_answer_id] = $option;
3386
                            }
3387
                        }
3388
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3389
3390
                        if ($answerCorrect == $studentChoice) {
3391
                            //$answerCorrect = 1;
3392
                            $real_answers[$answerId] = true;
3393
                        } else {
3394
                            //$answerCorrect = 0;
3395
                            $real_answers[$answerId] = false;
3396
                        }
3397
                    } else {
3398
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3399
                        if ($answerCorrect == $studentChoice) {
3400
                            //$answerCorrect = 1;
3401
                            $real_answers[$answerId] = true;
3402
                        } else {
3403
                            //$answerCorrect = 0;
3404
                            $real_answers[$answerId] = false;
3405
                        }
3406
                    }
3407
                    break;
3408
                case MULTIPLE_ANSWER_COMBINATION:
3409
                    if ($from_database) {
3410
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3411
                                WHERE exe_id = $exeId AND question_id= $questionId";
3412
                        $resultans = Database::query($sql);
3413
                        while ($row = Database::fetch_array($resultans)) {
3414
                            $ind = $row['answer'];
3415
                            $choice[$ind] = 1;
3416
                        }
3417
3418
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3419
3420 View Code Duplication
                        if ($answerCorrect == 1) {
3421
                            if ($studentChoice) {
3422
                                $real_answers[$answerId] = true;
3423
                            } else {
3424
                                $real_answers[$answerId] = false;
3425
                            }
3426
                        } else {
3427
                            if ($studentChoice) {
3428
                                $real_answers[$answerId] = false;
3429
                            } else {
3430
                                $real_answers[$answerId] = true;
3431
                            }
3432
                        }
3433
                    } else {
3434
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3435
3436 View Code Duplication
                        if ($answerCorrect == 1) {
3437
                            if ($studentChoice) {
3438
                                $real_answers[$answerId] = true;
3439
                            } else {
3440
                                $real_answers[$answerId] = false;
3441
                            }
3442
                        } else {
3443
                            if ($studentChoice) {
3444
                                $real_answers[$answerId] = false;
3445
                            } else {
3446
                                $real_answers[$answerId] = true;
3447
                            }
3448
                        }
3449
                    }
3450
                    break;
3451
                case FILL_IN_BLANKS:
3452
                    $str = '';
3453
                    $answerFromDatabase = '';
3454
                    if ($from_database) {
3455
                        $sql = "SELECT answer
3456
                                FROM $TBL_TRACK_ATTEMPT
3457
                                WHERE
3458
                                    exe_id = $exeId AND
3459
                                    question_id= ".intval($questionId);
3460
                        $result = Database::query($sql);
3461
                        $str = $answerFromDatabase = Database::result($result, 0, 'answer');
3462
                    }
3463
3464
                    if ($saved_results == false && strpos($str, 'font color') !== false) {
3465
                        // the question is encoded like this
3466
                        // [A] B [C] D [E] F::10,10,10@1
3467
                        // number 1 before the "@" means that is a switchable fill in blank question
3468
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3469
                        // means that is a normal fill blank question
3470
                        // first we explode the "::"
3471
                        $pre_array = explode('::', $answer);
3472
3473
                        // is switchable fill blank or not
3474
                        $last = count($pre_array) - 1;
3475
                        $is_set_switchable = explode('@', $pre_array[$last]);
3476
                        $switchable_answer_set = false;
3477
                        if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3478
                            $switchable_answer_set = true;
3479
                        }
3480
                        $answer = '';
3481
                        for ($k = 0; $k < $last; $k++) {
3482
                            $answer .= $pre_array[$k];
3483
                        }
3484
                        // splits weightings that are joined with a comma
3485
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3486
                        // we save the answer because it will be modified
3487
                        $temp = $answer;
3488
                        $answer = '';
3489
                        $j = 0;
3490
                        //initialise answer tags
3491
                        $user_tags = $correct_tags = $real_text = array();
3492
                        // the loop will stop at the end of the text
3493
                        while (1) {
3494
                            // quits the loop if there are no more blanks (detect '[')
3495
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
3496
                                // adds the end of the text
3497
                                $answer = $temp;
3498
                                $real_text[] = $answer;
3499
                                break; //no more "blanks", quit the loop
3500
                            }
3501
                            // adds the piece of text that is before the blank
3502
                            //and ends with '[' into a general storage array
3503
                            $real_text[] = api_substr($temp, 0, $pos +1);
3504
                            $answer .= api_substr($temp, 0, $pos +1);
3505
                            //take the string remaining (after the last "[" we found)
3506
                            $temp = api_substr($temp, $pos +1);
3507
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3508
                            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 3506 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...
3509
                                // adds the end of the text
3510
                                $answer .= $temp;
3511
                                break;
3512
                            }
3513
                            if ($from_database) {
3514
                                $str = $answerFromDatabase;
3515
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3516
                                $str = str_replace('\r\n', '', $str);
3517
3518
                                $choice = $arr[1];
3519
                                if (isset($choice[$j])) {
3520
                                    $tmp = api_strrpos($choice[$j], ' / ');
3521
                                    $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 3520 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...
3522
                                    $choice[$j] = trim($choice[$j]);
3523
                                    // Needed to let characters ' and " to work as part of an answer
3524
                                    $choice[$j] = stripslashes($choice[$j]);
3525
                                } else {
3526
                                    $choice[$j] = null;
3527
                                }
3528
                            } else {
3529
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3530
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3531
                            }
3532
3533
                            $user_tags[] = $choice[$j];
3534
                            //put the contents of the [] answer tag into correct_tags[]
3535
                            $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 3506 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...
3536
                            $j++;
3537
                            $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 3537 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

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

Consider the follow example

<?php

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

    return false;
}

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

Loading history...
3538
                        }
3539
                        $answer = '';
3540
                        $real_correct_tags = $correct_tags;
3541
                        $chosen_list = array();
3542
3543
                        for ($i = 0; $i < count($real_correct_tags); $i++) {
3544
                            if ($i == 0) {
3545
                                $answer .= $real_text[0];
3546
                            }
3547
                            if (!$switchable_answer_set) {
3548
                                // Needed to parse ' and " characters
3549
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3550 View Code Duplication
                                if ($correct_tags[$i] == $user_tags[$i]) {
3551
                                    // gives the related weighting to the student
3552
                                    $questionScore += $answerWeighting[$i];
3553
                                    // increments total score
3554
                                    $totalScore += $answerWeighting[$i];
3555
                                    // adds the word in green at the end of the string
3556
                                    $answer .= $correct_tags[$i];
3557
                                } elseif (!empty($user_tags[$i])) {
3558
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3559
                                    // adds the word in red at the end of the string, and strikes it
3560
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3561
                                } else {
3562
                                    // adds a tabulation if no word has been typed by the student
3563
                                    $answer .= ''; // remove &nbsp; that causes issue
3564
                                }
3565
                            } else {
3566
                                // switchable fill in the blanks
3567
                                if (in_array($user_tags[$i], $correct_tags)) {
3568
                                    $chosen_list[] = $user_tags[$i];
3569
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3570
                                    // gives the related weighting to the student
3571
                                    $questionScore += $answerWeighting[$i];
3572
                                    // increments total score
3573
                                    $totalScore += $answerWeighting[$i];
3574
                                    // adds the word in green at the end of the string
3575
                                    $answer .= $user_tags[$i];
3576
                                } elseif (!empty ($user_tags[$i])) {
3577
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3578
                                    // adds the word in red at the end of the string, and strikes it
3579
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3580
                                } else {
3581
                                    // adds a tabulation if no word has been typed by the student
3582
                                    $answer .= '';  // remove &nbsp; that causes issue
3583
                                }
3584
                            }
3585
3586
                            // adds the correct word, followed by ] to close the blank
3587
                            $answer .= ' / <font color="green"><b>' . $real_correct_tags[$i] . '</b></font>]';
3588
                            if (isset($real_text[$i +1])) {
3589
                                $answer .= $real_text[$i +1];
3590
                            }
3591
                        }
3592
                    } else {
3593
                        // insert the student result in the track_e_attempt table, field answer
3594
                        // $answer is the answer like in the c_quiz_answer table for the question
3595
                        // student data are choice[]
3596
                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
3597
                            $answer
3598
                        );
3599
3600
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
3601
                        $answerWeighting = $listCorrectAnswers['tabweighting'];
3602
                        // user choices is an array $choice
3603
3604
                        // get existing user data in n the BDD
3605
                        if ($from_database) {
3606
                            $listStudentResults = FillBlanks::getAnswerInfo(
3607
                                $answerFromDatabase,
3608
                                true
3609
                            );
3610
                            $choice = $listStudentResults['studentanswer'];
3611
                        }
3612
3613
                        // loop other all blanks words
3614
                        if (!$switchableAnswerSet) {
3615
                            // not switchable answer, must be in the same place than teacher order
3616
                            for ($i = 0; $i < count($listCorrectAnswers['tabwords']); $i++) {
3617
                                $studentAnswer = isset($choice[$i]) ? trim($choice[$i]) : '';
3618
                                $correctAnswer = $listCorrectAnswers['tabwords'][$i];
3619
3620
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3621
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3622
                                // ENT_QUOTES is used in order to transform ' to &#039;
3623
                                if (!$from_database) {
3624
                                    $studentAnswer = htmlentities(
3625
                                        api_utf8_encode($studentAnswer),
3626
                                        ENT_QUOTES
3627
                                    );
3628
                                    // fix apostrophe
3629
                                    $studentAnswer = str_replace('&#039;', '&#39;', $studentAnswer);
3630
                                }
3631
3632
                                $isAnswerCorrect = 0;
3633 View Code Duplication
                                if (FillBlanks::isGoodStudentAnswer($studentAnswer, $correctAnswer)) {
3634
                                    // gives the related weighting to the student
3635
                                    $questionScore += $answerWeighting[$i];
3636
                                    // increments total score
3637
                                    $totalScore += $answerWeighting[$i];
3638
                                    $isAnswerCorrect = 1;
3639
                                }
3640
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3641
                                $listCorrectAnswers['studentscore'][$i] = $isAnswerCorrect;
3642
                            }
3643
                        } else {
3644
                            // switchable answer
3645
                            $listStudentAnswerTemp = $choice;
3646
                            $listTeacherAnswerTemp = $listCorrectAnswers['tabwords'];
3647
                            // for every teacher answer, check if there is a student answer
3648
                            for ($i = 0; $i < count($listStudentAnswerTemp); $i++) {
3649
                                $studentAnswer = trim($listStudentAnswerTemp[$i]);
3650
                                $found = false;
3651
                                for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
3652
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3653
                                    if (!$found) {
3654 View Code Duplication
                                        if (FillBlanks::isGoodStudentAnswer(
3655
                                            $studentAnswer,
3656
                                            $correctAnswer
3657
                                        )
3658
                                        ) {
3659
                                            $questionScore += $answerWeighting[$i];
3660
                                            $totalScore += $answerWeighting[$i];
3661
                                            $listTeacherAnswerTemp[$j] = '';
3662
                                            $found = true;
3663
                                        }
3664
                                    }
3665
                                }
3666
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3667
                                if (!$found) {
3668
                                    $listCorrectAnswers['studentscore'][$i] = 0;
3669
                                } else {
3670
                                    $listCorrectAnswers['studentscore'][$i] = 1;
3671
                                }
3672
                            }
3673
                        }
3674
                        $answer = FillBlanks::getAnswerInStudentAttempt(
3675
                            $listCorrectAnswers
3676
                        );
3677
                    }
3678
                    break;
3679
                case CALCULATED_ANSWER:
3680
                    $answer = $objAnswerTmp->selectAnswer($_SESSION['calculatedAnswerId'][$questionId]);
3681
                    $preArray = explode('@@', $answer);
3682
                    $last = count($preArray) - 1;
3683
                    $answer = '';
3684
                    for ($k = 0; $k < $last; $k++) {
3685
                        $answer .= $preArray[$k];
3686
                    }
3687
                    $answerWeighting = array($answerWeighting);
3688
                    // we save the answer because it will be modified
3689
                    $temp = $answer;
3690
                    $answer = '';
3691
                    $j = 0;
3692
                    //initialise answer tags
3693
                    $userTags = $correctTags = $realText = array();
3694
                    // the loop will stop at the end of the text
3695
                    while (1) {
3696
                        // quits the loop if there are no more blanks (detect '[')
3697
                        if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
3698
                            // adds the end of the text
3699
                            $answer = $temp;
3700
                            $realText[] = $answer;
3701
                            break; //no more "blanks", quit the loop
3702
                        }
3703
                        // adds the piece of text that is before the blank
3704
                        //and ends with '[' into a general storage array
3705
                        $realText[] = api_substr($temp, 0, $pos +1);
3706
                        $answer .= api_substr($temp, 0, $pos +1);
3707
                        //take the string remaining (after the last "[" we found)
3708
                        $temp = api_substr($temp, $pos +1);
3709
                        // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3710
                        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 3708 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...
3711
                            // adds the end of the text
3712
                            $answer .= $temp;
3713
                            break;
3714
                        }
3715
                        if ($from_database) {
3716
                            $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3717
                                          WHERE
3718
                                            exe_id = '".$exeId."' AND
3719
                                            question_id= ".intval($questionId);
3720
                            $resfill = Database::query($queryfill);
3721
                            $str = Database::result($resfill, 0, 'answer');
3722
                            api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3723
                            $str = str_replace('\r\n', '', $str);
3724
                            $choice = $arr[1];
3725
                            if (isset($choice[$j])) {
3726
                                $tmp = api_strrpos($choice[$j], ' / ');
3727
3728
                                if ($tmp) {
3729
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3730
                                } else {
3731
                                    $tmp = ltrim($tmp, '[');
3732
                                    $tmp = rtrim($tmp, ']');
3733
                                }
3734
3735
                                $choice[$j] = trim($choice[$j]);
3736
                                // Needed to let characters ' and " to work as part of an answer
3737
                                $choice[$j] = stripslashes($choice[$j]);
3738
                            } else {
3739
                                $choice[$j] = null;
3740
                            }
3741
                        } else {
3742
                            // This value is the user input, not escaped while correct answer is escaped by fckeditor
3743
                            $choice[$j] = api_htmlentities(trim($choice[$j]));
3744
                        }
3745
                        $userTags[] = $choice[$j];
3746
                        //put the contents of the [] answer tag into correct_tags[]
3747
                        $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 3708 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...
3748
                        $j++;
3749
                        $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 3749 can also be of type false; however, api_substr() does only seem to accept string, did you maybe forget to handle an error condition?

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

Consider the follow example

<?php

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

    return false;
}

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

Loading history...
3750
                    }
3751
                    $answer = '';
3752
                    $realCorrectTags = $correctTags;
3753
                    for ($i = 0; $i < count($realCorrectTags); $i++) {
3754
                        if ($i == 0) {
3755
                            $answer .= $realText[0];
3756
                        }
3757
                        // Needed to parse ' and " characters
3758
                        $userTags[$i] = stripslashes($userTags[$i]);
3759 View Code Duplication
                        if ($correctTags[$i] == $userTags[$i]) {
3760
                            // gives the related weighting to the student
3761
                            $questionScore += $answerWeighting[$i];
3762
                            // increments total score
3763
                            $totalScore += $answerWeighting[$i];
3764
                            // adds the word in green at the end of the string
3765
                            $answer .= $correctTags[$i];
3766
                        } elseif (!empty($userTags[$i])) {
3767
                            // else if the word entered by the student IS NOT the same as the one defined by the professor
3768
                            // adds the word in red at the end of the string, and strikes it
3769
                            $answer .= '<font color="red"><s>' . $userTags[$i] . '</s></font>';
3770
                        } else {
3771
                            // adds a tabulation if no word has been typed by the student
3772
                            $answer .= ''; // remove &nbsp; that causes issue
3773
                        }
3774
                        // adds the correct word, followed by ] to close the blank
3775
3776
                        if (
3777
                            $this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM
3778
                        ) {
3779
                            $answer .= ' / <font color="green"><b>' . $realCorrectTags[$i] . '</b></font>';
3780
                        }
3781
3782
                        $answer .= ']';
3783
3784
                        if (isset($realText[$i +1])) {
3785
                            $answer .= $realText[$i +1];
3786
                        }
3787
                    }
3788
                    break;
3789
                case FREE_ANSWER:
3790
                    if ($from_database) {
3791
                        $sql  = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
3792
                                 WHERE 
3793
                                    exe_id = $exeId AND 
3794
                                    question_id= ".$questionId;
3795
                        $resq = Database::query($sql);
3796
                        $data = Database::fetch_array($resq);
3797
3798
                        $choice = $data['answer'];
3799
                        $choice = str_replace('\r\n', '', $choice);
3800
                        $choice = stripslashes($choice);
3801
                        $questionScore = $data['marks'];
3802
3803
                        if ($questionScore == -1) {
3804
                            $totalScore+= 0;
3805
                        } else {
3806
                            $totalScore+= $questionScore;
3807
                        }
3808
                        if ($questionScore == '') {
3809
                            $questionScore = 0;
3810
                        }
3811
                        $arrques = $questionName;
3812
                        $arrans  = $choice;
3813
                    } else {
3814
                        $studentChoice = $choice;
3815
                        if ($studentChoice) {
3816
                            //Fixing negative puntation see #2193
3817
                            $questionScore = 0;
3818
                            $totalScore += 0;
3819
                        }
3820
                    }
3821
                    break;
3822
                case ORAL_EXPRESSION:
3823
                    if ($from_database) {
3824
                        $query = "SELECT answer, marks 
3825
                                  FROM $TBL_TRACK_ATTEMPT
3826
                                  WHERE 
3827
                                        exe_id = $exeId AND 
3828
                                        question_id = $questionId
3829
                                 ";
3830
                        $resq = Database::query($query);
3831
                        $row = Database::fetch_assoc($resq);
3832
                        $choice = $row['answer'];
3833
                        $choice = str_replace('\r\n', '', $choice);
3834
                        $choice = stripslashes($choice);
3835
                        $questionScore = $row['marks'];
3836
                        if ($questionScore == -1) {
3837
                            $totalScore += 0;
3838
                        } else {
3839
                            $totalScore += $questionScore;
3840
                        }
3841
                        $arrques = $questionName;
3842
                        $arrans  = $choice;
3843
                    } else {
3844
                        $studentChoice = $choice;
3845
                        if ($studentChoice) {
3846
                            //Fixing negative puntation see #2193
3847
                            $questionScore = 0;
3848
                            $totalScore += 0;
3849
                        }
3850
                    }
3851
                    break;
3852
                case DRAGGABLE:
3853
                    //no break
3854
                case MATCHING_DRAGGABLE:
3855
                    //no break
3856
                case MATCHING:
3857
                    if ($from_database) {
3858
                        $sql = "SELECT id, answer, id_auto
3859
                                FROM $table_ans
3860
                                WHERE
3861
                                    c_id = $course_id AND
3862
                                    question_id = $questionId AND
3863
                                    correct = 0
3864
                                ";
3865
                        $res_answer = Database::query($sql);
3866
                        // Getting the real answer
3867
                        $real_list = array();
3868
                        while ($real_answer = Database::fetch_array($res_answer)) {
3869
                            $real_list[$real_answer['id_auto']] = $real_answer['answer'];
3870
                        }
3871
3872
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
3873
                                FROM $table_ans
3874
                                WHERE
3875
                                    c_id = $course_id AND
3876
                                    question_id = $questionId AND
3877
                                    correct <> 0
3878
                                ORDER BY id_auto";
3879
                        $res_answers = Database::query($sql);
3880
3881
                        $questionScore = 0;
3882
3883
                        while ($a_answers = Database::fetch_array($res_answers)) {
3884
                            $i_answer_id = $a_answers['id']; //3
3885
                            $s_answer_label = $a_answers['answer'];  // your daddy - your mother
3886
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
3887
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
3888
3889
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3890
                                    WHERE
3891
                                        exe_id = '$exeId' AND
3892
                                        question_id = '$questionId' AND
3893
                                        position = '$i_answer_id_auto'";
3894
3895
                            $res_user_answer = Database::query($sql);
3896
3897
                            if (Database::num_rows($res_user_answer) > 0) {
3898
                                //  rich - good looking
3899
                                $s_user_answer = Database::result($res_user_answer, 0, 0);
3900
                            } else {
3901
                                $s_user_answer = 0;
3902
                            }
3903
3904
                            $i_answerWeighting = $a_answers['ponderation'];
3905
3906
                            $user_answer = '';
3907
                            if (!empty($s_user_answer)) {
3908
                                if ($answerType == DRAGGABLE) {
3909
                                    if ($s_user_answer == $i_answer_correct_answer) {
3910
                                        $questionScore += $i_answerWeighting;
3911
                                        $totalScore += $i_answerWeighting;
3912
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
3913
                                    } else {
3914
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3915
                                    }
3916
                                } else {
3917
                                    if ($s_user_answer == $i_answer_correct_answer) {
3918
                                        $questionScore += $i_answerWeighting;
3919
                                        $totalScore += $i_answerWeighting;
3920
3921
                                        // Try with id
3922
                                        if (isset($real_list[$i_answer_id])) {
3923
                                            $user_answer = Display::span($real_list[$i_answer_id]);
3924
                                        }
3925
3926
                                        // Try with $i_answer_id_auto
3927
                                        if (empty($user_answer)) {
3928
                                            if (isset($real_list[$i_answer_id_auto])) {
3929
                                                $user_answer = Display::span(
3930
                                                    $real_list[$i_answer_id_auto]
3931
                                                );
3932
                                            }
3933
                                        }
3934
                                    } else {
3935
                                        $user_answer = Display::span(
3936
                                            $real_list[$s_user_answer],
3937
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
3938
                                        );
3939
                                    }
3940
                                }
3941
                            } elseif ($answerType == DRAGGABLE) {
3942
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3943
                            } else {
3944
                                $user_answer = Display::span(
3945
                                    get_lang('Incorrect').' &nbsp;',
3946
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
3947
                                );
3948
                            }
3949
3950
                            if ($show_result) {
3951
                                if ($showTotalScoreAndUserChoicesInLastAttempt === false) {
3952
                                    $user_answer = '';
3953
                                }
3954
                                echo '<tr>';
3955
                                echo '<td>' . $s_answer_label . '</td>';
3956
                                echo '<td>' . $user_answer;
3957
3958
                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
3959
                                    if (isset($real_list[$i_answer_correct_answer]) &&
3960
                                        $showTotalScoreAndUserChoicesInLastAttempt === true
3961
                                    ) {
3962
                                        echo Display::span(
3963
                                            $real_list[$i_answer_correct_answer],
3964
                                            ['style' => 'color: #008000; font-weight: bold;']
3965
                                        );
3966
                                    }
3967
                                }
3968
                                echo '</td>';
3969
                                echo '</tr>';
3970
                            }
3971
                        }
3972
                        break(2); // break the switch and the "for" condition
3973
                    } else {
3974
                        if ($answerCorrect) {
3975
                            if (isset($choice[$answerAutoId]) &&
3976
                                $answerCorrect == $choice[$answerAutoId]
3977
                            ) {
3978
                                $questionScore += $answerWeighting;
3979
                                $totalScore += $answerWeighting;
3980
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
3981
                            } else {
3982
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
3983
                                    $user_answer = Display::span(
3984
                                        $answerMatching[$choice[$answerAutoId]],
3985
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
3986
                                    );
3987
                                }
3988
                            }
3989
                            $matching[$answerAutoId] = $choice[$answerAutoId];
3990
                        }
3991
                    }
3992
                    break;
3993
                case HOT_SPOT:
3994
                    if ($from_database) {
3995
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
3996
                        // Check auto id
3997
                        $sql = "SELECT hotspot_correct
3998
                                FROM $TBL_TRACK_HOTSPOT
3999
                                WHERE
4000
                                    hotspot_exe_id = $exeId AND
4001
                                    hotspot_question_id= $questionId AND
4002
                                    hotspot_answer_id = ".intval($answerAutoId)."
4003
                                ORDER BY hotspot_id ASC";
4004
                        $result = Database::query($sql);
4005
                        if (Database::num_rows($result)) {
4006
                            $studentChoice = Database::result(
4007
                                $result,
4008
                                0,
4009
                                'hotspot_correct'
4010
                            );
4011
4012
                            if ($studentChoice) {
4013
                                $questionScore += $answerWeighting;
4014
                                $totalScore += $answerWeighting;
4015
                            }
4016
                        } else {
4017
                            // If answer.id is different:
4018
                            $sql = "SELECT hotspot_correct
4019
                                FROM $TBL_TRACK_HOTSPOT
4020
                                WHERE
4021
                                    hotspot_exe_id = $exeId AND
4022
                                    hotspot_question_id= $questionId AND
4023
                                    hotspot_answer_id = ".intval($answerId)."
4024
                                ORDER BY hotspot_id ASC";
4025
                            $result = Database::query($sql);
4026
4027
                            if (Database::num_rows($result)) {
4028
                                $studentChoice = Database::result(
4029
                                    $result,
4030
                                    0,
4031
                                    'hotspot_correct'
4032
                                );
4033
4034
                                if ($studentChoice) {
4035
                                    $questionScore += $answerWeighting;
4036
                                    $totalScore += $answerWeighting;
4037
                                }
4038
                            } else {
4039
                                // check answer.iid
4040
                                if (!empty($answerIid)) {
4041
                                    $sql = "SELECT hotspot_correct
4042
                                            FROM $TBL_TRACK_HOTSPOT
4043
                                            WHERE
4044
                                                hotspot_exe_id = $exeId AND
4045
                                                hotspot_question_id= $questionId AND
4046
                                                hotspot_answer_id = ".intval($answerIid)."
4047
                                            ORDER BY hotspot_id ASC";
4048
                                    $result = Database::query($sql);
4049
4050
                                    $studentChoice = Database::result(
4051
                                        $result,
4052
                                        0,
4053
                                        'hotspot_correct'
4054
                                    );
4055
4056
                                    if ($studentChoice) {
4057
                                        $questionScore += $answerWeighting;
4058
                                        $totalScore += $answerWeighting;
4059
                                    }
4060
                                }
4061
                            }
4062
                        }
4063
                    } else {
4064
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4065
                            $choice[$answerAutoId] = 0;
4066
                            $choice[$answerIid] = 0;
4067
                        } else {
4068
                            $studentChoice = $choice[$answerAutoId];
4069
                            if (empty($studentChoice)) {
4070
                                $studentChoice = $choice[$answerIid];
4071
                            }
4072
                            $choiceIsValid = false;
4073
                            if (!empty($studentChoice)) {
4074
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4075
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4076
                                $choicePoint = Geometry::decodePoint($studentChoice);
4077
4078
                                switch ($hotspotType) {
4079
                                    case 'square':
4080
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4081
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4082
                                        break;
4083
                                    case 'circle':
4084
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4085
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4086
                                        break;
4087
                                    case 'poly':
4088
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4089
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4090
                                        break;
4091
                                }
4092
                            }
4093
4094
                            $choice[$answerAutoId] = 0;
4095
                            if ($choiceIsValid) {
4096
                                $questionScore += $answerWeighting;
4097
                                $totalScore += $answerWeighting;
4098
                                $choice[$answerAutoId] = 1;
4099
                                $choice[$answerIid] = 1;
4100
                            }
4101
                        }
4102
                    }
4103
                    break;
4104
                // @todo never added to chamilo
4105
                //for hotspot with fixed order
4106
                case HOT_SPOT_ORDER:
4107
                    $studentChoice = $choice['order'][$answerId];
4108
                    if ($studentChoice == $answerId) {
4109
                        $questionScore  += $answerWeighting;
4110
                        $totalScore     += $answerWeighting;
4111
                        $studentChoice = true;
4112
                    } else {
4113
                        $studentChoice = false;
4114
                    }
4115
                    break;
4116
                // for hotspot with delineation
4117
                case HOT_SPOT_DELINEATION:
4118
                    if ($from_database) {
4119
                        // getting the user answer
4120
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4121
                        $query   = "SELECT hotspot_correct, hotspot_coordinate
4122
                                    FROM $TBL_TRACK_HOTSPOT
4123
                                    WHERE
4124
                                        hotspot_exe_id = '".$exeId."' AND
4125
                                        hotspot_question_id= '".$questionId."' AND
4126
                                        hotspot_answer_id='1'";
4127
                        //by default we take 1 because it's a delineation
4128
                        $resq = Database::query($query);
4129
                        $row = Database::fetch_array($resq,'ASSOC');
4130
4131
                        $choice = $row['hotspot_correct'];
4132
                        $user_answer = $row['hotspot_coordinate'];
4133
4134
                        // THIS is very important otherwise the poly_compile will throw an error!!
4135
                        // round-up the coordinates
4136
                        $coords = explode('/',$user_answer);
4137
                        $user_array = '';
4138 View Code Duplication
                        foreach ($coords as $coord) {
4139
                            list($x,$y) = explode(';',$coord);
4140
                            $user_array .= round($x).';'.round($y).'/';
4141
                        }
4142
                        $user_array = substr($user_array,0,-1);
4143
                    } else {
4144
                        if (!empty($studentChoice)) {
4145
                            $newquestionList[] = $questionId;
4146
                        }
4147
4148
                        if ($answerId === 1) {
4149
                            $studentChoice = $choice[$answerId];
4150
                            $questionScore += $answerWeighting;
4151
4152
                            if ($hotspot_delineation_result[1] == 1) {
4153
                                $totalScore += $answerWeighting; //adding the total
4154
                            }
4155
                        }
4156
                    }
4157
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4158
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4159
                    break;
4160
                case ANNOTATION:
4161
                    if ($from_database) {
4162
                        $sql  = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4163
                                 WHERE 
4164
                                    exe_id = $exeId AND 
4165
                                    question_id= ".$questionId;
4166
                        $resq = Database::query($sql);
4167
                        $data = Database::fetch_array($resq);
4168
4169
                        $questionScore = empty($data['marks']) ? 0 : $data['marks'];
4170
                        $totalScore += $questionScore == -1 ? 0 : $questionScore;
4171
4172
                        $arrques = $questionName;
4173
                        break;
4174
                    }
4175
4176
                    $studentChoice = $choice;
4177
4178
                    if ($studentChoice) {
4179
                        $questionScore = 0;
4180
                        $totalScore += 0;
4181
                    }
4182
                    break;
4183
            } // end switch Answertype
4184
4185
            if ($show_result) {
4186
                if ($debug) error_log('Showing questions $from '.$from);
4187
                if ($from == 'exercise_result') {
4188
                    //display answers (if not matching type, or if the answer is correct)
4189
                    if (
4190
                        !in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4191
                        $answerCorrect
4192
                    ) {
4193
                        if (
4194
                            in_array(
4195
                                $answerType,
4196
                                array(
4197
                                    UNIQUE_ANSWER,
4198
                                    UNIQUE_ANSWER_IMAGE,
4199
                                    UNIQUE_ANSWER_NO_OPTION,
4200
                                    MULTIPLE_ANSWER,
4201
                                    MULTIPLE_ANSWER_COMBINATION,
4202
                                    GLOBAL_MULTIPLE_ANSWER
4203
                                )
4204
                            )
4205
                        ) {
4206
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4207
                                $feedback_type,
4208
                                $answerType,
4209
                                $studentChoice,
4210
                                $answer,
4211
                                $answerComment,
4212
                                $answerCorrect,
4213
                                0,
4214
                                0,
4215
                                0,
4216
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type integer; however, ExerciseShowFunctions::d...ue_or_multiple_answer() does only seem to accept boolean, 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...
4217
                                $showTotalScoreAndUserChoicesInLastAttempt
4218
                            );
4219
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4220
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4221
                                $feedback_type,
4222
                                $answerType,
4223
                                $studentChoice,
4224
                                $answer,
4225
                                $answerComment,
4226
                                $answerCorrect,
4227
                                0,
4228
                                $questionId,
4229
                                0,
4230
                                $results_disabled,
4231
                                $showTotalScoreAndUserChoicesInLastAttempt
4232
                            );
4233
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ) {
4234
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4235
                                $feedback_type,
4236
                                $answerType,
4237
                                $studentChoice,
4238
                                $answer,
4239
                                $answerComment,
4240
                                $answerCorrect,
4241
                                0,
4242
                                0,
4243
                                0,
4244
                                $results_disabled,
4245
                                $showTotalScoreAndUserChoicesInLastAttempt
4246
                            );
4247
                        } elseif ($answerType == FILL_IN_BLANKS) {
4248
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4249
                                $feedback_type,
4250
                                $answer,
4251
                                0,
4252
                                0,
4253
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 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...
4254
                                '',
4255
                                $showTotalScoreAndUserChoicesInLastAttempt
4256
                            );
4257
                        } elseif ($answerType == CALCULATED_ANSWER) {
4258
                            ExerciseShowFunctions::display_calculated_answer(
4259
                                $feedback_type,
4260
                                $answer,
4261
                                0,
4262
                                0,
4263
                                $results_disabled,
4264
                                $showTotalScoreAndUserChoicesInLastAttempt
4265
                            );
4266
                        } elseif ($answerType == FREE_ANSWER) {
4267
                            ExerciseShowFunctions::display_free_answer(
4268
                                $feedback_type,
4269
                                $choice,
4270
                                $exeId,
4271
                                $questionId,
4272
                                $questionScore,
4273
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type boolean; however, ExerciseShowFunctions::display_free_answer() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
4274
                            );
4275
                        } elseif ($answerType == ORAL_EXPRESSION) {
4276
                            // to store the details of open questions in an array to be used in mail
4277
                            /** @var OralExpression $objQuestionTmp */
4278
                            ExerciseShowFunctions::display_oral_expression_answer(
4279
                                $feedback_type,
4280
                                $choice,
4281
                                0,
4282
                                0,
4283
                                $objQuestionTmp->getFileUrl(true),
4284
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type boolean; however, ExerciseShowFunctions::d...ral_expression_answer() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
4285
                            );
4286
                        } elseif ($answerType == HOT_SPOT) {
4287
                            /**
4288
                             * @var int $correctAnswerId
4289
                             * @var TrackEHotspot $hotspot
4290
                             */
4291
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4292
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4293
                                    break;
4294
                                }
4295
                            }
4296
4297
                            ExerciseShowFunctions::display_hotspot_answer(
4298
                                $feedback_type,
4299
                                ++$correctAnswerId,
4300
                                $answer,
4301
                                $studentChoice,
4302
                                $answerComment,
4303
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type boolean; however, ExerciseShowFunctions::display_hotspot_answer() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
4304
                                $correctAnswerId,
4305
                                $showTotalScoreAndUserChoicesInLastAttempt
4306
                            );
4307
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4308
                            ExerciseShowFunctions::display_hotspot_order_answer(
4309
                                $feedback_type,
4310
                                $answerId,
4311
                                $answer,
4312
                                $studentChoice,
4313
                                $answerComment
4314
                            );
4315
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4316
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4317
4318
                            //round-up the coordinates
4319
                            $coords = explode('/',$user_answer);
4320
                            $user_array = '';
4321 View Code Duplication
                            foreach ($coords as $coord) {
4322
                                list($x,$y) = explode(';',$coord);
4323
                                $user_array .= round($x).';'.round($y).'/';
4324
                            }
4325
                            $user_array = substr($user_array,0,-1);
4326
4327 View Code Duplication
                            if ($next) {
4328
                                $user_answer = $user_array;
4329
                                // we compare only the delineation not the other points
4330
                                $answer_question = $_SESSION['hotspot_coord'][1];
4331
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4332
4333
                                //calculating the area
4334
                                $poly_user = convert_coordinates($user_answer, '/');
4335
                                $poly_answer = convert_coordinates($answer_question, '|');
4336
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4337
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4338
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4339
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4340
4341
                                $overlap = $poly_results['both'];
4342
                                $poly_answer_area = $poly_results['s1'];
4343
                                $poly_user_area = $poly_results['s2'];
4344
                                $missing = $poly_results['s1Only'];
4345
                                $excess = $poly_results['s2Only'];
4346
4347
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4348
                                // //this is an area in pixels
4349
                                if ($debug > 0) {
4350
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4351
                                }
4352
4353
                                if ($overlap < 1) {
4354
                                    //shortcut to avoid complicated calculations
4355
                                    $final_overlap = 0;
4356
                                    $final_missing = 100;
4357
                                    $final_excess = 100;
4358
                                } else {
4359
                                    // the final overlap is the percentage of the initial polygon
4360
                                    // that is overlapped by the user's polygon
4361
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4362
                                    if ($debug > 1) {
4363
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4364
                                    }
4365
                                    // the final missing area is the percentage of the initial polygon
4366
                                    // that is not overlapped by the user's polygon
4367
                                    $final_missing = 100 - $final_overlap;
4368
                                    if ($debug > 1) {
4369
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4370
                                    }
4371
                                    // the final excess area is the percentage of the initial polygon's size
4372
                                    // that is covered by the user's polygon outside of the initial polygon
4373
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4374
                                    if ($debug > 1) {
4375
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4376
                                    }
4377
                                }
4378
4379
                                //checking the destination parameters parsing the "@@"
4380
                                $destination_items = explode(
4381
                                    '@@',
4382
                                    $answerDestination
4383
                                );
4384
                                $threadhold_total = $destination_items[0];
4385
                                $threadhold_items = explode(
4386
                                    ';',
4387
                                    $threadhold_total
4388
                                );
4389
                                $threadhold1 = $threadhold_items[0]; // overlap
4390
                                $threadhold2 = $threadhold_items[1]; // excess
4391
                                $threadhold3 = $threadhold_items[2];     //missing
4392
4393
                                // if is delineation
4394
                                if ($answerId === 1) {
4395
                                    //setting colors
4396
                                    if ($final_overlap >= $threadhold1) {
4397
                                        $overlap_color = true; //echo 'a';
4398
                                    }
4399
                                    //echo $excess.'-'.$threadhold2;
4400
                                    if ($final_excess <= $threadhold2) {
4401
                                        $excess_color = true; //echo 'b';
4402
                                    }
4403
                                    //echo '--------'.$missing.'-'.$threadhold3;
4404
                                    if ($final_missing <= $threadhold3) {
4405
                                        $missing_color = true; //echo 'c';
4406
                                    }
4407
4408
                                    // if pass
4409
                                    if (
4410
                                        $final_overlap >= $threadhold1 &&
4411
                                        $final_missing <= $threadhold3 &&
4412
                                        $final_excess <= $threadhold2
4413
                                    ) {
4414
                                        $next=1; //go to the oars
4415
                                        $result_comment=get_lang('Acceptable');
4416
                                        $final_answer = 1;	// do not update with  update_exercise_attempt
4417
                                    } else {
4418
                                        $next=0;
4419
                                        $result_comment=get_lang('Unacceptable');
4420
                                        $comment=$answerDestination=$objAnswerTmp->selectComment(1);
4421
                                        $answerDestination=$objAnswerTmp->selectDestination(1);
4422
                                        //checking the destination parameters parsing the "@@"
4423
                                        $destination_items= explode('@@', $answerDestination);
4424
                                    }
4425
                                } elseif($answerId>1) {
4426
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4427
                                        if ($debug>0) {
4428
                                            error_log(__LINE__.' - answerId is of type noerror',0);
4429
                                        }
4430
                                        //type no error shouldn't be treated
4431
                                        $next = 1;
4432
                                        continue;
4433
                                    }
4434
                                    if ($debug>0) {
4435
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR',0);
4436
                                    }
4437
                                    //check the intersection between the oar and the user
4438
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4439
                                    //echo 'official';print_r($x_list);print_r($y_list);
4440
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4441
                                    $inter = $result['success'];
4442
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4443
                                    $poly_answer = convert_coordinates($delineation_cord,'|');
4444
                                    $max_coord = poly_get_max($poly_user,$poly_answer);
4445
                                    $poly_answer_compiled = poly_compile($poly_answer,$max_coord);
4446
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4447
4448
                                    if ($overlap == false) {
4449
                                        //all good, no overlap
4450
                                        $next = 1;
4451
                                        continue;
4452
                                    } else {
4453
                                        if ($debug>0) {
4454
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit',0);
4455
                                        }
4456
                                        $organs_at_risk_hit++;
4457
                                        //show the feedback
4458
                                        $next = 0;
4459
                                        $comment = $answerDestination=$objAnswerTmp->selectComment($answerId);
4460
                                        $answerDestination=$objAnswerTmp->selectDestination($answerId);
4461
4462
                                        $destination_items= explode('@@', $answerDestination);
4463
                                        $try_hotspot = $destination_items[1];
4464
                                        $lp_hotspot = $destination_items[2];
4465
                                        $select_question_hotspot = $destination_items[3];
4466
                                        $url_hotspot = $destination_items[4];
4467
                                    }
4468
                                }
4469
                            } else {	// the first delineation feedback
4470
                                if ($debug>0) {
4471
                                    error_log(__LINE__.' first',0);
4472
                                }
4473
                            }
4474
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4475
                            echo '<tr>';
4476
                            echo Display::tag('td', $answerMatching[$answerId]);
4477
                            echo Display::tag(
4478
                                'td',
4479
                                "$user_answer / " . Display::tag(
4480
                                    'strong',
4481
                                    $answerMatching[$answerCorrect],
4482
                                    ['style' => 'color: #008000; font-weight: bold;']
4483
                                )
4484
                            );
4485
                            echo '</tr>';
4486
                        } else if ($answerType == ANNOTATION) {
4487
                            ExerciseShowFunctions::displayAnnotationAnswer(
4488
                                $feedback_type,
4489
                                $exeId,
4490
                                $questionId,
4491
                                $questionScore,
4492
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type boolean; however, ExerciseShowFunctions::displayAnnotationAnswer() 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...
4493
                            );
4494
                        }
4495
                    }
4496
                } else {
4497
                    if ($debug) error_log('Showing questions $from '.$from);
4498
4499
                    switch ($answerType) {
4500
                        case UNIQUE_ANSWER:
4501
                        case UNIQUE_ANSWER_IMAGE:
4502
                        case UNIQUE_ANSWER_NO_OPTION:
4503
                        case MULTIPLE_ANSWER:
4504
                        case GLOBAL_MULTIPLE_ANSWER :
4505 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION:
4506
                            if ($answerId == 1) {
4507
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4508
                                    $feedback_type,
4509
                                    $answerType,
4510
                                    $studentChoice,
4511
                                    $answer,
4512
                                    $answerComment,
4513
                                    $answerCorrect,
4514
                                    $exeId,
4515
                                    $questionId,
4516
                                    $answerId,
4517
                                    $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type integer; however, ExerciseShowFunctions::d...ue_or_multiple_answer() does only seem to accept boolean, 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...
4518
                                    $showTotalScoreAndUserChoicesInLastAttempt
4519
                                );
4520
                            } else {
4521
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4522
                                    $feedback_type,
4523
                                    $answerType,
4524
                                    $studentChoice,
4525
                                    $answer,
4526
                                    $answerComment,
4527
                                    $answerCorrect,
4528
                                    $exeId,
4529
                                    $questionId,
4530
                                    '',
4531
                                    $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type integer; however, ExerciseShowFunctions::d...ue_or_multiple_answer() does only seem to accept boolean, 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...
4532
                                    $showTotalScoreAndUserChoicesInLastAttempt
4533
                                );
4534
                            }
4535
                            break;
4536 View Code Duplication
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4537
                            if ($answerId == 1) {
4538
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4539
                                    $feedback_type,
4540
                                    $answerType,
4541
                                    $studentChoice,
4542
                                    $answer,
4543
                                    $answerComment,
4544
                                    $answerCorrect,
4545
                                    $exeId,
4546
                                    $questionId,
4547
                                    $answerId,
4548
                                    $results_disabled,
4549
                                    $showTotalScoreAndUserChoicesInLastAttempt
4550
                                );
4551
                            } else {
4552
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4553
                                    $feedback_type,
4554
                                    $answerType,
4555
                                    $studentChoice,
4556
                                    $answer,
4557
                                    $answerComment,
4558
                                    $answerCorrect,
4559
                                    $exeId,
4560
                                    $questionId,
4561
                                    '',
4562
                                    $results_disabled,
4563
                                    $showTotalScoreAndUserChoicesInLastAttempt
4564
                                );
4565
                            }
4566
                            break;
4567 View Code Duplication
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4568
                            if ($answerId == 1) {
4569
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4570
                                    $feedback_type,
4571
                                    $answerType,
4572
                                    $studentChoice,
4573
                                    $answer,
4574
                                    $answerComment,
4575
                                    $answerCorrect,
4576
                                    $exeId,
4577
                                    $questionId,
4578
                                    $answerId,
4579
                                    $results_disabled,
4580
                                    $showTotalScoreAndUserChoicesInLastAttempt
4581
                                );
4582
                            } else {
4583
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4584
                                    $feedback_type,
4585
                                    $answerType,
4586
                                    $studentChoice,
4587
                                    $answer,
4588
                                    $answerComment,
4589
                                    $answerCorrect,
4590
                                    $exeId,
4591
                                    $questionId,
4592
                                    '',
4593
                                    $results_disabled,
4594
                                    $showTotalScoreAndUserChoicesInLastAttempt
4595
                                );
4596
                            }
4597
                            break;
4598
                        case FILL_IN_BLANKS:
4599
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4600
                                $feedback_type,
4601
                                $answer,
4602
                                $exeId,
4603
                                $questionId,
4604
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type boolean; however, ExerciseShowFunctions::d...fill_in_blanks_answer() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
4605
                                $str,
4606
                                $showTotalScoreAndUserChoicesInLastAttempt
4607
                            );
4608
                            break;
4609
                        case CALCULATED_ANSWER:
4610
                            ExerciseShowFunctions::display_calculated_answer(
4611
                                $feedback_type,
4612
                                $answer,
4613
                                $exeId,
4614
                                $questionId,
4615
                                $results_disabled,
4616
                                '',
4617
                                $showTotalScoreAndUserChoicesInLastAttempt
4618
                            );
4619
                            break;
4620
                        case FREE_ANSWER:
4621
                            echo ExerciseShowFunctions::display_free_answer(
4622
                                $feedback_type,
4623
                                $choice,
4624
                                $exeId,
4625
                                $questionId,
4626
                                $questionScore,
4627
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type boolean; however, ExerciseShowFunctions::display_free_answer() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
4628
                            );
4629
                            break;
4630
                        case ORAL_EXPRESSION:
4631
                            echo '<tr>
4632
                                <td valign="top">' . ExerciseShowFunctions::display_oral_expression_answer(
4633
                                    $feedback_type,
4634
                                    $choice,
4635
                                    $exeId,
4636
                                    $questionId,
4637
                                    $objQuestionTmp->getFileUrl(),
4638
                                    $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type boolean; however, ExerciseShowFunctions::d...ral_expression_answer() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
4639
                                ) . '</td>
4640
                                </tr>
4641
                                </table>';
4642
                            break;
4643
                        case HOT_SPOT:
4644
                            ExerciseShowFunctions::display_hotspot_answer(
4645
                                $feedback_type,
4646
                                $answerId,
4647
                                $answer,
4648
                                $studentChoice,
4649
                                $answerComment,
4650
                                $results_disabled,
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type boolean; however, ExerciseShowFunctions::display_hotspot_answer() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
4651
                                $answerId,
4652
                                $showTotalScoreAndUserChoicesInLastAttempt
4653
                            );
4654
                            break;
4655
                        case HOT_SPOT_DELINEATION:
4656
                            $user_answer = $user_array;
4657 View Code Duplication
                            if ($next) {
4658
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4659
                                // Save into db
4660
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
4661
                                 * hotspot_user_id,
4662
                                 *  hotspot_course_code,
4663
                                 *  hotspot_exe_id,
4664
                                 *  hotspot_question_id,
4665
                                 *  hotspot_answer_id,
4666
                                 *  hotspot_correct,
4667
                                 *  hotspot_coordinate
4668
                                 *  )
4669
                                VALUES (
4670
                                 * '".Database::escape_string($_user['user_id'])."',
4671
                                 *  '".Database::escape_string($_course['id'])."',
4672
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
4673
                                 *  '".Database::escape_string($answerId)."',
4674
                                 *  '".Database::escape_string($studentChoice)."',
4675
                                 *  '".Database::escape_string($user_array)."')";
4676
                                $result = Database::query($sql,__FILE__,__LINE__);
4677
                                 */
4678
                                $user_answer = $user_array;
4679
                                // we compare only the delineation not the other points
4680
                                $answer_question = $_SESSION['hotspot_coord'][1];
4681
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4682
4683
                                // calculating the area
4684
                                $poly_user = convert_coordinates($user_answer, '/');
4685
                                $poly_answer = convert_coordinates($answer_question, '|');
4686
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4687
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4688
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4689
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4690
4691
                                $overlap = $poly_results['both'];
4692
                                $poly_answer_area = $poly_results['s1'];
4693
                                $poly_user_area = $poly_results['s2'];
4694
                                $missing = $poly_results['s1Only'];
4695
                                $excess = $poly_results['s2Only'];
4696
                                if ($debug > 0) {
4697
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4698
                                }
4699
                                if ($overlap < 1) {
4700
                                    //shortcut to avoid complicated calculations
4701
                                    $final_overlap = 0;
4702
                                    $final_missing = 100;
4703
                                    $final_excess = 100;
4704
                                } else {
4705
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
4706
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4707
                                    if ($debug > 1) {
4708
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4709
                                    }
4710
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
4711
                                    $final_missing = 100 - $final_overlap;
4712
                                    if ($debug > 1) {
4713
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4714
                                    }
4715
                                    // 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
4716
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4717
                                    if ($debug > 1) {
4718
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4719
                                    }
4720
                                }
4721
4722
                                // Checking the destination parameters parsing the "@@"
4723
                                $destination_items = explode('@@', $answerDestination);
4724
                                $threadhold_total = $destination_items[0];
4725
                                $threadhold_items = explode(';', $threadhold_total);
4726
                                $threadhold1 = $threadhold_items[0]; // overlap
4727
                                $threadhold2 = $threadhold_items[1]; // excess
4728
                                $threadhold3 = $threadhold_items[2];  //missing
4729
                                // if is delineation
4730
                                if ($answerId === 1) {
4731
                                    //setting colors
4732
                                    if ($final_overlap >= $threadhold1) {
4733
                                        $overlap_color = true; //echo 'a';
4734
                                    }
4735
                                    //echo $excess.'-'.$threadhold2;
4736
                                    if ($final_excess <= $threadhold2) {
4737
                                        $excess_color = true; //echo 'b';
4738
                                    }
4739
                                    //echo '--------'.$missing.'-'.$threadhold3;
4740
                                    if ($final_missing <= $threadhold3) {
4741
                                        $missing_color = true; //echo 'c';
4742
                                    }
4743
4744
                                    // if pass
4745
                                    if ($final_overlap >= $threadhold1 &&
4746
                                        $final_missing <= $threadhold3 &&
4747
                                        $final_excess <= $threadhold2
4748
                                    ) {
4749
                                        $next = 1; //go to the oars
4750
                                        $result_comment = get_lang('Acceptable');
4751
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4752
                                    } else {
4753
                                        $next = 0;
4754
                                        $result_comment = get_lang('Unacceptable');
4755
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4756
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4757
                                        //checking the destination parameters parsing the "@@"
4758
                                        $destination_items = explode('@@', $answerDestination);
4759
                                    }
4760
                                } elseif ($answerId > 1) {
4761
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4762
                                        if ($debug > 0) {
4763
                                            error_log(__LINE__ . ' - answerId is of type noerror', 0);
4764
                                        }
4765
                                        //type no error shouldn't be treated
4766
                                        $next = 1;
4767
                                        continue;
4768
                                    }
4769
                                    if ($debug > 0) {
4770
                                        error_log(__LINE__ . ' - answerId is >1 so we\'re probably in OAR', 0);
4771
                                    }
4772
                                    //check the intersection between the oar and the user
4773
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4774
                                    //echo 'official';print_r($x_list);print_r($y_list);
4775
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4776
                                    $inter = $result['success'];
4777
4778
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4779
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4780
4781
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4782
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4783
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4784
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4785
4786
                                    if ($overlap == false) {
4787
                                        //all good, no overlap
4788
                                        $next = 1;
4789
                                        continue;
4790
                                    } else {
4791
                                        if ($debug > 0) {
4792
                                            error_log(__LINE__ . ' - Overlap is ' . $overlap . ': OAR hit', 0);
4793
                                        }
4794
                                        $organs_at_risk_hit++;
4795
                                        //show the feedback
4796
                                        $next = 0;
4797
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4798
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4799
4800
                                        $destination_items = explode('@@', $answerDestination);
4801
                                        $try_hotspot = $destination_items[1];
4802
                                        $lp_hotspot = $destination_items[2];
4803
                                        $select_question_hotspot = $destination_items[3];
4804
                                        $url_hotspot=$destination_items[4];
4805
                                    }
4806
                                }
4807
                            } else {	// the first delineation feedback
4808
                                if ($debug > 0) {
4809
                                    error_log(__LINE__ . ' first', 0);
4810
                                }
4811
                            }
4812
                            break;
4813
                        case HOT_SPOT_ORDER:
4814
                            ExerciseShowFunctions::display_hotspot_order_answer(
4815
                                $feedback_type,
4816
                                $answerId,
4817
                                $answer,
4818
                                $studentChoice,
4819
                                $answerComment
4820
                            );
4821
                            break;
4822
                        case DRAGGABLE:
4823
                            //no break
4824
                        case MATCHING_DRAGGABLE:
4825
                            //no break
4826
                        case MATCHING:
4827
                            echo '<tr>';
4828
                            echo Display::tag('td', $answerMatching[$answerId]);
4829
                            echo Display::tag(
4830
                                'td',
4831
                                "$user_answer / " . Display::tag(
4832
                                    'strong',
4833
                                    $answerMatching[$answerCorrect],
4834
                                    ['style' => 'color: #008000; font-weight: bold;']
4835
                                )
4836
                            );
4837
                            echo '</tr>';
4838
4839
                            break;
4840
                        case ANNOTATION:
4841
                            ExerciseShowFunctions::displayAnnotationAnswer(
4842
                                $feedback_type,
4843
                                $exeId,
4844
                                $questionId,
4845
                                $questionScore,
4846
                                $results_disabled
0 ignored issues
show
Bug introduced by
It seems like $results_disabled defined by $this->selectResultsDisabled() on line 3103 can also be of type boolean; however, ExerciseShowFunctions::displayAnnotationAnswer() 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...
4847
                            );
4848
                            break;
4849
                    }
4850
                }
4851
            }
4852
            if ($debug) error_log(' ------ ');
4853
        } // end for that loops over all answers of the current question
4854
4855
        if ($debug) error_log('-- end answer loop --');
4856
4857
        $final_answer = true;
4858
4859
        foreach ($real_answers as $my_answer) {
4860
            if (!$my_answer) {
4861
                $final_answer = false;
4862
            }
4863
        }
4864
4865
        //we add the total score after dealing with the answers
4866
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
4867
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
4868
        ) {
4869
            if ($final_answer) {
4870
                //getting only the first score where we save the weight of all the question
4871
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
4872
                $questionScore += $answerWeighting;
4873
                $totalScore += $answerWeighting;
4874
            }
4875
        }
4876
4877
        //Fixes multiple answer question in order to be exact
4878
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
4879
       /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
4880
            $diff = @array_diff($answer_correct_array, $real_answers);
4881
4882
            // All good answers or nothing works like exact
4883
4884
            $counter = 1;
4885
            $correct_answer = true;
4886
            foreach ($real_answers as $my_answer) {
4887
                if ($debug)
4888
                    error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
4889
                if ($my_answer != $answer_correct_array[$counter]) {
4890
                    $correct_answer = false;
4891
                    break;
4892
                }
4893
                $counter++;
4894
            }
4895
4896
            if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
4897
            if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
4898
            if ($debug) error_log(" correct_answer: ".$correct_answer);
4899
4900
            if ($correct_answer == false) {
4901
                $questionScore = 0;
4902
            }
4903
4904
            // This makes the result non exact
4905
            if (!empty($diff)) {
4906
                $questionScore = 0;
4907
            }
4908
        }*/
4909
4910
        $extra_data = array(
4911
            'final_overlap' => $final_overlap,
4912
            'final_missing' => $final_missing,
4913
            'final_excess' => $final_excess,
4914
            'overlap_color' => $overlap_color,
4915
            'missing_color' => $missing_color,
4916
            'excess_color' => $excess_color,
4917
            'threadhold1' => $threadhold1,
4918
            'threadhold2' => $threadhold2,
4919
            'threadhold3' => $threadhold3,
4920
        );
4921
        if ($from == 'exercise_result') {
4922
            // if answer is hotspot. To the difference of exercise_show.php,
4923
            //  we use the results from the session (from_db=0)
4924
            // TODO Change this, because it is wrong to show the user
4925
            //  some results that haven't been stored in the database yet
4926
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION ) {
4927
                if ($debug) error_log('$from AND this is a hotspot kind of question ');
4928
                $my_exe_id = 0;
4929
                $from_database = 0;
4930
                if ($answerType == HOT_SPOT_DELINEATION) {
4931
                    if (0) {
4932
                        if ($overlap_color) {
4933
                            $overlap_color='green';
4934
                        } else {
4935
                            $overlap_color='red';
4936
                        }
4937
                        if ($missing_color) {
4938
                            $missing_color='green';
4939
                        } else {
4940
                            $missing_color='red';
4941
                        }
4942
                        if ($excess_color) {
4943
                            $excess_color='green';
4944
                        } else {
4945
                            $excess_color='red';
4946
                        }
4947
                        if (!is_numeric($final_overlap)) {
4948
                            $final_overlap = 0;
4949
                        }
4950
                        if (!is_numeric($final_missing)) {
4951
                            $final_missing = 0;
4952
                        }
4953
                        if (!is_numeric($final_excess)) {
4954
                            $final_excess = 0;
4955
                        }
4956
4957
                        if ($final_overlap>100) {
4958
                            $final_overlap = 100;
4959
                        }
4960
4961
                        $table_resume='<table class="data_table">
4962
                                <tr class="row_odd" >
4963
                                    <td></td>
4964
                                    <td ><b>' . get_lang('Requirements') . '</b></td>
4965
                                    <td><b>' . get_lang('YourAnswer') . '</b></td>
4966
                                </tr>
4967
                                <tr class="row_even">
4968
                                    <td><b>' . get_lang('Overlap') . '</b></td>
4969
                                    <td>' . get_lang('Min') . ' ' . $threadhold1 . '</td>
4970
                                    <td><div style="color:' . $overlap_color . '">'
4971
                                        . (($final_overlap < 0) ? 0 : intval($final_overlap)) . '</div></td>
4972
                                </tr>
4973
                                <tr>
4974
                                    <td><b>' . get_lang('Excess') . '</b></td>
4975
                                    <td>' . get_lang('Max') . ' ' . $threadhold2 . '</td>
4976
                                    <td><div style="color:' . $excess_color . '">'
4977
                                        . (($final_excess < 0) ? 0 : intval($final_excess)) . '</div></td>
4978
                                </tr>
4979
                                <tr class="row_even">
4980
                                    <td><b>' . get_lang('Missing') . '</b></td>
4981
                                    <td>' . get_lang('Max') . ' ' . $threadhold3 . '</td>
4982
                                    <td><div style="color:' . $missing_color . '">'
4983
                                        . (($final_missing < 0) ? 0 : intval($final_missing)) . '</div></td>
4984
                                </tr>
4985
                            </table>';
4986 View Code Duplication
                        if ($next == 0) {
4987
                            $try = $try_hotspot;
4988
                            $lp = $lp_hotspot;
4989
                            $destinationid = $select_question_hotspot;
4990
                            $url = $url_hotspot;
4991
                        } else {
4992
                            //show if no error
4993
                            //echo 'no error';
4994
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
4995
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
4996
                        }
4997
4998
                        echo '<h1><div style="color:#333;">' . get_lang('Feedback') . '</div></h1>
4999
                            <p style="text-align:center">';
5000
5001
                        $message = '<p>' . get_lang('YourDelineation') . '</p>';
5002
                        $message .= $table_resume;
5003
                        $message .= '<br />' . get_lang('ResultIs') . ' ' . $result_comment . '<br />';
5004
                        if ($organs_at_risk_hit > 0) {
5005
                            $message .= '<p><b>' . get_lang('OARHit') . '</b></p>';
5006
                        }
5007
                        $message .='<p>' . $comment . '</p>';
5008
                        echo $message;
5009
                    } else {
5010
                        echo $hotspot_delineation_result[0]; //prints message
5011
                        $from_database = 1;  // the hotspot_solution.swf needs this variable
5012
                    }
5013
5014
                    //save the score attempts
5015
                    if (1) {
5016
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5017
                        $final_answer = $hotspot_delineation_result[1];
5018
                        if ($final_answer == 0) {
5019
                            $questionScore = 0;
5020
                        }
5021
                        // we always insert the answer_id 1 = delineation
5022
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5023
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5024
                        $hotspotValue = (int) $hotspot_delineation_result[1] === 1 ? 1 : 0;
5025
                        Event::saveExerciseAttemptHotspot(
5026
                            $exeId,
5027
                            $quesId,
5028
                            1,
5029
                            $hotspotValue,
5030
                            $exerciseResultCoordinates[$quesId]
5031
                        );
5032
                    } else {
5033
                        if ($final_answer==0) {
5034
                            $questionScore = 0;
5035
                            $answer=0;
5036
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5037
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5038
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
5039
                                    Event::saveExerciseAttemptHotspot(
5040
                                        $exeId,
5041
                                        $quesId,
5042
                                        $idx,
5043
                                        0,
5044
                                        $val
5045
                                    );
5046
                                }
5047
                            }
5048
                        } else {
5049
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5050
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5051
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
5052
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5053
                                    Event::saveExerciseAttemptHotspot(
5054
                                        $exeId,
5055
                                        $quesId,
5056
                                        $idx,
5057
                                        $hotspotValue,
5058
                                        $val
5059
                                    );
5060
                                }
5061
                            }
5062
                        }
5063
                    }
5064
                    $my_exe_id = $exeId;
5065
                }
5066
            }
5067
5068
            $relPath = api_get_path(WEB_CODE_PATH);
5069
5070
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5071
                // We made an extra table for the answers
5072
5073
                if ($show_result) {
5074
5075
                    //	if ($origin != 'learnpath') {
5076
                    echo '</table></td></tr>';
5077
                    echo "
5078
                        <tr>
5079
                            <td colspan=\"2\">
5080
                                <p><em>" . get_lang('HotSpot') . "</em></p>
5081
                                <div id=\"hotspot-solution-$questionId\"></div>
5082
                                <script>
5083
                                    $(document).on('ready', function () {
5084
                                        new HotspotQuestion({
5085
                                            questionId: $questionId,
5086
                                            exerciseId: $exeId,
5087
                                            selector: '#hotspot-solution-$questionId',
5088
                                            for: 'solution',
5089
                                            relPath: '$relPath'
5090
                                        });
5091
                                    });
5092
                                </script>
5093
                            </td>
5094
                        </tr>
5095
                    ";
5096
                    //	}
5097
                }
5098
            } else if ($answerType == ANNOTATION) {
5099
                if ($show_result) {
5100
                    echo '
5101
                        <p><em>' . get_lang('Annotation') . '</em></p>
5102
                        <div id="annotation-canvas-'.$questionId.'"></div>
5103
                        <script>
5104
                            AnnotationQuestion({
5105
                                questionId: parseInt('.$questionId.'),
5106
                                exerciseId: parseInt('.$exeId.'),
5107
                                relPath: \''.$relPath.'\'
5108
                            });
5109
                        </script>
5110
                    ';
5111
                }
5112
            }
5113
5114
            //if ($origin != 'learnpath') {
5115
            if ($show_result && $answerType != ANNOTATION) {
5116
                echo '</table>';
5117
            }
5118
            //	}
5119
        }
5120
        unset($objAnswerTmp);
5121
5122
        $totalWeighting += $questionWeighting;
5123
        // Store results directly in the database
5124
        // For all in one page exercises, the results will be
5125
        // stored by exercise_results.php (using the session)
5126
5127
        if ($saved_results) {
5128
            if ($debug) error_log("Save question results $saved_results");
5129
            if ($debug) error_log(print_r($choice ,1 ));
5130
5131
            if (empty($choice)) {
5132
                $choice = 0;
5133
            }
5134
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5135
                if ($choice != 0) {
5136
                    $reply = array_keys($choice);
5137
                    for ($i = 0; $i < sizeof($reply); $i++) {
5138
                        $ans = $reply[$i];
5139
                        Event::saveQuestionAttempt(
5140
                            $questionScore,
5141
                            $ans . ':' . $choice[$ans],
5142
                            $quesId,
5143
                            $exeId,
5144
                            $i,
5145
                            $this->id
5146
                        );
5147
                        if ($debug) {
5148
                            error_log('result =>' . $questionScore . ' ' . $ans . ':' . $choice[$ans]);
5149
                        }
5150
                    }
5151
                } else {
5152
                    Event::saveQuestionAttempt(
5153
                        $questionScore,
5154
                        0,
5155
                        $quesId,
5156
                        $exeId,
5157
                        0,
5158
                        $this->id
5159
                    );
5160
                }
5161
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5162
                if ($choice != 0) {
5163
                    $reply = array_keys($choice);
5164
5165
                    if ($debug) {
5166
                        error_log("reply " . print_r($reply, 1) . "");
5167
                    }
5168 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5169
                        $ans = $reply[$i];
5170
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5171
                    }
5172
                } else {
5173
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5174
                }
5175
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5176
                if ($choice != 0) {
5177
                    $reply = array_keys($choice);
5178 View Code Duplication
                    for ($i = 0; $i < sizeof($reply); $i++) {
5179
                        $ans = $reply[$i];
5180
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5181
                    }
5182
                } else {
5183
                    Event::saveQuestionAttempt(
5184
                        $questionScore,
5185
                        0,
5186
                        $quesId,
5187
                        $exeId,
5188
                        0,
5189
                        $this->id
5190
                    );
5191
                }
5192
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5193
                if (isset($matching)) {
5194
                    foreach ($matching as $j => $val) {
5195
                        Event::saveQuestionAttempt(
5196
                            $questionScore,
5197
                            $val,
5198
                            $quesId,
5199
                            $exeId,
5200
                            $j,
5201
                            $this->id
5202
                        );
5203
                    }
5204
                }
5205
            } elseif ($answerType == FREE_ANSWER) {
5206
                $answer = $choice;
5207
                Event::saveQuestionAttempt(
5208
                    $questionScore,
5209
                    $answer,
5210
                    $quesId,
5211
                    $exeId,
5212
                    0,
5213
                    $this->id
5214
                );
5215
            } elseif ($answerType == ORAL_EXPRESSION) {
5216
                $answer = $choice;
5217
                Event::saveQuestionAttempt(
5218
                    $questionScore,
5219
                    $answer,
5220
                    $quesId,
5221
                    $exeId,
5222
                    0,
5223
                    $this->id,
5224
                    false,
5225
                    $objQuestionTmp->getAbsoluteFilePath()
5226
                );
5227
            } elseif (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION])) {
5228
                $answer = $choice;
5229
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5230
                //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
5231
            } elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
5232
                $answer = [];
5233
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5234
                    Database::delete(
5235
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5236
                        [
5237
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5238
                                $exeId,
5239
                                $questionId,
5240
                                api_get_course_int_id()
5241
                            ]
5242
                        ]
5243
                    );
5244
5245
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5246
                        $answer[] = $val;
5247
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5248
                        Event::saveExerciseAttemptHotspot(
5249
                            $exeId,
5250
                            $quesId,
5251
                            $idx,
5252
                            $hotspotValue,
5253
                            $val,
5254
                            false,
5255
                            $this->id
5256
                        );
5257
                    }
5258
                }
5259
5260
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5261
            } else {
5262
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0,$this->id);
5263
            }
5264
        }
5265
5266
        if ($propagate_neg == 0 && $questionScore < 0) {
5267
            $questionScore = 0;
5268
        }
5269
5270
        if ($saved_results) {
5271
            $stat_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5272
            $sql = 'UPDATE ' . $stat_table . ' SET
5273
                        exe_result = exe_result + ' . floatval($questionScore) . '
5274
                    WHERE exe_id = ' . $exeId;
5275
            Database::query($sql);
5276
        }
5277
5278
        $return_array = array(
5279
            'score' => $questionScore,
5280
            'weight' => $questionWeighting,
5281
            'extra' => $extra_data,
5282
            'open_question' => $arrques,
5283
            'open_answer' => $arrans,
5284
            'answer_type' => $answerType,
5285
        );
5286
5287
        return $return_array;
5288
    }
5289
5290
    /**
5291
     * Sends a notification when a user ends an examn
5292
     *
5293
     * @param array $question_list_answers
5294
     * @param string $origin
5295
     * @param int $exe_id
5296
     * @param float $score
5297
     * @param float $weight
5298
     * @return bool
5299
     */
5300
    public function send_mail_notification_for_exam($question_list_answers, $origin, $exe_id, $score, $weight)
5301
    {
5302
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1) {
5303
            return false;
5304
        }
5305
5306
        // Email configuration settings
5307
        $courseCode = api_get_course_id();
5308
        $courseInfo = api_get_course_info($courseCode);
5309
        $sessionId = api_get_session_id();
5310
5311
        if (empty($courseInfo)) {
5312
            return false;
5313
        }
5314
5315
        $url_email = api_get_path(WEB_CODE_PATH)
5316
            . 'exercise/exercise_show.php?'
5317
            . api_get_cidreq()
5318
            . '&id_session='
5319
            . $sessionId
5320
            . '&id='
5321
            . $exe_id
5322
            . '&action=qualify';
5323
        $user_info = api_get_user_info(api_get_user_id());
5324
5325
        $scoreLabel = '';
5326
        if (api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true) {
5327
            $scoreLabel = ExerciseLib::show_score($score, $weight, false, true);
5328
            $scoreLabel = "<tr>
5329
                            <td>".get_lang('Score')."</td>
5330
                            <td>&nbsp;$scoreLabel</td>
5331
                        </tr>";
5332
        }
5333
5334
        $msg = get_lang('ExerciseAttempted').'<br /><br />'
5335
                    .get_lang('AttemptDetails').' : <br /><br />
5336
                    <table>
5337
                        <tr>
5338
                            <td><em>'.get_lang('CourseName').'</em></td>
5339
                            <td>&nbsp;<b>#course#</b></td>
5340
                        </tr>
5341
                        <tr>
5342
                            <td>'.get_lang('TestAttempted').'</td>
5343
                            <td>&nbsp;#exercise#</td>
5344
                        </tr>
5345
                        <tr>
5346
                            <td>'.get_lang('StudentName').'</td>
5347
                            <td>&nbsp;#firstName# #lastName#</td>
5348
                        </tr>
5349
                        <tr>
5350
                            <td>'.get_lang('StudentEmail').'</td>
5351
                            <td>&nbsp;#email#</td>
5352
                        </tr>
5353
                        '.$scoreLabel.'
5354
                    </table>';
5355
        $open_question_list = null;
5356
5357
        $msg = str_replace("#email#", $user_info['email'], $msg);
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("#course#", $courseInfo['name'], $msg1);
5362
5363
        if ($origin != 'learnpath') {
5364
            $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5365
        }
5366
        $msg1 = str_replace("#url#", $url_email, $msg);
5367
        $mail_content = $msg1;
5368
        $subject = get_lang('ExerciseAttempted');
5369
5370
        if (!empty($sessionId)) {
5371
            $addGeneralCoach = true;
5372
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
5373
            if ($setting === true) {
5374
                $addGeneralCoach = false;
5375
            }
5376
            $teachers = CourseManager::get_coach_list_from_course_code(
5377
                $courseCode,
5378
                $sessionId,
5379
                $addGeneralCoach
5380
            );
5381
        } else {
5382
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5383
        }
5384
5385
        if (!empty($teachers)) {
5386
            foreach ($teachers as $user_id => $teacher_data) {
5387
                MessageManager::send_message_simple(
5388
                    $user_id,
5389
                    $subject,
5390
                    $mail_content
5391
                );
5392
            }
5393
        }
5394
    }
5395
5396
    /**
5397
     * Sends a notification when a user ends an examn
5398
     * @param array $question_list_answers
5399
     * @param string $origin
5400
     * @param int $exe_id
5401
     * @return null
5402
     */
5403
    public function send_notification_for_open_questions($question_list_answers, $origin, $exe_id)
5404
    {
5405
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5406
            return null;
5407
        }
5408
        // Email configuration settings
5409
        $courseCode = api_get_course_id();
5410
        $course_info = api_get_course_info($courseCode);
5411
        $url_email = api_get_path(WEB_CODE_PATH)
5412
            . 'exercise/exercise_show.php?'
5413
            . api_get_cidreq()
5414
            . '&id_session='
5415
            . api_get_session_id()
5416
            . '&id='
5417
            . $exe_id
5418
            . '&action=qualify';
5419
        $user_info = api_get_user_info(api_get_user_id());
5420
5421
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
5422
                    .get_lang('AttemptDetails').' : <br /><br />'
5423
                    .'<table>'
5424
                        .'<tr>'
5425
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5426
                            .'<td>&nbsp;<b>#course#</b></td>'
5427
                        .'</tr>'
5428
                        .'<tr>'
5429
                            .'<td>'.get_lang('TestAttempted').'</td>'
5430
                            .'<td>&nbsp;#exercise#</td>'
5431
                        .'</tr>'
5432
                        .'<tr>'
5433
                            .'<td>'.get_lang('StudentName').'</td>'
5434
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5435
                        .'</tr>'
5436
                        .'<tr>'
5437
                            .'<td>'.get_lang('StudentEmail').'</td>'
5438
                            .'<td>&nbsp;#mail#</td>'
5439
                        .'</tr>'
5440
                    .'</table>';
5441
        $open_question_list = null;
5442 View Code Duplication
        foreach ($question_list_answers as $item) {
5443
            $question = $item['question'];
5444
            $answer = $item['answer'];
5445
            $answer_type = $item['answer_type'];
5446
5447
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
5448
                $open_question_list .=
5449
                    '<tr>'
5450
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5451
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5452
                    .'</tr>'
5453
                    .'<tr>'
5454
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5455
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5456
                    .'</tr>';
5457
            }
5458
        }
5459
5460
        if (!empty($open_question_list)) {
5461
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
5462
                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
5463
            $msg .= $open_question_list;
5464
            $msg .= '</table><br />';
5465
5466
5467
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5468
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5469
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5470
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5471
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5472
5473
            if ($origin != 'learnpath') {
5474
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5475
            }
5476
            $msg1 = str_replace("#url#", $url_email, $msg);
5477
            $mail_content = $msg1;
5478
            $subject = get_lang('OpenQuestionsAttempted');
5479
5480 View Code Duplication
            if (api_get_session_id()) {
5481
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5482
            } else {
5483
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5484
            }
5485
5486
            if (!empty($teachers)) {
5487
                foreach ($teachers as $user_id => $teacher_data) {
5488
                    MessageManager::send_message_simple(
5489
                        $user_id,
5490
                        $subject,
5491
                        $mail_content
5492
                    );
5493
                }
5494
            }
5495
        }
5496
    }
5497
5498
    /**
5499
     * Send notification for oral questions
5500
     * @param array $question_list_answers
5501
     * @param string $origin
5502
     * @param int $exe_id
5503
     * @return null
5504
     */
5505
    public function send_notification_for_oral_questions($question_list_answers, $origin, $exe_id)
5506
    {
5507
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5508
            return null;
5509
        }
5510
        // Email configuration settings
5511
        $courseCode = api_get_course_id();
5512
        $course_info = api_get_course_info($courseCode);
5513
5514
        $url_email = api_get_path(WEB_CODE_PATH)
5515
            . 'exercise/exercise_show.php?'
5516
            . api_get_cidreq()
5517
            . '&id_session='
5518
            . api_get_session_id()
5519
            . '&id='
5520
            . $exe_id
5521
            . '&action=qualify';
5522
        $user_info = api_get_user_info(api_get_user_id());
5523
5524
        $oral_question_list = null;
5525 View Code Duplication
        foreach ($question_list_answers as $item) {
5526
            $question    = $item['question'];
5527
            $answer      = $item['answer'];
5528
            $answer_type = $item['answer_type'];
5529
5530
            if (!empty($question) && !empty($answer) && $answer_type == ORAL_EXPRESSION) {
5531
                $oral_question_list.='<br /><table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'
5532
                    .'<tr>'
5533
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5534
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5535
                    .'</tr>'
5536
                    .'<tr>'
5537
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5538
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5539
                    .'</tr></table>';
5540
            }
5541
        }
5542
5543
        if (!empty($oral_question_list)) {
5544
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
5545
                    '.get_lang('AttemptDetails').' : <br /><br />'
5546
                    .'<table>'
5547
                        .'<tr>'
5548
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5549
                            .'<td>&nbsp;<b>#course#</b></td>'
5550
                        .'</tr>'
5551
                        .'<tr>'
5552
                            .'<td>'.get_lang('TestAttempted').'</td>'
5553
                            .'<td>&nbsp;#exercise#</td>'
5554
                        .'</tr>'
5555
                        .'<tr>'
5556
                            .'<td>'.get_lang('StudentName').'</td>'
5557
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5558
                        .'</tr>'
5559
                        .'<tr>'
5560
                            .'<td>'.get_lang('StudentEmail').'</td>'
5561
                            .'<td>&nbsp;#mail#</td>'
5562
                        .'</tr>'
5563
                    .'</table>';
5564
            $msg .=  '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'),$oral_question_list).'<br />';
5565
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5566
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5567
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5568
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5569
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5570
5571
            if ($origin != 'learnpath') {
5572
                $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5573
            }
5574
            $msg1 = str_replace("#url#", $url_email, $msg);
5575
            $mail_content = $msg1;
5576
            $subject = get_lang('OralQuestionsAttempted');
5577
5578 View Code Duplication
            if (api_get_session_id()) {
5579
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5580
            } else {
5581
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5582
            }
5583
5584
            if (!empty($teachers)) {
5585
                foreach ($teachers as $user_id => $teacher_data) {
5586
                    MessageManager::send_message_simple(
5587
                        $user_id,
5588
                        $subject,
5589
                        $mail_content
5590
                    );
5591
                }
5592
            }
5593
        }
5594
    }
5595
5596
    /**
5597
     * @param array $user_data result of api_get_user_info()
5598
     * @param string $start_date
5599
     * @param null $duration
5600
     * @param string $ip Optional. The user IP
5601
     * @return string
5602
     */
5603
    public function show_exercise_result_header($user_data, $start_date = null, $duration = null, $ip = null)
5604
    {
5605
        $array = array();
5606
5607
        if (!empty($user_data)) {
5608
            $array[] = array('title' => get_lang('Name'), 'content' => $user_data['complete_name']);
5609
            $array[] = array('title' => get_lang('Username'), 'content' => $user_data['username']);
5610
            if (!empty($user_data['official_code'])) {
5611
                $array[] = array(
5612
                    'title' => get_lang('OfficialCode'),
5613
                    'content' => $user_data['official_code']
5614
                );
5615
            }
5616
        }
5617
        // Description can be very long and is generally meant to explain
5618
        //   rules *before* the exam. Leaving here to make display easier if
5619
        //   necessary
5620
        /*
5621
        if (!empty($this->description)) {
5622
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
5623
        }
5624
        */
5625 View Code Duplication
        if (!empty($start_date)) {
5626
            $array[] = array('title' => get_lang('StartDate'), 'content' => $start_date);
5627
        }
5628
5629 View Code Duplication
        if (!empty($duration)) {
5630
            $array[] = array('title' => get_lang('Duration'), 'content' => $duration);
5631
        }
5632
5633 View Code Duplication
        if (!empty($ip)) {
5634
            $array[] = array('title' => get_lang('IP'), 'content' => $ip);
5635
        }
5636
        $html  = '<div class="question-result">';
5637
        $html .= Display::page_header(
5638
            Display::return_icon('test-quiz.png', get_lang('Result'),null, ICON_SIZE_MEDIUM).' '.$this->exercise.' : '.get_lang('Result')
5639
        );
5640
        $html .= Display::description($array);
5641
        $html .="</div>";
5642
        return $html;
5643
    }
5644
5645
    /**
5646
     * Create a quiz from quiz data
5647
     * @param string  Title
5648
     * @param int     Time before it expires (in minutes)
5649
     * @param int     Type of exercise
5650
     * @param int     Whether it's randomly picked questions (1) or not (0)
5651
     * @param int     Whether the exercise is visible to the user (1) or not (0)
5652
     * @param int     Whether the results are show to the user (0) or not (1)
5653
     * @param int     Maximum number of attempts (0 if no limit)
5654
     * @param int     Feedback type
5655
     * @todo this was function was added due the import exercise via CSV
5656
     * @return    int New exercise ID
5657
     */
5658
    public function createExercise(
5659
        $title,
5660
        $expired_time = 0,
5661
        $type = 2,
5662
        $random = 0,
5663
        $active = 1,
5664
        $results_disabled = 0,
5665
        $max_attempt = 0,
5666
        $feedback = 3,
5667
        $propagateNegative = 0
5668
    ) {
5669
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
5670
        $type = intval($type);
5671
        $random = intval($random);
5672
        $active = intval($active);
5673
        $results_disabled = intval($results_disabled);
5674
        $max_attempt = intval($max_attempt);
5675
        $feedback = intval($feedback);
5676
        $expired_time = intval($expired_time);
5677
        $title = Database::escape_string($title);
5678
        $propagateNegative = intval($propagateNegative);
5679
        $sessionId = api_get_session_id();
5680
        $course_id = api_get_course_int_id();
5681
        // Save a new quiz
5682
        $sql = "INSERT INTO $tbl_quiz (
5683
                c_id,
5684
                title,
5685
                type,
5686
                random,
5687
                active,
5688
                results_disabled,
5689
                max_attempt,
5690
                start_time,
5691
                end_time,
5692
                feedback_type,
5693
                expired_time,
5694
                session_id,
5695
                propagate_neg
5696
            )
5697
            VALUES (
5698
                '$course_id',
5699
                '$title',
5700
                $type,
5701
                $random,
5702
                $active,
5703
                $results_disabled,
5704
                $max_attempt,
5705
                '',
5706
                '',
5707
                $feedback,
5708
                $expired_time,
5709
                $sessionId,
5710
                $propagateNegative
5711
            )";
5712
        Database::query($sql);
5713
        $quiz_id = Database::insert_id();
5714
5715
        if ($quiz_id) {
5716
5717
            $sql = "UPDATE $tbl_quiz SET id = iid WHERE iid = {$quiz_id} ";
5718
            Database::query($sql);
5719
        }
5720
5721
        return $quiz_id;
5722
    }
5723
5724
    function process_geometry()
5725
    {
5726
5727
    }
5728
5729
    /**
5730
     * Returns the exercise result
5731
     * @param 	int		attempt id
5732
     * @return 	float 	exercise result
5733
     */
5734
    public function get_exercise_result($exe_id)
5735
    {
5736
        $result = array();
5737
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
5738
5739
        if (!empty($track_exercise_info)) {
5740
            $totalScore = 0;
5741
            $objExercise = new Exercise();
5742
            $objExercise->read($track_exercise_info['exe_exo_id']);
5743
            if (!empty($track_exercise_info['data_tracking'])) {
5744
                $question_list = explode(',', $track_exercise_info['data_tracking']);
5745
            }
5746
            foreach ($question_list as $questionId) {
5747
                $question_result = $objExercise->manage_answer(
5748
                    $exe_id,
5749
                    $questionId,
5750
                    '',
5751
                    'exercise_show',
5752
                    array(),
5753
                    false,
5754
                    true,
5755
                    false,
5756
                    $objExercise->selectPropagateNeg()
5757
                );
5758
                $totalScore      += $question_result['score'];
5759
            }
5760
5761
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
5762
                $totalScore = 0;
5763
            }
5764
            $result = array(
5765
                'score' => $totalScore,
5766
                'weight' => $track_exercise_info['exe_weighting']
5767
            );
5768
        }
5769
        return $result;
5770
    }
5771
5772
    /**
5773
     * Checks if the exercise is visible due a lot of conditions
5774
     * visibility, time limits, student attempts
5775
     * Return associative array
5776
     * value : true if execise visible
5777
     * message : HTML formated message
5778
     * rawMessage : text message
5779
     * @param int $lpId
5780
     * @param int $lpItemId
5781
     * @param int $lpItemViewId
5782
     * @param bool $filterByAdmin
5783
     * @return array
5784
     */
5785
    public function is_visible(
5786
        $lpId = 0,
5787
        $lpItemId = 0,
5788
        $lpItemViewId = 0,
5789
        $filterByAdmin = true
5790
    ) {
5791
        // 1. By default the exercise is visible
5792
        $isVisible = true;
5793
        $message = null;
5794
5795
        // 1.1 Admins and teachers can access to the exercise
5796
        if ($filterByAdmin) {
5797
            if (api_is_platform_admin() || api_is_course_admin()) {
5798
                return array('value' => true, 'message' => '');
5799
            }
5800
        }
5801
5802
        // Deleted exercise.
5803 View Code Duplication
        if ($this->active == -1) {
5804
            return array(
5805
                'value' => false,
5806
                'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5807
                'rawMessage' => get_lang('ExerciseNotFound')
5808
            );
5809
        }
5810
5811
        // Checking visibility in the item_property table.
5812
        $visibility = api_get_item_visibility(
5813
            api_get_course_info(),
5814
            TOOL_QUIZ,
5815
            $this->id,
5816
            api_get_session_id()
5817
        );
5818
5819
        if ($visibility == 0 || $visibility == 2) {
5820
            $this->active = 0;
5821
        }
5822
5823
        // 2. If the exercise is not active.
5824
        if (empty($lpId)) {
5825
            // 2.1 LP is OFF
5826 View Code Duplication
            if ($this->active == 0) {
5827
                return array(
5828
                    'value' => false,
5829
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5830
                    'rawMessage' => get_lang('ExerciseNotFound')
5831
                );
5832
            }
5833
        } else {
5834
            // 2.1 LP is loaded
5835
            if ($this->active == 0 && !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())) {
5836
                return array(
5837
                    'value' => false,
5838
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5839
                    'rawMessage' => get_lang('ExerciseNotFound')
5840
                );
5841
            }
5842
        }
5843
5844
        //3. We check if the time limits are on
5845
        if (!empty($this->start_time) || !empty($this->end_time)) {
5846
            $limitTimeExists = true;
5847
        } else {
5848
            $limitTimeExists = false;
5849
        }
5850
5851
        if ($limitTimeExists) {
5852
            $timeNow = time();
5853
5854
            $existsStartDate = false;
5855
            $nowIsAfterStartDate = true;
5856
            $existsEndDate = false;
5857
            $nowIsBeforeEndDate = true;
5858
5859
            if (!empty($this->start_time)) {
5860
                $existsStartDate = true;
5861
            }
5862
5863
            if (!empty($this->end_time)) {
5864
                $existsEndDate = true;
5865
            }
5866
5867
            // check if we are before-or-after end-or-start date
5868
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
5869
                $nowIsAfterStartDate = false;
5870
            }
5871
5872
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
5873
                $nowIsBeforeEndDate = false;
5874
            }
5875
5876
            // lets check all cases
5877
            if ($existsStartDate && !$existsEndDate) {
5878
                // exists start date and dont exists end date
5879
                if ($nowIsAfterStartDate) {
5880
                    // after start date, no end date
5881
                    $isVisible = true;
5882
                    $message = sprintf(get_lang('ExerciseAvailableSinceX'),
5883
                        api_convert_and_format_date($this->start_time));
5884
                } else {
5885
                    // before start date, no end date
5886
                    $isVisible = false;
5887
                    $message = sprintf(get_lang('ExerciseAvailableFromX'),
5888
                        api_convert_and_format_date($this->start_time));
5889
            }
5890
            } else if (!$existsStartDate && $existsEndDate) {
5891
                // doesnt exist start date, exists end date
5892
                if ($nowIsBeforeEndDate) {
5893
                    // before end date, no start date
5894
                    $isVisible = true;
5895
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5896
                        api_convert_and_format_date($this->end_time));
5897
                } else {
5898
                    // after end date, no start date
5899
                    $isVisible = false;
5900
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5901
                        api_convert_and_format_date($this->end_time));
5902
                }
5903
            } elseif ($existsStartDate && $existsEndDate) {
5904
                // exists start date and end date
5905
                if ($nowIsAfterStartDate) {
5906
                    if ($nowIsBeforeEndDate) {
5907
                        // after start date and before end date
5908
                        $isVisible = true;
5909
                        $message = sprintf(get_lang('ExerciseIsActivatedFromXToY'),
5910
                            api_convert_and_format_date($this->start_time),
5911
                            api_convert_and_format_date($this->end_time));
5912 View Code Duplication
                    } else {
5913
                        // after start date and after end date
5914
                        $isVisible = false;
5915
                        $message = sprintf(get_lang('ExerciseWasActivatedFromXToY'),
5916
                            api_convert_and_format_date($this->start_time),
5917
                            api_convert_and_format_date($this->end_time));
5918
                    }
5919 View Code Duplication
                } else {
5920
                    if ($nowIsBeforeEndDate) {
5921
                        // before start date and before end date
5922
                        $isVisible = false;
5923
                        $message = sprintf(get_lang('ExerciseWillBeActivatedFromXToY'),
5924
                            api_convert_and_format_date($this->start_time),
5925
                            api_convert_and_format_date($this->end_time));
5926
                    }
5927
                    // case before start date and after end date is impossible
5928
                }
5929
            } elseif (!$existsStartDate && !$existsEndDate) {
5930
                // doesnt exist start date nor end date
5931
                $isVisible = true;
5932
                $message = "";
5933
            }
5934
        }
5935
5936
        // 4. We check if the student have attempts
5937
        $exerciseAttempts = $this->selectAttempts();
5938
5939
        if ($isVisible) {
5940
            if ($exerciseAttempts > 0) {
5941
5942
                $attemptCount = Event::get_attempt_count_not_finished(
5943
                    api_get_user_id(),
5944
                    $this->id,
5945
                    $lpId,
5946
                    $lpItemId,
5947
                    $lpItemViewId
5948
                );
5949
5950
                if ($attemptCount >= $exerciseAttempts) {
5951
                    $message = sprintf(
5952
                        get_lang('ReachedMaxAttempts'),
5953
                        $this->name,
5954
                        $exerciseAttempts
5955
                    );
5956
                    $isVisible = false;
5957
                }
5958
            }
5959
        }
5960
5961
        $rawMessage = "";
5962
        if (!empty($message)){
5963
            $rawMessage = $message;
5964
            $message = Display::return_message($message, 'warning', false);
5965
        }
5966
5967
        return array(
5968
            'value' => $isVisible,
5969
            'message' => $message,
5970
            'rawMessage' => $rawMessage
5971
        );
5972
    }
5973
5974
    public function added_in_lp()
5975
    {
5976
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
5977
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
5978
            WHERE c_id = {$this->course_id} AND item_type = '" . TOOL_QUIZ . "' AND path = '{$this->id}'";
5979
        $result = Database::query($sql);
5980
        if (Database::num_rows($result) > 0) {
5981
            return true;
5982
        }
5983
        return false;
5984
    }
5985
5986
    /**
5987
     * Returns an array with the media list
5988
     * @param array question list
5989
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
5990
     * <code>
5991
     * array (size=2)
5992
     *  999 =>
5993
     *    array (size=3)
5994
     *      0 => int 7
5995
     *      1 => int 6
5996
     *      2 => int 3254
5997
     *  100 =>
5998
     *   array (size=1)
5999
     *      0 => int 5
6000
     *  </code>
6001
     * @return array
6002
     */
6003
    private function setMediaList($questionList)
6004
    {
6005
        $mediaList = array();
6006
        if (!empty($questionList)) {
6007
            foreach ($questionList as $questionId) {
6008
                $objQuestionTmp = Question::read($questionId, $this->course_id);
6009
6010
                // If a media question exists
6011
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
6012
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
6013
                } else {
6014
                    //Always the last item
6015
                    $mediaList[999][] = $objQuestionTmp->id;
6016
                }
6017
            }
6018
        }
6019
        $this->mediaList = $mediaList;
6020
    }
6021
6022
    /**
6023
     * Returns an array with this form
6024
     * @example
6025
     * <code>
6026
     * array (size=3)
6027
    999 =>
6028
    array (size=3)
6029
    0 => int 3422
6030
    1 => int 3423
6031
    2 => int 3424
6032
    100 =>
6033
    array (size=2)
6034
    0 => int 3469
6035
    1 => int 3470
6036
    101 =>
6037
    array (size=1)
6038
    0 => int 3482
6039
     * </code>
6040
     * The array inside the key 999 means the question list that belongs to the media id = 999,
6041
     * this case is special because 999 means "no media".
6042
     * @return array
6043
     */
6044
    public function getMediaList()
6045
    {
6046
        return $this->mediaList;
6047
    }
6048
6049
    /**
6050
     * Is media question activated?
6051
     * @return bool
6052
     */
6053
    public function mediaIsActivated()
6054
    {
6055
        $mediaQuestions = $this->getMediaList();
6056
        $active = false;
6057
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6058
            $media_count = count($mediaQuestions);
6059
            if ($media_count > 1) {
6060
                return true;
6061
            } elseif ($media_count == 1) {
6062
                if (isset($mediaQuestions[999])) {
6063
                    return false;
6064
                } else {
6065
                    return true;
6066
                }
6067
            }
6068
        }
6069
6070
        return $active;
6071
    }
6072
6073
    /**
6074
     * Gets question list from the exercise
6075
     *
6076
     * @return array
6077
     */
6078
    public function getQuestionList()
6079
    {
6080
        return $this->questionList;
6081
    }
6082
6083
    /**
6084
     * Question list with medias compressed like this
6085
     * @example
6086
     * <code>
6087
     * array(
6088
     *      question_id_1,
6089
     *      question_id_2,
6090
     *      media_id, <- this media id contains question ids
6091
     *      question_id_3,
6092
     * )
6093
     * </code>
6094
     * @return array
6095
     */
6096
    public function getQuestionListWithMediasCompressed()
6097
    {
6098
        return $this->questionList;
6099
    }
6100
6101
    /**
6102
     * Question list with medias uncompressed like this
6103
     * @example
6104
     * <code>
6105
     * array(
6106
     *      question_id,
6107
     *      question_id,
6108
     *      question_id, <- belongs to a media id
6109
     *      question_id, <- belongs to a media id
6110
     *      question_id,
6111
     * )
6112
     * </code>
6113
     * @return array
6114
     */
6115
    public function getQuestionListWithMediasUncompressed()
6116
    {
6117
        return $this->questionListUncompressed;
6118
    }
6119
6120
    /**
6121
     * Sets the question list when the exercise->read() is executed
6122
     * @param   bool    $adminView  Whether to view the set the list of *all* questions or just the normal student view
6123
     */
6124
    public function setQuestionList($adminView = false)
6125
    {
6126
        // Getting question list.
6127
        $questionList = $this->selectQuestionList(true, $adminView);
6128
6129
        $this->setMediaList($questionList);
6130
6131
        $this->questionList = $this->transformQuestionListWithMedias($questionList, false);
6132
        $this->questionListUncompressed = $this->transformQuestionListWithMedias($questionList, true);
6133
    }
6134
6135
    /**
6136
     *
6137
     * @params array question list
6138
     * @params bool expand or not question list (true show all questions, false show media question id instead of the question ids)
6139
     *
6140
     **/
6141 View Code Duplication
    public function transformQuestionListWithMedias($question_list, $expand_media_questions = false)
6142
    {
6143
        $new_question_list = array();
6144
        if (!empty($question_list)) {
6145
            $media_questions = $this->getMediaList();
6146
6147
            $media_active = $this->mediaIsActivated($media_questions);
6148
6149
            if ($media_active) {
6150
                $counter = 1;
6151
                foreach ($question_list as $question_id) {
6152
                    $add_question = true;
6153
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6154
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6155
                            $add_question = false;
6156
                            if (!in_array($media_id, $new_question_list)) {
6157
                                $new_question_list[$counter] = $media_id;
6158
                                $counter++;
6159
                            }
6160
                            break;
6161
                        }
6162
                    }
6163
                    if ($add_question) {
6164
                        $new_question_list[$counter] = $question_id;
6165
                        $counter++;
6166
                    }
6167
                }
6168
                if ($expand_media_questions) {
6169
                    $media_key_list = array_keys($media_questions);
6170
                    foreach ($new_question_list as &$question_id) {
6171
                        if (in_array($question_id, $media_key_list)) {
6172
                            $question_id = $media_questions[$question_id];
6173
                        }
6174
                    }
6175
                    $new_question_list = array_flatten($new_question_list);
6176
                }
6177
            } else {
6178
                $new_question_list = $question_list;
6179
            }
6180
        }
6181
6182
        return $new_question_list;
6183
    }
6184
6185
    /**
6186
     * Get question list depend on the random settings.
6187
     *
6188
     * @return array
6189
     */
6190
    public function get_validated_question_list()
6191
    {
6192
        $tabres = array();
6193
        $isRandomByCategory = $this->isRandomByCat();
6194
        if ($isRandomByCategory == 0) {
6195
            if ($this->isRandom()) {
6196
                $tabres = $this->selectRandomList();
6197
            } else {
6198
                $tabres = $this->selectQuestionList();
6199
            }
6200
        } else {
6201
            if ($this->isRandom()) {
6202
                // USE question categories
6203
                // get questions by category for this exercise
6204
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6205
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6206
                // value is the array of question id of this category
6207
                $questionList = array();
6208
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6209
                $isRandomByCategory = $this->selectRandomByCat();
6210
                // We sort categories based on the term between [] in the head
6211
                // of the category's description
6212
                /* examples of categories :
6213
                 * [biologie] Maitriser les mecanismes de base de la genetique
6214
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6215
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6216
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6217
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6218
                 * [chimie] Connaître les charges des particules
6219
                 * We want that in the order of the groups defined by the term
6220
                 * between brackets at the beginning of the category title
6221
                */
6222
                // If test option is Grouped By Categories
6223
                if ($isRandomByCategory == 2) {
6224
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6225
                }
6226
                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...
6227
                    $number_of_random_question = $this->random;
6228
                    if ($this->random == -1) {
6229
                        $number_of_random_question = count($this->questionList);
6230
                    }
6231
                    $questionList = array_merge(
6232
                        $questionList,
6233
                        TestCategory::getNElementsFromArray(
6234
                            $tabquestion,
6235
                            $number_of_random_question
6236
                        )
6237
                    );
6238
                }
6239
                // shuffle the question list if test is not grouped by categories
6240
                if ($isRandomByCategory == 1) {
6241
                    shuffle($questionList); // or not
6242
                }
6243
                $tabres = $questionList;
6244
            } 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...
6245
                // Problem, random by category has been selected and
6246
                // we have no $this->isRandom number of question selected
6247
                // Should not happened
6248
            }
6249
        }
6250
        return $tabres;
6251
    }
6252
6253
    function get_question_list($expand_media_questions = false)
6254
    {
6255
        $question_list = $this->get_validated_question_list();
6256
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6257
        return $question_list;
6258
    }
6259
6260 View Code Duplication
    function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6261
    {
6262
        $new_question_list = array();
6263
        if (!empty($question_list)) {
6264
            $media_questions = $this->getMediaList();
6265
            $media_active = $this->mediaIsActivated($media_questions);
6266
6267
            if ($media_active) {
6268
                $counter = 1;
6269
                foreach ($question_list as $question_id) {
6270
                    $add_question = true;
6271
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6272
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6273
                            $add_question = false;
6274
                            if (!in_array($media_id, $new_question_list)) {
6275
                                $new_question_list[$counter] = $media_id;
6276
                                $counter++;
6277
                            }
6278
                            break;
6279
                        }
6280
                    }
6281
                    if ($add_question) {
6282
                        $new_question_list[$counter] = $question_id;
6283
                        $counter++;
6284
                    }
6285
                }
6286
                if ($expand_media_questions) {
6287
                    $media_key_list = array_keys($media_questions);
6288
                    foreach ($new_question_list as &$question_id) {
6289
                        if (in_array($question_id, $media_key_list)) {
6290
                            $question_id = $media_questions[$question_id];
6291
                        }
6292
                    }
6293
                    $new_question_list = array_flatten($new_question_list);
6294
                }
6295
            } else {
6296
                $new_question_list = $question_list;
6297
            }
6298
        }
6299
        return $new_question_list;
6300
    }
6301
6302
    /**
6303
     * @param int $exe_id
6304
     * @return array|mixed
6305
     */
6306
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6307
    {
6308
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6309
        $exe_id = intval($exe_id);
6310
        $sql_track = "SELECT * FROM $track_exercises WHERE exe_id = $exe_id ";
6311
        $result = Database::query($sql_track);
6312
        $new_array = array();
6313
        if (Database::num_rows($result) > 0 ) {
6314
            $new_array = Database::fetch_array($result, 'ASSOC');
6315
6316
            $new_array['duration'] = null;
6317
6318
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6319
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6320
6321
            if (!empty($start_date) && !empty($end_date)) {
6322
                $start_date = api_strtotime($start_date, 'UTC');
0 ignored issues
show
Bug introduced by
It seems like $start_date defined by api_strtotime($start_date, 'UTC') on line 6322 can also be of type object<DateTime>; however, api_strtotime() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

    return array();
}

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

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

Loading history...
6324
                if ($start_date && $end_date) {
6325
                    $mytime = $end_date- $start_date;
6326
                    $new_learnpath_item = new learnpathItem(null);
6327
                    $time_attemp = $new_learnpath_item->get_scorm_time('js', $mytime);
6328
                    $h = get_lang('h');
6329
                    $time_attemp = str_replace('NaN', '00' . $h . '00\'00"', $time_attemp);
6330
                    $new_array['duration'] = $time_attemp;
6331
                }
6332
            }
6333
        }
6334
        return $new_array;
6335
    }
6336
6337
    public function edit_question_to_remind($exe_id, $question_id, $action = 'add')
6338
    {
6339
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6340
        $question_id = intval($question_id);
6341
        $exe_id = intval($exe_id);
6342
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6343
        if ($exercise_info) {
6344
6345
            if (empty($exercise_info['questions_to_check'])) {
6346
                if ($action == 'add') {
6347
                    $sql = "UPDATE $track_exercises SET questions_to_check = '$question_id' WHERE exe_id = $exe_id ";
6348
                    Database::query($sql);
6349
                }
6350
            } else {
6351
                $remind_list = explode(',',$exercise_info['questions_to_check']);
6352
6353
                $remind_list_string = '';
6354
                if ($action == 'add') {
6355
                    if (!in_array($question_id, $remind_list)) {
6356
                        $remind_list[] = $question_id;
6357
                        if (!empty($remind_list)) {
6358
                            sort($remind_list);
6359
                            array_filter($remind_list);
6360
                        }
6361
                        $remind_list_string = implode(',', $remind_list);
6362
                    }
6363
                } elseif ($action == 'delete')  {
6364
                    if (!empty($remind_list)) {
6365
                        if (in_array($question_id, $remind_list)) {
6366
                            $remind_list = array_flip($remind_list);
6367
                            unset($remind_list[$question_id]);
6368
                            $remind_list = array_flip($remind_list);
6369
6370
                            if (!empty($remind_list)) {
6371
                                sort($remind_list);
6372
                                array_filter($remind_list);
6373
                                $remind_list_string = implode(',', $remind_list);
6374
                            }
6375
                        }
6376
                    }
6377
                }
6378
                $remind_list_string = Database::escape_string($remind_list_string);
6379
                $sql = "UPDATE $track_exercises SET questions_to_check = '$remind_list_string' WHERE exe_id = $exe_id ";
6380
                Database::query($sql);
6381
            }
6382
        }
6383
    }
6384
6385
    public function fill_in_blank_answer_to_array($answer)
6386
    {
6387
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6388
        $teacher_answer_list = $teacher_answer_list[0];
6389
        return $teacher_answer_list;
6390
    }
6391
6392
    public function fill_in_blank_answer_to_string($answer)
6393
    {
6394
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6395
        $result = '';
6396
        if (!empty($teacher_answer_list)) {
6397
            $i = 0;
6398
            foreach ($teacher_answer_list as $teacher_item) {
6399
                $value = null;
6400
                //Cleaning student answer list
6401
                $value = strip_tags($teacher_item);
6402
                $value = api_substr($value, 1, api_strlen($value) - 2);
6403
                $value = explode('/', $value);
6404
                if (!empty($value[0])) {
6405
                    $value = trim($value[0]);
6406
                    $value = str_replace('&nbsp;', '', $value);
6407
                    $result .= $value;
6408
                }
6409
            }
6410
        }
6411
        return $result;
6412
    }
6413
6414
    public function return_time_left_div()
6415
    {
6416
        $html = '<div id="clock_warning" style="display:none">';
6417
        $html .= Display::return_message(
6418
            get_lang('ReachedTimeLimit'),
6419
            'warning'
6420
        );
6421
        $html .= ' ';
6422
        $html .= sprintf(
6423
            get_lang('YouWillBeRedirectedInXSeconds'),
6424
            '<span id="counter_to_redirect" class="red_alert"></span>'
6425
        );
6426
        $html .= '</div>';
6427
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6428
        return $html;
6429
    }
6430
6431
    function get_count_question_list()
6432
    {
6433
        //Real question count
6434
        $question_count = 0;
6435
        $question_list = $this->get_question_list();
6436
        if (!empty($question_list)) {
6437
            $question_count = count($question_list);
6438
        }
6439
        return $question_count;
6440
    }
6441
6442
    function get_exercise_list_ordered()
6443
    {
6444
        $table_exercise_order = Database::get_course_table(TABLE_QUIZ_ORDER);
6445
        $course_id = api_get_course_int_id();
6446
        $session_id = api_get_session_id();
6447
        $sql = "SELECT exercise_id, exercise_order
6448
                FROM $table_exercise_order
6449
                WHERE c_id = $course_id AND session_id = $session_id
6450
                ORDER BY exercise_order";
6451
        $result = Database::query($sql);
6452
        $list = array();
6453
        if (Database::num_rows($result)) {
6454
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6455
                $list[$row['exercise_order']] = $row['exercise_id'];
6456
            }
6457
        }
6458
        return $list;
6459
    }
6460
6461
    /**
6462
     * Get categories added in the exercise--category matrix
6463
     * @return bool
6464
     */
6465
    public function get_categories_in_exercise()
6466
    {
6467
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6468
        if (!empty($this->id)) {
6469
            $sql = "SELECT * FROM $table
6470
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6471
            $result = Database::query($sql);
6472
            $list = array();
6473
            if (Database::num_rows($result)) {
6474
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6475
                    $list[$row['category_id']] = $row;
6476
                }
6477
                return $list;
6478
            }
6479
        }
6480
        return false;
6481
    }
6482
6483
    /**
6484
     * @param null $order
6485
     * @return bool
6486
     */
6487
    public function get_categories_with_name_in_exercise($order = null)
6488
    {
6489
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6490
        $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
6491
        $sql = "SELECT * FROM $table qc
6492
                INNER JOIN $table_category c
6493
                ON (category_id = c.iid)
6494
                WHERE exercise_id = {$this->id} AND qc.c_id = {$this->course_id} ";
6495
        if (!empty($order)) {
6496
            $sql .= "ORDER BY $order ";
6497
        }
6498
        $result = Database::query($sql);
6499
        if (Database::num_rows($result)) {
6500
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6501
                $list[$row['category_id']] = $row;
6502
            }
6503
            return $list;
6504
        }
6505
        return false;
6506
    }
6507
6508
    /**
6509
     * Get total number of question that will be parsed when using the category/exercise
6510
     */
6511 View Code Duplication
    public function getNumberQuestionExerciseCategory()
6512
    {
6513
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6514
        if (!empty($this->id)) {
6515
            $sql = "SELECT SUM(count_questions) count_questions
6516
                    FROM $table
6517
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6518
            $result = Database::query($sql);
6519
            if (Database::num_rows($result)) {
6520
                $row = Database::fetch_array($result);
6521
                return $row['count_questions'];
6522
            }
6523
        }
6524
        return 0;
6525
    }
6526
6527
    /**
6528
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table
6529
     * @param array $categories
6530
     */
6531
    public function save_categories_in_exercise($categories)
6532
    {
6533
        if (!empty($categories) && !empty($this->id)) {
6534
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6535
            $sql = "DELETE FROM $table
6536
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6537
            Database::query($sql);
6538
            if (!empty($categories)) {
6539
                foreach ($categories as $category_id => $count_questions) {
6540
                    $params = array(
6541
                        'c_id' => $this->course_id,
6542
                        'exercise_id' => $this->id,
6543
                        'category_id' => $category_id,
6544
                        'count_questions' => $count_questions
6545
                    );
6546
                    Database::insert($table, $params);
6547
                }
6548
            }
6549
        }
6550
    }
6551
6552
    /**
6553
     * @param array $questionList
6554
     * @param int $currentQuestion
6555
     * @param array $conditions
6556
     * @param string $link
6557
     * @return string
6558
     */
6559
    public function progressExercisePaginationBar($questionList, $currentQuestion, $conditions, $link)
6560
    {
6561
        $mediaQuestions = $this->getMediaList();
6562
6563
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6564
        $counter = 0;
6565
        $nextValue = 0;
6566
        $wasMedia = false;
6567
        $before = 0;
6568
        $counterNoMedias = 0;
6569
        foreach ($questionList as $questionId) {
6570
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6571
6572
            if (!empty($nextValue)) {
6573
                if ($wasMedia) {
6574
                    $nextValue = $nextValue - $before + 1;
6575
                }
6576
            }
6577
6578
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6579
                $fixedValue = $counterNoMedias;
6580
6581
                $html .= Display::progressPaginationBar(
6582
                    $nextValue,
6583
                    $mediaQuestions[$questionId],
6584
                    $currentQuestion,
6585
                    $fixedValue,
6586
                    $conditions,
6587
                    $link,
6588
                    true,
6589
                    true
6590
                );
6591
6592
                $counter += count($mediaQuestions[$questionId]) - 1 ;
6593
                $before = count($questionList);
6594
                $wasMedia = true;
6595
                $nextValue += count($questionList);
6596
            } else {
6597
                $html .= Display::parsePaginationItem($questionId, $isCurrent, $conditions, $link, $counter);
6598
                $counter++;
6599
                $nextValue++;
6600
                $wasMedia = false;
6601
            }
6602
            $counterNoMedias++;
6603
        }
6604
        $html .= '</ul></div>';
6605
        return $html;
6606
    }
6607
6608
6609
    /**
6610
     *  Shows a list of numbers that represents the question to answer in a exercise
6611
     *
6612
     * @param array $categories
6613
     * @param int $current
6614
     * @param array $conditions
6615
     * @param string $link
6616
     * @return string
6617
     */
6618
    public function progressExercisePaginationBarWithCategories(
6619
        $categories,
6620
        $current,
6621
        $conditions = array(),
6622
        $link = null
6623
    ) {
6624
        $html = null;
6625
        $counterNoMedias = 0;
6626
        $nextValue = 0;
6627
        $wasMedia = false;
6628
        $before = 0;
6629
6630
        if (!empty($categories)) {
6631
            $selectionType = $this->getQuestionSelectionType();
6632
            $useRootAsCategoryTitle = false;
6633
6634
            // Grouping questions per parent category see BT#6540
6635
6636
            if (in_array(
6637
                $selectionType,
6638
                array(
6639
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
6640
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM
6641
                )
6642
            )) {
6643
                $useRootAsCategoryTitle = true;
6644
            }
6645
6646
            // If the exercise is set to only show the titles of the categories
6647
            // at the root of the tree, then pre-order the categories tree by
6648
            // removing children and summing their questions into the parent
6649
            // categories
6650
6651
            if ($useRootAsCategoryTitle) {
6652
                // The new categories list starts empty
6653
                $newCategoryList = array();
6654
                foreach ($categories as $category) {
6655
                    $rootElement = $category['root'];
6656
6657
                    if (isset($category['parent_info'])) {
6658
                        $rootElement = $category['parent_info']['id'];
6659
                    }
6660
6661
                    //$rootElement = $category['id'];
6662
                    // If the current category's ancestor was never seen
6663
                    // before, then declare it and assign the current
6664
                    // category to it.
6665
                    if (!isset($newCategoryList[$rootElement])) {
6666
                        $newCategoryList[$rootElement] = $category;
6667
                    } else {
6668
                        // If it was already seen, then merge the previous with
6669
                        // the current category
6670
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
6671
                        $category['question_list'] = array_merge($oldQuestionList , $category['question_list']);
6672
                        $newCategoryList[$rootElement] = $category;
6673
                    }
6674
                }
6675
                // Now use the newly built categories list, with only parents
6676
                $categories = $newCategoryList;
6677
            }
6678
6679
            foreach ($categories as $category) {
6680
                $questionList = $category['question_list'];
6681
                // Check if in this category there questions added in a media
6682
                $mediaQuestionId = $category['media_question'];
6683
                $isMedia = false;
6684
                $fixedValue = null;
6685
6686
                // Media exists!
6687
                if ($mediaQuestionId != 999) {
6688
                    $isMedia = true;
6689
                    $fixedValue = $counterNoMedias;
6690
                }
6691
6692
                //$categoryName = $category['path']; << show the path
6693
                $categoryName = $category['name'];
6694
6695
                if ($useRootAsCategoryTitle) {
6696
                    if (isset($category['parent_info'])) {
6697
                        $categoryName  = $category['parent_info']['title'];
6698
                    }
6699
                }
6700
                $html .= '<div class="row">';
6701
                $html .= '<div class="span2">'.$categoryName.'</div>';
6702
                $html .= '<div class="span8">';
6703
6704
                if (!empty($nextValue)) {
6705
                    if ($wasMedia) {
6706
                        $nextValue = $nextValue - $before + 1;
6707
                    }
6708
                }
6709
                $html .= Display::progressPaginationBar(
6710
                    $nextValue,
6711
                    $questionList,
6712
                    $current,
6713
                    $fixedValue,
6714
                    $conditions,
6715
                    $link,
6716
                    $isMedia,
6717
                    true
6718
                );
6719
                $html .= '</div>';
6720
                $html .= '</div>';
6721
6722
                if ($mediaQuestionId == 999) {
6723
                    $counterNoMedias += count($questionList);
6724
                } else {
6725
                    $counterNoMedias++;
6726
                }
6727
6728
                $nextValue += count($questionList);
6729
                $before = count($questionList);
6730
6731
                if ($mediaQuestionId != 999) {
6732
                    $wasMedia = true;
6733
                } else {
6734
                    $wasMedia = false;
6735
                }
6736
6737
            }
6738
        }
6739
        return $html;
6740
    }
6741
6742
    /**
6743
     * Renders a question list
6744
     *
6745
     * @param array $questionList (with media questions compressed)
6746
     * @param int $currentQuestion
6747
     * @param array $exerciseResult
6748
     * @param array $attemptList
6749
     * @param array $remindList
6750
     */
6751
    public function renderQuestionList($questionList, $currentQuestion, $exerciseResult, $attemptList, $remindList)
6752
    {
6753
        $mediaQuestions = $this->getMediaList();
6754
        $i = 0;
6755
6756
        // Normal question list render (medias compressed)
6757
        foreach ($questionList as $questionId) {
6758
            $i++;
6759
            // For sequential exercises
6760
6761
            if ($this->type == ONE_PER_PAGE) {
6762
                // If it is not the right question, goes to the next loop iteration
6763
                if ($currentQuestion != $i) {
6764
                    continue;
6765
                } else {
6766
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
6767
                        // if the user has already answered this question
6768
                        if (isset($exerciseResult[$questionId])) {
6769
                            echo Display::return_message(get_lang('AlreadyAnswered'), 'normal');
6770
                            break;
6771
                        }
6772
                    }
6773
                }
6774
            }
6775
6776
            // The $questionList contains the media id we check if this questionId is a media question type
6777
6778
            if (isset($mediaQuestions[$questionId]) && $mediaQuestions[$questionId] != 999) {
6779
6780
                // The question belongs to a media
6781
                $mediaQuestionList = $mediaQuestions[$questionId];
6782
                $objQuestionTmp = Question::read($questionId);
6783
6784
                $counter = 1;
6785
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
6786
                    echo $objQuestionTmp->show_media_content();
6787
6788
                    $countQuestionsInsideMedia = count($mediaQuestionList);
6789
6790
                    // Show questions that belongs to a media
6791
                    if (!empty($mediaQuestionList)) {
6792
                        // In order to parse media questions we use letters a, b, c, etc.
6793
                        $letterCounter = 97;
6794
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
6795
                            $isLastQuestionInMedia = false;
6796
                            if ($counter == $countQuestionsInsideMedia) {
6797
                                $isLastQuestionInMedia = true;
6798
                            }
6799
                            $this->renderQuestion(
6800
                                $questionIdInsideMedia,
6801
                                $attemptList,
6802
                                $remindList,
6803
                                chr($letterCounter),
6804
                                $currentQuestion,
6805
                                $mediaQuestionList,
6806
                                $isLastQuestionInMedia,
6807
                                $questionList
6808
                            );
6809
                            $letterCounter++;
6810
                            $counter++;
6811
                        }
6812
                    }
6813
                } else {
6814
                    $this->renderQuestion(
6815
                        $questionId,
6816
                        $attemptList,
6817
                        $remindList,
6818
                        $i,
6819
                        $currentQuestion,
6820
                        null,
6821
                        null,
6822
                        $questionList
6823
                    );
6824
                    $i++;
6825
                }
6826
            } else {
6827
                // Normal question render.
6828
                $this->renderQuestion($questionId, $attemptList, $remindList, $i, $currentQuestion, null, null, $questionList);
6829
            }
6830
6831
            // For sequential exercises.
6832
            if ($this->type == ONE_PER_PAGE) {
6833
                // quits the loop
6834
                break;
6835
            }
6836
        }
6837
        // end foreach()
6838
6839
        if ($this->type == ALL_ON_ONE_PAGE) {
6840
            $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 6757. 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...
6841
            echo Display::div($exercise_actions, array('class'=>'exercise_actions'));
6842
        }
6843
    }
6844
6845
    /**
6846
     * @param int $questionId
6847
     * @param array $attemptList
6848
     * @param array $remindList
6849
     * @param int $i
6850
     * @param int $current_question
6851
     * @param array $questions_in_media
6852
     * @param bool $last_question_in_media
6853
     * @param array $realQuestionList
6854
     * @param bool $generateJS
6855
     * @return null
6856
     */
6857
    public function renderQuestion(
6858
        $questionId,
6859
        $attemptList,
6860
        $remindList,
6861
        $i,
6862
        $current_question,
6863
        $questions_in_media = array(),
6864
        $last_question_in_media = false,
6865
        $realQuestionList,
6866
        $generateJS = true
6867
    ) {
6868
6869
        // With this option on the question is loaded via AJAX
6870
        //$generateJS = true;
6871
        //$this->loadQuestionAJAX = true;
6872
6873
        if ($generateJS && $this->loadQuestionAJAX) {
6874
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
6875
            $params = array(
6876
                'questionId' => $questionId,
6877
                'attemptList'=> $attemptList,
6878
                'remindList' => $remindList,
6879
                'i' => $i,
6880
                'current_question' => $current_question,
6881
                'questions_in_media' => $questions_in_media,
6882
                'last_question_in_media' => $last_question_in_media
6883
            );
6884
            $params = json_encode($params);
6885
6886
            $script = '<script>
6887
            $(function(){
6888
                var params = '.$params.';
6889
                $.ajax({
6890
                    type: "GET",
6891
                    async: false,
6892
                    data: params,
6893
                    url: "'.$url.'",
6894
                    success: function(return_value) {
6895
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
6896
                    }
6897
                });
6898
            });
6899
            </script>
6900
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
6901
            echo $script;
6902
        } else {
6903
6904
            global $origin;
6905
            $question_obj = Question::read($questionId);
6906
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
6907
6908
            $remind_highlight = null;
6909
6910
            //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
6911
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
6912
                $remind_highlight = 'no_remind_highlight';
6913
                if (in_array($question_obj->type, Question::question_type_no_review())) {
6914
                    return null;
6915
                }
6916
            }
6917
6918
            $attributes = array('id' =>'remind_list['.$questionId.']');
6919
            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...
6920
                //$attributes['checked'] = 1;
6921
                //$remind_highlight = ' remind_highlight ';
6922
            }
6923
6924
            // Showing the question
6925
6926
            $exercise_actions  = null;
6927
6928
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
6929
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
6930
6931
            // Shows the question + possible answers
6932
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
6933
            echo $this->showQuestion($question_obj, false, $origin, $i, $showTitle, false, $user_choice, false, null, false, $this->getModelType(), $this->categoryMinusOne);
6934
6935
            // Button save and continue
6936 View Code Duplication
            switch ($this->type) {
6937
                case ONE_PER_PAGE:
6938
                    $exercise_actions .= $this->show_button($questionId, $current_question, null, $remindList);
6939
                    break;
6940
                case ALL_ON_ONE_PAGE:
6941
                    $button = [
6942
                        Display::button(
6943
                            'save_now',
6944
                            get_lang('SaveForNow'),
6945
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
6946
                        ),
6947
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>'
6948
                    ];
6949
                    $exercise_actions .= Display::div(
6950
                        implode(PHP_EOL, $button),
6951
                        array('class'=>'exercise_save_now_button')
6952
                    );
6953
                    break;
6954
            }
6955
6956
            if (!empty($questions_in_media)) {
6957
                $count_of_questions_inside_media = count($questions_in_media);
6958
                if ($count_of_questions_inside_media > 1) {
6959
                    $button = [
6960
                        Display::button(
6961
                            'save_now',
6962
                            get_lang('SaveForNow'),
6963
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
6964
                        ),
6965
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;'
6966
                    ];
6967
                    $exercise_actions = Display::div(
6968
                        implode(PHP_EOL, $button),
6969
                        array('class'=>'exercise_save_now_button')
6970
                    );
6971
                }
6972
6973
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
6974
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
6975
                }
6976
            }
6977
6978
            // Checkbox review answers
6979
            if ($this->review_answers && !in_array($question_obj->type, Question::question_type_no_review())) {
6980
                $remind_question_div = Display::tag('label', Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), array('class' => 'checkbox', 'for' =>'remind_list['.$questionId.']'));
6981
                $exercise_actions   .= Display::div($remind_question_div, array('class'=>'exercise_save_now_button'));
6982
            }
6983
6984
            echo Display::div(' ', array('class'=>'clear'));
6985
6986
            $paginationCounter = null;
6987
            if ($this->type == ONE_PER_PAGE) {
6988
                if (empty($questions_in_media)) {
6989
                    $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6990
                } else {
6991
                    if ($last_question_in_media) {
6992
                        $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6993
                    }
6994
                }
6995
            }
6996
6997
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
6998
            echo Display::div($exercise_actions, array('class'=>'form-actions'));
6999
            echo '</div>';
7000
        }
7001
    }
7002
7003
    /**
7004
     * @param int $exeId
7005
     * @return array
7006
     */
7007
    public function returnQuestionListByAttempt($exeId)
7008
    {
7009
        return $this->displayQuestionListByAttempt($exeId, false, true);
7010
    }
7011
7012
    /**
7013
     * Display the exercise results
7014
     * @param int  $exe_id
7015
     * @param bool $saveUserResult save users results (true) or just show the results (false)
7016
     * @param bool $returnExerciseResult return array with exercise result info
7017
     * @return mixed
7018
     */
7019
    public function displayQuestionListByAttempt($exe_id, $saveUserResult = false, $returnExerciseResult = false)
7020
    {
7021
        global $origin, $debug;
7022
7023
        //Getting attempt info
7024
        $exercise_stat_info = $this->getStatTrackExerciseInfoByExeId($exe_id);
7025
7026
        //Getting question list
7027
        $question_list = array();
7028
        if (!empty($exercise_stat_info['data_tracking'])) {
7029
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
7030
        } else {
7031
            //Try getting the question list only if save result is off
7032
            if ($saveUserResult == false) {
7033
                $question_list = $this->selectQuestionList();
7034
            }
7035
            error_log("Data tracking is empty! exe_id: $exe_id");
7036
        }
7037
7038
        $counter = 1;
7039
        $total_score = 0;
7040
        $total_weight = 0;
7041
7042
        $exercise_content = null;
7043
7044
        //Hide results
7045
        $show_results = false;
7046
        $show_only_score = false;
7047
7048
        if ($this->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
7049
            $show_results = true;
7050
        }
7051
7052
        $showScoreOptions = [
7053
            RESULT_DISABLE_SHOW_SCORE_ONLY,
7054
            RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
7055
            RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT
7056
        ];
7057
7058
        if (in_array($this->results_disabled, $showScoreOptions)) {
7059
            $show_only_score = true;
7060
        }
7061
7062
        if ($show_results || $show_only_score) {
7063
            $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
7064
            // Shows exercise header.
7065
            echo $this->show_exercise_result_header(
7066
                $user_info['complete_name'],
7067
                api_convert_and_format_date($exercise_stat_info['start_date'], DATE_TIME_FORMAT_LONG),
7068
                $exercise_stat_info['duration']
7069
            );
7070
        }
7071
7072
        // Display text when test is finished #4074 and for LP #4227
7073
        $end_of_message = $this->selectTextWhenFinished();
7074
        if (!empty($end_of_message)) {
7075
            echo Display::return_message($end_of_message, 'normal', false);
7076
            echo "<div class='clear'>&nbsp;</div>";
7077
        }
7078
7079
        $question_list_answers = array();
7080
        $media_list = array();
7081
        $category_list = array();
7082
        $tempParentId = null;
7083
        $mediaCounter = 0;
7084
        $exerciseResultInfo = array();
7085
7086
        // Loop over all question to show results for each of them, one by one
7087
        if (!empty($question_list)) {
7088
            if ($debug) {
7089
                error_log('Looping question_list '.print_r($question_list, 1));
7090
            }
7091
7092
            foreach ($question_list as $questionId) {
7093
                // Creates a temporary Question object
7094
                $objQuestionTmp = Question::read($questionId);
7095
7096
                // This variable comes from exercise_submit_modal.php
7097
                ob_start();
7098
                $hotspot_delineation_result = null;
7099
7100
                // We're inside *one* question. Go through each possible answer for this question
7101
                $result = $this->manageAnswers(
7102
                    $exercise_stat_info['exe_id'],
7103
                    $questionId,
7104
                    null,
7105
                    'exercise_result',
7106
                    array(),
7107
                    $saveUserResult,
7108
                    true,
7109
                    $show_results,
7110
                    $hotspot_delineation_result
7111
                );
7112
7113
                if (empty($result)) {
7114
                    continue;
7115
                }
7116
7117
                $total_score += $result['score'];
7118
                $total_weight += $result['weight'];
7119
7120
                $question_list_answers[] = array(
7121
                    'question' => $result['open_question'],
7122
                    'answer' => $result['open_answer'],
7123
                    'answer_type' => $result['answer_type']
7124
                );
7125
7126
                $my_total_score = $result['score'];
7127
                $my_total_weight = $result['weight'];
7128
7129
                // Category report
7130
                $category_was_added_for_this_test = false;
7131
                $categoryExerciseList = $this->getListOfCategoriesWithQuestionForTest();
7132
7133
                $category_list = array();
7134
                if (isset($categoryExerciseList) && !empty($categoryExerciseList)) {
7135
                    foreach ($categoryExerciseList as $category_id => $categoryInfo) {
7136
                        if (!isset($category_list[$category_id])) {
7137
                            $category_list[$category_id] = array();
7138
                            $category_list[$category_id]['score'] = 0;
7139
                            $category_list[$category_id]['total'] = 0;
7140
                        }
7141
                        $category_list[$category_id]['score'] += $my_total_score;
7142
                        $category_list[$category_id]['total'] += $my_total_weight;
7143
                        $category_was_added_for_this_test = true;
7144
                    }
7145
                }
7146
7147
                // No category for this question!
7148
                if ($category_was_added_for_this_test == false) {
7149
                    if (!isset($category_list['none'])) {
7150
                        $category_list['none'] = array();
7151
                        $category_list['none']['score'] = 0;
7152
                        $category_list['none']['total'] = 0;
7153
                    }
7154
7155
                    $category_list['none']['score'] += $my_total_score;
7156
                    $category_list['none']['total'] += $my_total_weight;
7157
                }
7158
7159
                if ($this->selectPropagateNeg() == 0 && $my_total_score < 0) {
7160
                    $my_total_score = 0;
7161
                }
7162
7163
                $comnt = null;
7164 View Code Duplication
                if ($show_results) {
7165
                    $comnt = get_comments($exe_id, $questionId);
7166
                    if (!empty($comnt)) {
7167
                        echo '<b>'.get_lang('Feedback').'</b>';
7168
                        echo '<div id="question_feedback">'.$comnt.'</div>';
7169
                    }
7170
                }
7171
7172
                $score = array();
7173
                $score['result'] = get_lang('Score')." : ".
7174
                    ExerciseLib::show_score(
7175
                        $my_total_score,
7176
                        $my_total_weight,
7177
                        false,
7178
                        true
7179
                    );
7180
                $score['pass'] = $my_total_score >= $my_total_weight ? true : false;
7181
                $score['score'] = $my_total_score;
7182
                $score['weight'] = $my_total_weight;
7183
                $score['comments'] = $comnt;
7184
7185
                $exerciseResultInfo[$questionId]['score'] = $score;
7186
                $exerciseResultInfo[$questionId]['details'] = $result;
7187
7188
                // If no results we hide the results
7189
                if ($show_results == false) {
7190
                    $score = array();
7191
                }
7192
                $contents = ob_get_clean();
7193
7194
                $question_content = '<div class="question_row">';
7195
7196
                if ($show_results) {
7197
                    $show_media = false;
7198
                    $counterToShow = $counter;
7199
                    if ($objQuestionTmp->parent_id != 0) {
7200
7201
                        if (!in_array($objQuestionTmp->parent_id, $media_list)) {
7202
                            $media_list[] = $objQuestionTmp->parent_id;
7203
                            $show_media = true;
7204
                        }
7205
                        if ($tempParentId == $objQuestionTmp->parent_id) {
7206
                            $mediaCounter++;
7207
                        } else {
7208
                            $mediaCounter = 0;
7209
                        }
7210
                        $counterToShow = chr(97 + $mediaCounter);
7211
                        $tempParentId = $objQuestionTmp->parent_id;
7212
                    }
7213
7214
                    // Shows question title an description.
7215
                    $question_content .= $objQuestionTmp->return_header(
7216
                        null,
7217
                        $counterToShow,
7218
                        $score,
7219
                        $show_media,
7220
                        $this->getHideQuestionTitle()
7221
                    );
7222
7223
                    // display question category, if any
7224
                    $question_content .= TestCategory::getCategoryNamesForQuestion(
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...
7225
                        $questionId,
7226
                        null,
7227
                        true,
7228
                        $this->categoryMinusOne
7229
                    );
7230
                }
7231
                $counter++;
7232
                $question_content .= $contents;
7233
                $question_content .= '</div>';
7234
                $exercise_content .= $question_content;
7235
            } // end foreach() block that loops over all questions
7236
        }
7237
7238
        $total_score_text = null;
7239
7240
        if ($returnExerciseResult) {
7241
            return $exerciseResultInfo;
7242
        }
7243
7244
        if ($origin != 'learnpath') {
7245
            if ($show_results || $show_only_score) {
7246
                $total_score_text .= $this->get_question_ribbon($total_score, $total_weight, true);
7247
            }
7248
        }
7249
7250 View Code Duplication
        if (!empty($category_list) && ($show_results || $show_only_score)) {
7251
            //Adding total
7252
            $category_list['total'] = array('score' => $total_score, 'total' => $total_weight);
7253
            echo TestCategory::get_stats_table_by_attempt($this->id, $category_list, $this->categoryMinusOne);
7254
        }
7255
7256
        echo $total_score_text;
7257
        echo $exercise_content;
7258
7259
        if (!$show_only_score) {
7260
            echo $total_score_text;
7261
        }
7262
7263
        if ($saveUserResult) {
7264
7265
            // Tracking of results
7266
            $learnpath_id = $exercise_stat_info['orig_lp_id'];
7267
            $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
7268
            $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
7269
7270 View Code Duplication
            if (api_is_allowed_to_session_edit()) {
7271
                Event::update_event_exercise(
7272
                    $exercise_stat_info['exe_id'],
7273
                    $this->selectId(),
7274
                    $total_score,
7275
                    $total_weight,
7276
                    api_get_session_id(),
7277
                    $learnpath_id,
7278
                    $learnpath_item_id,
7279
                    $learnpath_item_view_id,
7280
                    $exercise_stat_info['exe_duration'],
7281
                    '',
7282
                    array()
7283
                );
7284
            }
7285
7286
            // Send notification.
7287
            if (!api_is_allowed_to_edit(null, true)) {
7288
                $isSuccess = ExerciseLib::is_success_exercise_result($total_score, $total_weight, $this->selectPassPercentage());
7289
                $this->sendCustomNotification($exe_id, $exerciseResultInfo, $isSuccess);
7290
                $this->sendNotificationForOpenQuestions($question_list_answers, $origin, $exe_id);
7291
                $this->sendNotificationForOralQuestions($question_list_answers, $origin, $exe_id);
7292
            }
7293
        }
7294
    }
7295
7296
    /**
7297
     * Returns an HTML ribbon to show on top of the exercise result, with
7298
     * colouring depending on the success or failure of the student
7299
     * @param integer $score
7300
     * @param integer $weight
7301
     * @param bool $check_pass_percentage
7302
     * @return string
7303
     */
7304
    public function get_question_ribbon($score, $weight, $check_pass_percentage = false)
7305
    {
7306
        $eventMessage = null;
7307
        $ribbon = '<div class="question_row">';
7308
        $ribbon .= '<div class="ribbon">';
7309
        if ($check_pass_percentage) {
7310
            $is_success = ExerciseLib::is_success_exercise_result($score, $weight, $this->selectPassPercentage());
7311
            // Color the final test score if pass_percentage activated
7312
            $ribbon_total_success_or_error = "";
7313
            if (ExerciseLib::is_pass_pourcentage_enabled($this->selectPassPercentage())) {
7314
                if ($is_success) {
7315
                    $eventMessage = $this->getOnSuccessMessage();
7316
                    $ribbon_total_success_or_error = ' ribbon-total-success';
7317
                } else {
7318
                    $eventMessage = $this->getOnFailedMessage();
7319
                    $ribbon_total_success_or_error = ' ribbon-total-error';
7320
                }
7321
            }
7322
            $ribbon .= '<div class="rib rib-total '.$ribbon_total_success_or_error.'">';
7323
        } else {
7324
            $ribbon .= '<div class="rib rib-total">';
7325
        }
7326
        $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
7327
        $ribbon .= ExerciseLib::show_score($score, $weight, false, true);
7328
        $ribbon .= '</h3>';
7329
        $ribbon .= '</div>';
7330
7331
        if ($check_pass_percentage) {
7332
            $ribbon .= ExerciseLib::show_success_message($score, $weight, $this->selectPassPercentage());
7333
        }
7334
        $ribbon .= '</div>';
7335
        $ribbon .= '</div>';
7336
7337
        $ribbon .= $eventMessage;
7338
7339
        return $ribbon;
7340
    }
7341
7342
    /**
7343
     * Returns an array of categories details for the questions of the current
7344
     * exercise.
7345
     * @return array
7346
     */
7347
    public function getQuestionWithCategories()
7348
    {
7349
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
7350
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
7351
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7352
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7353
        $sql = "SELECT DISTINCT cat.*
7354
                FROM $TBL_EXERCICE_QUESTION e
7355
                INNER JOIN $TBL_QUESTIONS q
7356
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7357
                INNER JOIN $categoryRelTable catRel
7358
                ON (catRel.question_id = e.question_id)
7359
                INNER JOIN $categoryTable cat
7360
                ON (cat.id = catRel.category_id)
7361
                WHERE
7362
                  e.c_id = {$this->course_id} AND
7363
                  e.exercice_id	= ".intval($this->id);
7364
7365
        $result = Database::query($sql);
7366
        $categoriesInExercise = array();
7367
        if (Database::num_rows($result)) {
7368
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
7369
        }
7370
7371
        return $categoriesInExercise;
7372
    }
7373
7374
    /**
7375
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option
7376
     */
7377
    public function get_max_score()
7378
    {
7379
        $out_max_score = 0;
7380
        // list of question's id !!! the array key start at 1 !!!
7381
        $questionList = $this->selectQuestionList(true);
7382
7383
        // test is randomQuestions - see field random of test
7384
        if ($this->random > 0 && $this->randomByCat == 0) {
7385
            $numberRandomQuestions = $this->random;
7386
            $questionScoreList = array();
7387
            for ($i = 1; $i <= count($questionList); $i++) {
7388
                if (isset($questionList[$i])) {
7389
                    $tmpobj_question = Question::read($questionList[$i]);
7390
                    if (is_object($tmpobj_question)) {
7391
                        $questionScoreList[] = $tmpobj_question->weighting;
7392
                    }
7393
                }
7394
            }
7395
            rsort($questionScoreList);
7396
            // add the first $numberRandomQuestions value of score array to get max_score
7397
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
7398
                $out_max_score += $questionScoreList[$i];
7399
            }
7400
        } else if ($this->random > 0 && $this->randomByCat > 0) {
7401
            // test is random by category
7402
            // get the $numberRandomQuestions best score question of each category
7403
7404
            $numberRandomQuestions = $this->random;
7405
            $tab_categories_scores = array();
7406
            for ($i = 1; $i <= count($questionList); $i++) {
7407
                $question_category_id = TestCategory::getCategoryForQuestion($questionList[$i]);
7408
                if (!is_array($tab_categories_scores[$question_category_id])) {
7409
                    $tab_categories_scores[$question_category_id] = array();
7410
                }
7411
                $tmpobj_question = Question::read($questionList[$i]);
7412
                $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
7413
            }
7414
7415
            // here we've got an array with first key, the category_id, second key, score of question for this cat
7416
            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...
7417
                rsort($tab_scores);
7418
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
7419
                    $out_max_score += $tab_scores[$i];
7420
                }
7421
            }
7422
        } else {
7423
            // standard test, just add each question score
7424
            foreach ($questionList as $questionId) {
7425
                $question = Question::read($questionId, $this->course_id);
7426
                $out_max_score += $question->weighting;
7427
            }
7428
        }
7429
7430
        return $out_max_score;
7431
    }
7432
7433
    /**
7434
    * @return string
7435
    */
7436
    public function get_formated_title()
7437
    {
7438
        return api_html_entity_decode($this->selectTitle());
7439
    }
7440
7441
    /**
7442
     * @param $in_title
7443
     * @return string
7444
     */
7445
    public static function get_formated_title_variable($in_title)
7446
    {
7447
        return api_html_entity_decode($in_title);
7448
    }
7449
7450
    /**
7451
     * @return string
7452
     */
7453
    public function format_title()
7454
    {
7455
        return api_htmlentities($this->title);
7456
    }
7457
7458
    /**
7459
     * @param $in_title
7460
     * @return string
7461
     */
7462
    public static function format_title_variable($in_title)
7463
    {
7464
        return api_htmlentities($in_title);
7465
    }
7466
7467
    /**
7468
     * @param int $courseId
7469
     * @param int $sessionId
7470
     * @return array exercises
7471
     */
7472 View Code Duplication
    public function getExercisesByCourseSession($courseId, $sessionId)
7473
    {
7474
        $courseId = intval($courseId);
7475
        $sessionId = intval($sessionId);
7476
7477
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7478
        $sql = "SELECT * FROM $tbl_quiz cq
7479
                WHERE
7480
                    cq.c_id = %s AND
7481
                    (cq.session_id = %s OR cq.session_id = 0) AND
7482
                    cq.active = 0
7483
                ORDER BY cq.id";
7484
        $sql = sprintf($sql, $courseId, $sessionId);
7485
7486
        $result = Database::query($sql);
7487
7488
        $rows = array();
7489
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7490
            $rows[] = $row;
7491
        }
7492
7493
        return $rows;
7494
    }
7495
7496
    /**
7497
     *
7498
     * @param int $courseId
7499
     * @param int $sessionId
7500
     * @param array $quizId
7501
     * @return array exercises
7502
     */
7503
    public function getExerciseAndResult($courseId, $sessionId, $quizId = array())
7504
    {
7505
        if (empty($quizId)) {
7506
            return array();
7507
        }
7508
7509
        $sessionId = intval($sessionId);
7510
7511
        $ids = is_array($quizId) ? $quizId : array($quizId);
7512
        $ids = array_map('intval', $ids);
7513
        $ids = implode(',', $ids);
7514
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7515
        if ($sessionId != 0) {
7516
            $sql = "SELECT * FROM $track_exercises te
7517
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7518
              WHERE
7519
              te.id = %s AND
7520
              te.session_id = %s AND
7521
              cq.id IN (%s)
7522
              ORDER BY cq.id";
7523
7524
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
7525
        } else {
7526
            $sql = "SELECT * FROM $track_exercises te
7527
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7528
              WHERE
7529
              te.id = %s AND
7530
              cq.id IN (%s)
7531
              ORDER BY cq.id";
7532
            $sql = sprintf($sql, $courseId, $ids);
7533
        }
7534
        $result = Database::query($sql);
7535
        $rows = array();
7536
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7537
            $rows[] = $row;
7538
        }
7539
7540
        return $rows;
7541
    }
7542
7543
    /**
7544
     * @param $exeId
7545
     * @param $exercise_stat_info
7546
     * @param $remindList
7547
     * @param $currentQuestion
7548
     * @return int|null
7549
     */
7550
    public static function getNextQuestionId($exeId, $exercise_stat_info, $remindList, $currentQuestion)
7551
    {
7552
        $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete');
7553
7554
        if (isset($result[$exeId])) {
7555
            $result = $result[$exeId];
7556
        } else {
7557
            return null;
7558
        }
7559
7560
        $data_tracking  = $exercise_stat_info['data_tracking'];
7561
        $data_tracking  = explode(',', $data_tracking);
7562
7563
        // if this is the final question do nothing.
7564
        if ($currentQuestion == count($data_tracking)) {
7565
            return null;
7566
        }
7567
7568
        $currentQuestion = $currentQuestion - 1;
7569
7570
        if (!empty($result['question_list'])) {
7571
            $answeredQuestions = array();
7572
7573
            foreach ($result['question_list'] as $question) {
7574
                if (!empty($question['answer'])) {
7575
                    $answeredQuestions[] = $question['question_id'];
7576
                }
7577
            }
7578
7579
            // Checking answered questions
7580
7581
            $counterAnsweredQuestions = 0;
7582
            foreach ($data_tracking as $questionId) {
7583
                if (!in_array($questionId, $answeredQuestions)) {
7584
                    if ($currentQuestion != $counterAnsweredQuestions) {
7585
                        break;
7586
                    }
7587
                }
7588
                $counterAnsweredQuestions++;
7589
            }
7590
7591
            $counterRemindListQuestions = 0;
7592
            // Checking questions saved in the reminder list
7593
7594
            if (!empty($remindList)) {
7595
                foreach ($data_tracking as $questionId) {
7596
                    if (in_array($questionId, $remindList)) {
7597
                        // Skip the current question
7598
                        if ($currentQuestion != $counterRemindListQuestions) {
7599
                            break;
7600
                        }
7601
                    }
7602
                    $counterRemindListQuestions++;
7603
                }
7604
7605
                if ($counterRemindListQuestions < $currentQuestion) {
7606
                    return null;
7607
                }
7608
7609
                if (!empty($counterRemindListQuestions)) {
7610
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
7611
                        return $counterAnsweredQuestions;
7612
                    } else {
7613
                        return $counterRemindListQuestions;
7614
                    }
7615
                }
7616
            }
7617
7618
            return $counterAnsweredQuestions;
7619
        }
7620
    }
7621
7622
    /**
7623
     * Gets the position of a questionId in the question list
7624
     * @param $questionId
7625
     * @return int
7626
     */
7627
    public function getPositionInCompressedQuestionList($questionId)
7628
    {
7629
        $questionList = $this->getQuestionListWithMediasCompressed();
7630
        $mediaQuestions = $this->getMediaList();
7631
        $position = 1;
7632
        foreach ($questionList as $id) {
7633
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
7634
                $mediaQuestionList = $mediaQuestions[$id];
7635
                if (in_array($questionId, $mediaQuestionList)) {
7636
                    return $position;
7637
                } else {
7638
                    $position++;
7639
                }
7640
            } else {
7641
                if ($id == $questionId) {
7642
                    return $position;
7643
                } else {
7644
                    $position++;
7645
                }
7646
            }
7647
        }
7648
        return 1;
7649
    }
7650
7651
    /**
7652
     * Get the correct answers in all attempts
7653
     * @param int $learnPathId
7654
     * @param int $learnPathItemId
7655
     * @return array
7656
     */
7657
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
7658
    {
7659
        $attempts = Event::getExerciseResultsByUser(
7660
            api_get_user_id(),
7661
            $this->id,
7662
            api_get_course_int_id(),
7663
            api_get_session_id(),
7664
            $learnPathId,
7665
            $learnPathItemId,
7666
            'asc'
7667
        );
7668
7669
        $corrects = [];
7670
7671
        foreach ($attempts as $attempt) {
7672
            foreach ($attempt['question_list'] as $answers) {
7673
                foreach ($answers as $answer) {
7674
                    $objAnswer = new Answer($answer['question_id']);
7675
7676
                    switch ($objAnswer->getQuestionType()) {
7677
                        case FILL_IN_BLANKS:
7678
                            $isCorrect = FillBlanks::isCorrect($answer['answer']);
7679
                            break;
7680
                        case MATCHING:
7681
                            //no break
7682
                        case DRAGGABLE:
7683
                            //no break
7684
                        case MATCHING_DRAGGABLE:
7685
                            $isCorrect = Matching::isCorrect(
7686
                                $answer['position'],
7687
                                $answer['answer'],
7688
                                $answer['question_id']
7689
                            );
7690
                            break;
7691
                        default:
7692
                            $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
7693
                    }
7694
7695
                    if ($isCorrect) {
7696
                        $corrects[$answer['question_id']][] = $answer;
7697
                    }
7698
                }
7699
            }
7700
        }
7701
7702
        return $corrects;
7703
    }
7704
}
7705