Completed
Push — master ( 27e209...a08afa )
by Julito
186:04 queued 150:53
created

Exercise::updateTitle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3209
                                    if (isset($myRemindList[$i]) && $myRemindList[$i] == $question_id) {
3210
                                        $beforeId = isset($myRemindList[$i - 1]) ? $myRemindList[$i - 1] : null;
3211
                                        break;
3212
                                    }
3213
                                }
3214
3215
                                if (empty($beforeId)) {
3216
                                    $showPreview = false;
3217
                                } else {
3218
                                    $num = 0;
3219
                                    foreach ($this->questionList as $originalQuestionId) {
3220
                                        if ($originalQuestionId == $beforeId) {
3221
                                            break;
3222
                                        }
3223
                                        $num++;
3224
                                    }
3225
                                    $prev_question = $num;
3226
                                    //$question_id = $beforeId;
3227
                                }
3228
                            }
3229
3230
                            if ($showPreview) {
3231
                                $buttonList[] = Display::button(
3232
                                    'previous_question_and_save',
3233
                                    get_lang('PreviousQuestion'),
3234
                                    [
3235
                                        'type' => 'button',
3236
                                        'class' => 'btn btn-default',
3237
                                        'data-prev' => $prev_question,
3238
                                        'data-question' => $question_id
3239
                                    ]
3240
                                );
3241
                            }
3242
                        }
3243
                    }
3244
3245
                    // Next question
3246
                    if (!empty($questions_in_media)) {
3247
                        $buttonList[] = Display::button(
3248
                            'save_question_list',
3249
                            $label,
3250
                            [
3251
                                'type' => 'button',
3252
                                'class' => $class,
3253
                                'data-list' => implode(",", $questions_in_media)
3254
                            ]
3255
                        );
3256
                    } else {
3257
                        $buttonList[] = Display::button(
3258
                            'save_now',
3259
                            $label,
3260
                            ['type' => 'button', 'class' => $class, 'data-question' => $question_id]
3261
                        );
3262
                    }
3263
                    $buttonList[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
3264
3265
                    $html .= implode(PHP_EOL, $buttonList);
3266
                } else {
3267
                    if ($this->review_answers) {
3268
                        $all_label = get_lang('ReviewQuestions');
3269
                        $class = 'btn btn-success';
3270
                    } else {
3271
                        $all_label = get_lang('EndTest');
3272
                        $class = 'btn btn-warning';
3273
                    }
3274
                    // used to select it with jquery
3275
                    $class .= ' question-validate-btn';
3276
                    $buttonList[] = Display::button(
3277
                        'validate_all',
3278
                        $all_label,
3279
                        ['type' => 'button', 'class' => $class]
3280
                    );
3281
                    $buttonList[] = '&nbsp;'.Display::span(null, ['id' => 'save_all_response']);
3282
                    $html .= implode(PHP_EOL, $buttonList);
3283
                }
3284
            }
3285
        }
3286
3287
        return $html;
3288
    }
3289
3290
    /**
3291
     * So the time control will work
3292
     *
3293
     * @param string $time_left
3294
     * @return string
3295
     */
3296
    public function showTimeControlJS($time_left)
3297
    {
3298
        $time_left = intval($time_left);
3299
        return "<script>
3300
3301
            function get_expired_date_string(expired_time) {
3302
                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
3303
                var day, month, year, hours, minutes, seconds, date_string;
3304
                var obj_date = new Date(expired_time);
3305
                day     = obj_date.getDate();
3306
                if (day < 10) day = '0' + day;
3307
                    month   = obj_date.getMonth();
3308
                    year    = obj_date.getFullYear();
3309
                    hours   = obj_date.getHours();
3310
                if (hours < 10) hours = '0' + hours;
3311
                minutes = obj_date.getMinutes();
3312
                if (minutes < 10) minutes = '0' + minutes;
3313
                seconds = obj_date.getSeconds();
3314
                if (seconds < 10) seconds = '0' + seconds;
3315
                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
3316
                return date_string;
3317
            }
3318
3319
            function open_clock_warning() {
3320
                $('#clock_warning').dialog({
3321
                    modal:true,
3322
                    height:250,
3323
                    closeOnEscape: false,
3324
                    resizable: false,
3325
                    buttons: {
3326
                        '".addslashes(get_lang("EndTest"))."': function() {
3327
                            $('#clock_warning').dialog('close');
3328
                        }
3329
                    },
3330
                    close: function() {
3331
                        send_form();
3332
                    }
3333
                });
3334
                $('#clock_warning').dialog('open');
3335
3336
                $('#counter_to_redirect').epiclock({
3337
                    mode: $.epiclock.modes.countdown,
3338
                    offset: {seconds: 5},
3339
                    format: 's'
3340
                }).bind('timer', function () {
3341
                    send_form();
3342
                });
3343
            }
3344
3345
            function send_form() {
3346
                if ($('#exercise_form').length) {
3347
                    $('#exercise_form').submit();
3348
                } else {
3349
                    // In exercise_reminder.php
3350
                    final_submit();
3351
                }
3352
            }
3353
3354
            function onExpiredTimeExercise() {
3355
                $('#wrapper-clock').hide();
3356
                //$('#exercise_form').hide();
3357
                $('#expired-message-id').show();
3358
3359
                //Fixes bug #5263
3360
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3361
                open_clock_warning();
3362
            }
3363
3364
			$(document).ready(function() {
3365
				var current_time = new Date().getTime();
3366
                var time_left    = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
3367
				var expired_time = current_time + (time_left*1000);
3368
				var expired_date = get_expired_date_string(expired_time);
3369
3370
                $('#exercise_clock_warning').epiclock({
3371
                    mode: $.epiclock.modes.countdown,
3372
                    offset: {seconds: time_left},
3373
                    format: 'x:i:s',
3374
                    renderer: 'minute'
3375
                }).bind('timer', function () {
3376
                    onExpiredTimeExercise();
3377
                });
3378
	       		$('#submit_save').click(function () {});
3379
	    });
3380
	    </script>";
3381
    }
3382
3383
3384
    /**
3385
     * This function was originally found in the exercise_show.php
3386
     * @param int $exeId
3387
     * @param int $questionId
3388
     * @param int $choice the user selected
3389
     * @param string $from  function is called from 'exercise_show' or 'exercise_result'
3390
     * @param array $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates
3391
     * @param bool $saved_results save results in the DB or just show the reponse
3392
     * @param bool $from_database gets information from DB or from the current selection
3393
     * @param bool $show_result show results or not
3394
     * @param int $propagate_neg
3395
     * @param array $hotspot_delineation_result
3396
     * @param bool $showTotalScoreAndUserChoicesInLastAttempt
3397
     * @todo    reduce parameters of this function
3398
     * @return  string  html code
3399
     */
3400
    public function manage_answer(
3401
        $exeId,
3402
        $questionId,
3403
        $choice,
3404
        $from = 'exercise_show',
3405
        $exerciseResultCoordinates = [],
3406
        $saved_results = true,
3407
        $from_database = false,
3408
        $show_result = true,
3409
        $propagate_neg = 0,
3410
        $hotspot_delineation_result = [],
3411
        $showTotalScoreAndUserChoicesInLastAttempt = true
3412
    ) {
3413
        global $debug;
3414
        //needed in order to use in the exercise_attempt() for the time
3415
        global $learnpath_id, $learnpath_item_id;
3416
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3417
        $em = Database::getManager();
3418
        $feedback_type = $this->selectFeedbackType();
3419
        $results_disabled = $this->selectResultsDisabled();
3420
3421
        if ($debug) {
3422
            error_log("<------ manage_answer ------> ");
3423
            error_log('exe_id: '.$exeId);
3424
            error_log('$from:  '.$from);
3425
            error_log('$saved_results: '.intval($saved_results));
3426
            error_log('$from_database: '.intval($from_database));
3427
            error_log('$show_result: '.$show_result);
3428
            error_log('$propagate_neg: '.$propagate_neg);
3429
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3430
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3431
            error_log('$learnpath_id: '.$learnpath_id);
3432
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3433
            error_log('$choice: '.print_r($choice, 1));
3434
        }
3435
3436
        $final_overlap = 0;
3437
        $final_missing = 0;
3438
        $final_excess = 0;
3439
        $overlap_color = 0;
3440
        $missing_color = 0;
3441
        $excess_color = 0;
3442
        $threadhold1 = 0;
3443
        $threadhold2 = 0;
3444
        $threadhold3 = 0;
3445
        $arrques = null;
3446
        $arrans  = null;
3447
        $questionId = intval($questionId);
3448
        $exeId = intval($exeId);
3449
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3450
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3451
3452
        // Creates a temporary Question object
3453
        $course_id = $this->course_id;
3454
        $objQuestionTmp = Question::read($questionId, $course_id);
3455
3456
        if ($objQuestionTmp === false) {
3457
            return false;
3458
        }
3459
3460
        $questionName = $objQuestionTmp->selectTitle();
3461
        $questionWeighting = $objQuestionTmp->selectWeighting();
3462
        $answerType = $objQuestionTmp->selectType();
3463
        $quesId = $objQuestionTmp->selectId();
3464
        $extra = $objQuestionTmp->extra;
3465
        $next = 1; //not for now
3466
        $totalWeighting = 0;
3467
        $totalScore = 0;
3468
3469
        // Extra information of the question
3470
        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE && !empty($extra)) {
3471
            $extra = explode(':', $extra);
3472
            if ($debug) {
3473
                error_log(print_r($extra, 1));
3474
            }
3475
            // Fixes problems with negatives values using intval
3476
            $true_score = floatval(trim($extra[0]));
3477
            $false_score = floatval(trim($extra[1]));
3478
            $doubt_score = floatval(trim($extra[2]));
3479
        }
3480
3481
        // Construction of the Answer object
3482
        $objAnswerTmp = new Answer($questionId, $course_id);
3483
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3484
3485
        if ($debug) {
3486
            error_log('Count of answers: '.$nbrAnswers);
3487
            error_log('$answerType: '.$answerType);
3488
        }
3489
3490
        if ($answerType == FREE_ANSWER ||
3491
            $answerType == ORAL_EXPRESSION ||
3492
            $answerType == CALCULATED_ANSWER ||
3493
            $answerType == ANNOTATION
3494
        ) {
3495
            $nbrAnswers = 1;
3496
        }
3497
3498
        if ($answerType == ORAL_EXPRESSION) {
3499
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3500
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3501
3502
            $objQuestionTmp->initFile(
3503
                api_get_session_id(),
3504
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3505
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3506
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3507
            );
3508
3509
            //probably this attempt came in an exercise all question by page
3510
            if ($feedback_type == 0) {
3511
                $objQuestionTmp->replaceWithRealExe($exeId);
3512
            }
3513
        }
3514
3515
        $user_answer = '';
3516
        // Get answer list for matching
3517
        $sql = "SELECT id_auto, id, answer
3518
                FROM $table_ans
3519
                WHERE c_id = $course_id AND question_id = $questionId";
3520
        $res_answer = Database::query($sql);
3521
3522
        $answerMatching = [];
3523
        while ($real_answer = Database::fetch_array($res_answer)) {
3524
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3525
        }
3526
3527
        $real_answers = [];
3528
        $quiz_question_options = Question::readQuestionOption(
3529
            $questionId,
3530
            $course_id
3531
        );
3532
3533
        $organs_at_risk_hit = 0;
3534
        $questionScore = 0;
3535
        $answer_correct_array = [];
3536
        $orderedHotspots = [];
3537
3538
        if ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
3539
            $orderedHotspots = $em->getRepository('ChamiloCoreBundle:TrackEHotspot')->findBy(
3540
                [
3541
                    'hotspotQuestionId' => $questionId,
3542
                    'cId' => $course_id,
3543
                    'hotspotExeId' => $exeId,
3544
                ],
3545
                ['hotspotAnswerId' => 'ASC']
3546
            );
3547
        }
3548
        if ($debug) {
3549
            error_log('Start answer loop ');
3550
        }
3551
3552
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3553
            $answer = $objAnswerTmp->selectAnswer($answerId);
3554
            $answerComment = $objAnswerTmp->selectComment($answerId);
3555
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3556
            $answerWeighting = (float) $objAnswerTmp->selectWeighting($answerId);
3557
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3558
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? $objAnswerTmp->iid[$answerId] : '';
3559
            $answer_correct_array[$answerId] = (bool) $answerCorrect;
3560
3561
            if ($debug) {
3562
                error_log("answer auto id: $answerAutoId ");
3563
                error_log("answer correct: $answerCorrect ");
3564
            }
3565
3566
            // Delineation
3567
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3568
            $answer_delineation_destination = $objAnswerTmp->selectDestination(1);
3569
3570
            switch ($answerType) {
3571
                // for unique answer
3572
                case UNIQUE_ANSWER:
3573
                case UNIQUE_ANSWER_IMAGE:
3574
                case UNIQUE_ANSWER_NO_OPTION:
3575
                case READING_COMPREHENSION:
3576
                    if ($from_database) {
3577
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3578
                                WHERE
3579
                                    exe_id = '".$exeId."' AND
3580
                                    question_id= '".$questionId."'";
3581
                        $result = Database::query($sql);
3582
                        $choice = Database::result($result, 0, 'answer');
3583
3584
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3585
                        if ($studentChoice) {
3586
                            $questionScore += $answerWeighting;
3587
                            $totalScore += $answerWeighting;
3588
                        }
3589
                    } else {
3590
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3591
                        if ($studentChoice) {
3592
                            $questionScore += $answerWeighting;
3593
                            $totalScore += $answerWeighting;
3594
                        }
3595
                    }
3596
                    break;
3597
                // for multiple answers
3598
                case MULTIPLE_ANSWER_TRUE_FALSE:
3599
                    if ($from_database) {
3600
                        $choice = [];
3601
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3602
                                WHERE
3603
                                    exe_id = $exeId AND
3604
                                    question_id = ".$questionId;
3605
3606
                        $result = Database::query($sql);
3607
                        while ($row = Database::fetch_array($result)) {
3608
                            $values = explode(':', $row['answer']);
3609
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3610
                            $option = isset($values[1]) ? $values[1] : '';
3611
                            $choice[$my_answer_id] = $option;
3612
                        }
3613
                    }
3614
3615
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3616
3617
                    if (!empty($studentChoice)) {
3618
                        if ($studentChoice == $answerCorrect) {
3619
                            $questionScore += $true_score;
3620
                        } else {
3621
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3622
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3623
                            ) {
3624
                                $questionScore += $doubt_score;
3625
                            } else {
3626
                                $questionScore += $false_score;
3627
                            }
3628
                        }
3629
                    } else {
3630
                        // If no result then the user just hit don't know
3631
                        $studentChoice = 3;
3632
                        $questionScore += $doubt_score;
3633
                    }
3634
                    $totalScore = $questionScore;
3635
                    break;
3636
                case MULTIPLE_ANSWER: //2
3637
                    if ($from_database) {
3638
                        $choice = [];
3639
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3640
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3641
                        $resultans = Database::query($sql);
3642
                        while ($row = Database::fetch_array($resultans)) {
3643
                            $choice[$row['answer']] = 1;
3644
                        }
3645
3646
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3647
                        $real_answers[$answerId] = (bool) $studentChoice;
3648
3649
                        if ($studentChoice) {
3650
                            $questionScore += $answerWeighting;
3651
                        }
3652
                    } else {
3653
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3654
                        $real_answers[$answerId] = (bool) $studentChoice;
3655
3656
                        if (isset($studentChoice)) {
3657
                            $questionScore += $answerWeighting;
3658
                        }
3659
                    }
3660
                    $totalScore += $answerWeighting;
3661
3662
                    if ($debug) {
3663
                        error_log("studentChoice: $studentChoice");
3664
                    }
3665
                    break;
3666
                case GLOBAL_MULTIPLE_ANSWER:
3667
                    if ($from_database) {
3668
                        $choice = [];
3669
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3670
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3671
                        $resultans = Database::query($sql);
3672
                        while ($row = Database::fetch_array($resultans)) {
3673
                            $choice[$row['answer']] = 1;
3674
                        }
3675
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3676
                        $real_answers[$answerId] = (bool) $studentChoice;
3677
                        if ($studentChoice) {
3678
                            $questionScore += $answerWeighting;
3679
                        }
3680
                    } else {
3681
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3682
                        if (isset($studentChoice)) {
3683
                            $questionScore += $answerWeighting;
3684
                        }
3685
                        $real_answers[$answerId] = (bool) $studentChoice;
3686
                    }
3687
                    $totalScore += $answerWeighting;
3688
                    if ($debug) {
3689
                        error_log("studentChoice: $studentChoice");
3690
                    }
3691
                    break;
3692
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3693
                    if ($from_database) {
3694
                        $choice = [];
3695
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3696
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3697
                        $resultans = Database::query($sql);
3698
                        while ($row = Database::fetch_array($resultans)) {
3699
                            $result = explode(':', $row['answer']);
3700
                            if (isset($result[0])) {
3701
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3702
                                $option = isset($result[1]) ? $result[1] : '';
3703
                                $choice[$my_answer_id] = $option;
3704
                            }
3705
                        }
3706
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3707
3708
                        if ($answerCorrect == $studentChoice) {
3709
                            //$answerCorrect = 1;
3710
                            $real_answers[$answerId] = true;
3711
                        } else {
3712
                            //$answerCorrect = 0;
3713
                            $real_answers[$answerId] = false;
3714
                        }
3715
                    } else {
3716
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3717
                        if ($answerCorrect == $studentChoice) {
3718
                            //$answerCorrect = 1;
3719
                            $real_answers[$answerId] = true;
3720
                        } else {
3721
                            //$answerCorrect = 0;
3722
                            $real_answers[$answerId] = false;
3723
                        }
3724
                    }
3725
                    break;
3726
                case MULTIPLE_ANSWER_COMBINATION:
3727
                    if ($from_database) {
3728
                        $choice = [];
3729
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3730
                                WHERE exe_id = $exeId AND question_id= $questionId";
3731
                        $resultans = Database::query($sql);
3732
                        while ($row = Database::fetch_array($resultans)) {
3733
                            $choice[$row['answer']] = 1;
3734
                        }
3735
3736
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3737
                        if ($answerCorrect == 1) {
3738
                            if ($studentChoice) {
3739
                                $real_answers[$answerId] = true;
3740
                            } else {
3741
                                $real_answers[$answerId] = false;
3742
                            }
3743
                        } else {
3744
                            if ($studentChoice) {
3745
                                $real_answers[$answerId] = false;
3746
                            } else {
3747
                                $real_answers[$answerId] = true;
3748
                            }
3749
                        }
3750
                    } else {
3751
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3752
                        if ($answerCorrect == 1) {
3753
                            if ($studentChoice) {
3754
                                $real_answers[$answerId] = true;
3755
                            } else {
3756
                                $real_answers[$answerId] = false;
3757
                            }
3758
                        } else {
3759
                            if ($studentChoice) {
3760
                                $real_answers[$answerId] = false;
3761
                            } else {
3762
                                $real_answers[$answerId] = true;
3763
                            }
3764
                        }
3765
                    }
3766
                    break;
3767
                case FILL_IN_BLANKS:
3768
                    $str = '';
3769
                    $answerFromDatabase = '';
3770
                    if ($from_database) {
3771
                        $sql = "SELECT answer
3772
                                FROM $TBL_TRACK_ATTEMPT
3773
                                WHERE
3774
                                    exe_id = $exeId AND
3775
                                    question_id= ".intval($questionId);
3776
                        $result = Database::query($sql);
3777
                        $str = $answerFromDatabase = Database::result($result, 0, 'answer');
3778
                    }
3779
3780
                    if ($saved_results == false && strpos($answerFromDatabase, 'font color') !== false) {
3781
                        // the question is encoded like this
3782
                        // [A] B [C] D [E] F::10,10,10@1
3783
                        // number 1 before the "@" means that is a switchable fill in blank question
3784
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3785
                        // means that is a normal fill blank question
3786
                        // first we explode the "::"
3787
                        $pre_array = explode('::', $answer);
3788
3789
                        // is switchable fill blank or not
3790
                        $last = count($pre_array) - 1;
3791
                        $is_set_switchable = explode('@', $pre_array[$last]);
3792
                        $switchable_answer_set = false;
3793
                        if (isset($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3794
                            $switchable_answer_set = true;
3795
                        }
3796
                        $answer = '';
3797
                        for ($k = 0; $k < $last; $k++) {
3798
                            $answer .= $pre_array[$k];
3799
                        }
3800
                        // splits weightings that are joined with a comma
3801
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3802
                        // we save the answer because it will be modified
3803
                        $temp = $answer;
3804
                        $answer = '';
3805
                        $j = 0;
3806
                        //initialise answer tags
3807
                        $user_tags = $correct_tags = $real_text = [];
3808
                        // the loop will stop at the end of the text
3809
                        while (1) {
3810
                            // quits the loop if there are no more blanks (detect '[')
3811
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
3812
                                // adds the end of the text
3813
                                $answer = $temp;
3814
                                $real_text[] = $answer;
3815
                                break; //no more "blanks", quit the loop
3816
                            }
3817
                            // adds the piece of text that is before the blank
3818
                            //and ends with '[' into a general storage array
3819
                            $real_text[] = api_substr($temp, 0, $pos + 1);
3820
                            $answer .= api_substr($temp, 0, $pos + 1);
3821
                            //take the string remaining (after the last "[" we found)
3822
                            $temp = api_substr($temp, $pos + 1);
3823
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3824
                            if (($pos = api_strpos($temp, ']')) === false) {
3825
                                // adds the end of the text
3826
                                $answer .= $temp;
3827
                                break;
3828
                            }
3829
                            if ($from_database) {
3830
                                $str = $answerFromDatabase;
3831
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3832
                                $str = str_replace('\r\n', '', $str);
3833
3834
                                $choice = $arr[1];
3835
                                if (isset($choice[$j])) {
3836
                                    $tmp = api_strrpos($choice[$j], ' / ');
3837
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3838
                                    $choice[$j] = trim($choice[$j]);
3839
                                    // Needed to let characters ' and " to work as part of an answer
3840
                                    $choice[$j] = stripslashes($choice[$j]);
3841
                                } else {
3842
                                    $choice[$j] = null;
3843
                                }
3844
                            } else {
3845
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3846
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3847
                            }
3848
3849
                            $user_tags[] = $choice[$j];
3850
                            //put the contents of the [] answer tag into correct_tags[]
3851
                            $correct_tags[] = api_substr($temp, 0, $pos);
3852
                            $j++;
3853
                            $temp = api_substr($temp, $pos + 1);
3854
                        }
3855
                        $answer = '';
3856
                        $real_correct_tags = $correct_tags;
3857
                        $chosen_list = [];
3858
3859
                        for ($i = 0; $i < count($real_correct_tags); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3860
                            if ($i == 0) {
3861
                                $answer .= $real_text[0];
3862
                            }
3863
                            if (!$switchable_answer_set) {
3864
                                // Needed to parse ' and " characters
3865
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3866
                                if ($correct_tags[$i] == $user_tags[$i]) {
3867
                                    // gives the related weighting to the student
3868
                                    $questionScore += $answerWeighting[$i];
3869
                                    // increments total score
3870
                                    $totalScore += $answerWeighting[$i];
3871
                                    // adds the word in green at the end of the string
3872
                                    $answer .= $correct_tags[$i];
3873
                                } elseif (!empty($user_tags[$i])) {
3874
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3875
                                    // adds the word in red at the end of the string, and strikes it
3876
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3877
                                } else {
3878
                                    // adds a tabulation if no word has been typed by the student
3879
                                    $answer .= ''; // remove &nbsp; that causes issue
3880
                                }
3881
                            } else {
3882
                                // switchable fill in the blanks
3883
                                if (in_array($user_tags[$i], $correct_tags)) {
3884
                                    $chosen_list[] = $user_tags[$i];
3885
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3886
                                    // gives the related weighting to the student
3887
                                    $questionScore += $answerWeighting[$i];
3888
                                    // increments total score
3889
                                    $totalScore += $answerWeighting[$i];
3890
                                    // adds the word in green at the end of the string
3891
                                    $answer .= $user_tags[$i];
3892
                                } elseif (!empty($user_tags[$i])) {
3893
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3894
                                    // adds the word in red at the end of the string, and strikes it
3895
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
3896
                                } else {
3897
                                    // adds a tabulation if no word has been typed by the student
3898
                                    $answer .= ''; // remove &nbsp; that causes issue
3899
                                }
3900
                            }
3901
3902
                            // adds the correct word, followed by ] to close the blank
3903
                            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
3904
                            if (isset($real_text[$i + 1])) {
3905
                                $answer .= $real_text[$i + 1];
3906
                            }
3907
                        }
3908
                    } else {
3909
                        // insert the student result in the track_e_attempt table, field answer
3910
                        // $answer is the answer like in the c_quiz_answer table for the question
3911
                        // student data are choice[]
3912
                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
3913
                            $answer
3914
                        );
3915
3916
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
3917
                        $answerWeighting = $listCorrectAnswers['weighting'];
3918
                        // user choices is an array $choice
3919
3920
                        // get existing user data in n the BDD
3921
                        if ($from_database) {
3922
                            $listStudentResults = FillBlanks::getAnswerInfo(
3923
                                $answerFromDatabase,
3924
                                true
3925
                            );
3926
                            $choice = $listStudentResults['student_answer'];
3927
                        }
3928
3929
                        // loop other all blanks words
3930
                        if (!$switchableAnswerSet) {
3931
                            // not switchable answer, must be in the same place than teacher order
3932
                            for ($i = 0; $i < count($listCorrectAnswers['words']); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3933
                                $studentAnswer = isset($choice[$i]) ? $choice[$i] : '';
3934
                                $correctAnswer = $listCorrectAnswers['words'][$i];
3935
3936
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3937
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3938
                                // ENT_QUOTES is used in order to transform ' to &#039;
3939
                                if (!$from_database) {
3940
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
3941
                                }
3942
3943
                                $isAnswerCorrect = 0;
3944
                                if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database)) {
3945
                                    // gives the related weighting to the student
3946
                                    $questionScore += $answerWeighting[$i];
3947
                                    // increments total score
3948
                                    $totalScore += $answerWeighting[$i];
3949
                                    $isAnswerCorrect = 1;
3950
                                }
3951
3952
                                $studentAnswerToShow = $studentAnswer;
3953
                                $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
3954
                                if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
3955
                                    $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
3956
                                    if ($studentAnswer != '') {
3957
                                        foreach ($listMenu as $item) {
3958
                                            if (sha1($item) == $studentAnswer) {
3959
                                                $studentAnswerToShow = $item;
3960
                                            }
3961
                                        }
3962
                                    }
3963
                                }
3964
3965
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
3966
                                $listCorrectAnswers['student_score'][$i] = $isAnswerCorrect;
3967
                            }
3968
                        } else {
3969
                            // switchable answer
3970
                            $listStudentAnswerTemp = $choice;
3971
                            $listTeacherAnswerTemp = $listCorrectAnswers['words'];
3972
3973
                            // for every teacher answer, check if there is a student answer
3974
                            for ($i = 0; $i < count($listStudentAnswerTemp); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3975
                                $studentAnswer = trim($listStudentAnswerTemp[$i]);
3976
                                $studentAnswerToShow = $studentAnswer;
3977
3978
                                $found = false;
3979
                                for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3980
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3981
                                    $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
3982
                                    if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
3983
                                        $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
3984
                                        if (!empty($studentAnswer)) {
3985
                                            //var_dump($listMenu, $correctAnswer);
3986
                                            foreach ($listMenu as $key => $item) {
3987
                                                if ($key == $correctAnswer) {
3988
                                                    $studentAnswerToShow = $item;
3989
                                                    break;
3990
                                                }
3991
                                            }
3992
                                        }
3993
                                    }
3994
3995
                                    if (!$found) {
3996
                                        if (FillBlanks::isStudentAnswerGood(
3997
                                            $studentAnswer,
3998
                                            $correctAnswer,
3999
                                            $from_database
4000
                                        )
4001
                                        ) {
4002
                                            $questionScore += $answerWeighting[$i];
4003
                                            $totalScore += $answerWeighting[$i];
4004
                                            $listTeacherAnswerTemp[$j] = '';
4005
                                            $found = true;
4006
                                        }
4007
                                    }
4008
                                }
4009
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
4010
                                if (!$found) {
4011
                                    $listCorrectAnswers['student_score'][$i] = 0;
4012
                                } else {
4013
                                    $listCorrectAnswers['student_score'][$i] = 1;
4014
                                }
4015
                            }
4016
                        }
4017
                        $answer = FillBlanks::getAnswerInStudentAttempt(
4018
                            $listCorrectAnswers
4019
                        );
4020
4021
                        if ($saved_results) {
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...
4022
                            //var_dump($listCorrectAnswers);
4023
                        }
4024
                    }
4025
                    break;
4026
                case CALCULATED_ANSWER:
4027
                    $answer = $objAnswerTmp->selectAnswer($_SESSION['calculatedAnswerId'][$questionId]);
4028
                    $preArray = explode('@@', $answer);
4029
                    $last = count($preArray) - 1;
4030
                    $answer = '';
4031
                    for ($k = 0; $k < $last; $k++) {
4032
                        $answer .= $preArray[$k];
4033
                    }
4034
                    $answerWeighting = [$answerWeighting];
4035
                    // we save the answer because it will be modified
4036
                    $temp = $answer;
4037
                    $answer = '';
4038
                    $j = 0;
4039
                    //initialise answer tags
4040
                    $userTags = $correctTags = $realText = [];
4041
                    // the loop will stop at the end of the text
4042
                    while (1) {
4043
                        // quits the loop if there are no more blanks (detect '[')
4044
                        if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
4045
                            // adds the end of the text
4046
                            $answer = $temp;
4047
                            $realText[] = $answer;
4048
                            break; //no more "blanks", quit the loop
4049
                        }
4050
                        // adds the piece of text that is before the blank
4051
                        //and ends with '[' into a general storage array
4052
                        $realText[] = api_substr($temp, 0, $pos + 1);
4053
                        $answer .= api_substr($temp, 0, $pos + 1);
4054
                        //take the string remaining (after the last "[" we found)
4055
                        $temp = api_substr($temp, $pos + 1);
4056
                        // quit the loop if there are no more blanks, and update $pos to the position of next ']'
4057
                        if (($pos = api_strpos($temp, ']')) === false) {
4058
                            // adds the end of the text
4059
                            $answer .= $temp;
4060
                            break;
4061
                        }
4062
4063
                        if ($from_database) {
4064
                            $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
4065
                                    WHERE
4066
                                        exe_id = '".$exeId."' AND
4067
                                        question_id = ".intval($questionId);
4068
                            $result = Database::query($sql);
4069
                            $str = Database::result($result, 0, 'answer');
4070
4071
                            api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
4072
                            $str = str_replace('\r\n', '', $str);
4073
                            $choice = $arr[1];
4074
                            if (isset($choice[$j])) {
4075
                                $tmp = api_strrpos($choice[$j], ' / ');
4076
4077
                                if ($tmp) {
4078
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
4079
                                } else {
4080
                                    $tmp = ltrim($tmp, '[');
4081
                                    $tmp = rtrim($tmp, ']');
4082
                                }
4083
4084
                                $choice[$j] = trim($choice[$j]);
4085
                                // Needed to let characters ' and " to work as part of an answer
4086
                                $choice[$j] = stripslashes($choice[$j]);
4087
                            } else {
4088
                                $choice[$j] = null;
4089
                            }
4090
                        } else {
4091
                            // This value is the user input, not escaped while correct answer is escaped by fckeditor
4092
                            $choice[$j] = api_htmlentities(trim($choice[$j]));
4093
                        }
4094
                        $userTags[] = $choice[$j];
4095
                        //put the contents of the [] answer tag into correct_tags[]
4096
                        $correctTags[] = api_substr($temp, 0, $pos);
4097
                        $j++;
4098
                        $temp = api_substr($temp, $pos + 1);
4099
                    }
4100
                    $answer = '';
4101
                    $realCorrectTags = $correctTags;
4102
                    for ($i = 0; $i < count($realCorrectTags); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4103
                        if ($i == 0) {
4104
                            $answer .= $realText[0];
4105
                        }
4106
                        // Needed to parse ' and " characters
4107
                        $userTags[$i] = stripslashes($userTags[$i]);
4108
                        if ($correctTags[$i] == $userTags[$i]) {
4109
                            // gives the related weighting to the student
4110
                            $questionScore += $answerWeighting[$i];
4111
                            // increments total score
4112
                            $totalScore += $answerWeighting[$i];
4113
                            // adds the word in green at the end of the string
4114
                            $answer .= $correctTags[$i];
4115
                        } elseif (!empty($userTags[$i])) {
4116
                            // else if the word entered by the student IS NOT the same as the one defined by the professor
4117
                            // adds the word in red at the end of the string, and strikes it
4118
                            $answer .= '<font color="red"><s>'.$userTags[$i].'</s></font>';
4119
                        } else {
4120
                            // adds a tabulation if no word has been typed by the student
4121
                            $answer .= ''; // remove &nbsp; that causes issue
4122
                        }
4123
                        // adds the correct word, followed by ] to close the blank
4124
4125
                        if ($this->results_disabled != EXERCISE_FEEDBACK_TYPE_EXAM) {
4126
                            $answer .= ' / <font color="green"><b>'.$realCorrectTags[$i].'</b></font>';
4127
                        }
4128
4129
                        $answer .= ']';
4130
4131
                        if (isset($realText[$i + 1])) {
4132
                            $answer .= $realText[$i + 1];
4133
                        }
4134
                    }
4135
                    break;
4136
                case FREE_ANSWER:
4137
                    if ($from_database) {
4138
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4139
                                 WHERE 
4140
                                    exe_id = $exeId AND 
4141
                                    question_id= ".$questionId;
4142
                        $result = Database::query($sql);
4143
                        $data = Database::fetch_array($result);
4144
4145
                        $choice = $data['answer'];
4146
                        $choice = str_replace('\r\n', '', $choice);
4147
                        $choice = stripslashes($choice);
4148
                        $questionScore = $data['marks'];
4149
4150
                        if ($questionScore == -1) {
4151
                            $totalScore += 0;
4152
                        } else {
4153
                            $totalScore += $questionScore;
4154
                        }
4155
                        if ($questionScore == '') {
4156
                            $questionScore = 0;
4157
                        }
4158
                        $arrques = $questionName;
4159
                        $arrans  = $choice;
4160
                    } else {
4161
                        $studentChoice = $choice;
4162
                        if ($studentChoice) {
4163
                            //Fixing negative puntation see #2193
4164
                            $questionScore = 0;
4165
                            $totalScore += 0;
4166
                        }
4167
                    }
4168
                    break;
4169
                case ORAL_EXPRESSION:
4170
                    if ($from_database) {
4171
                        $query = "SELECT answer, marks 
4172
                                  FROM $TBL_TRACK_ATTEMPT
4173
                                  WHERE 
4174
                                        exe_id = $exeId AND 
4175
                                        question_id = $questionId
4176
                                 ";
4177
                        $resq = Database::query($query);
4178
                        $row = Database::fetch_assoc($resq);
4179
                        $choice = $row['answer'];
4180
                        $choice = str_replace('\r\n', '', $choice);
4181
                        $choice = stripslashes($choice);
4182
                        $questionScore = $row['marks'];
4183
                        if ($questionScore == -1) {
4184
                            $totalScore += 0;
4185
                        } else {
4186
                            $totalScore += $questionScore;
4187
                        }
4188
                        $arrques = $questionName;
4189
                        $arrans  = $choice;
4190
                    } else {
4191
                        $studentChoice = $choice;
4192
                        if ($studentChoice) {
4193
                            //Fixing negative puntation see #2193
4194
                            $questionScore = 0;
4195
                            $totalScore += 0;
4196
                        }
4197
                    }
4198
                    break;
4199
                case DRAGGABLE:
4200
                case MATCHING_DRAGGABLE:
4201
                case MATCHING:
4202
                    if ($from_database) {
4203
                        $sql = "SELECT id, answer, id_auto
4204
                                FROM $table_ans
4205
                                WHERE
4206
                                    c_id = $course_id AND
4207
                                    question_id = $questionId AND
4208
                                    correct = 0
4209
                                ";
4210
                        $res_answer = Database::query($sql);
4211
                        // Getting the real answer
4212
                        $real_list = [];
4213
                        while ($real_answer = Database::fetch_array($res_answer)) {
4214
                            $real_list[$real_answer['id_auto']] = $real_answer['answer'];
4215
                        }
4216
4217
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
4218
                                FROM $table_ans
4219
                                WHERE
4220
                                    c_id = $course_id AND
4221
                                    question_id = $questionId AND
4222
                                    correct <> 0
4223
                                ORDER BY id_auto";
4224
                        $res_answers = Database::query($sql);
4225
4226
                        $questionScore = 0;
4227
4228
                        while ($a_answers = Database::fetch_array($res_answers)) {
4229
                            $i_answer_id = $a_answers['id']; //3
4230
                            $s_answer_label = $a_answers['answer']; // your daddy - your mother
4231
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
4232
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
4233
4234
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4235
                                    WHERE
4236
                                        exe_id = '$exeId' AND
4237
                                        question_id = '$questionId' AND
4238
                                        position = '$i_answer_id_auto'";
4239
4240
                            $res_user_answer = Database::query($sql);
4241
4242
                            if (Database::num_rows($res_user_answer) > 0) {
4243
                                //  rich - good looking
4244
                                $s_user_answer = Database::result($res_user_answer, 0, 0);
4245
                            } else {
4246
                                $s_user_answer = 0;
4247
                            }
4248
4249
                            $i_answerWeighting = $a_answers['ponderation'];
4250
4251
                            $user_answer = '';
4252
                            if (!empty($s_user_answer)) {
4253
                                if ($answerType == DRAGGABLE) {
4254
                                    if ($s_user_answer == $i_answer_correct_answer) {
4255
                                        $questionScore += $i_answerWeighting;
4256
                                        $totalScore += $i_answerWeighting;
4257
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
4258
                                    } else {
4259
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4260
                                    }
4261
                                } else {
4262
                                    if ($s_user_answer == $i_answer_correct_answer) {
4263
                                        $questionScore += $i_answerWeighting;
4264
                                        $totalScore += $i_answerWeighting;
4265
4266
                                        // Try with id
4267
                                        if (isset($real_list[$i_answer_id])) {
4268
                                            $user_answer = Display::span($real_list[$i_answer_id]);
4269
                                        }
4270
4271
                                        // Try with $i_answer_id_auto
4272
                                        if (empty($user_answer)) {
4273
                                            if (isset($real_list[$i_answer_id_auto])) {
4274
                                                $user_answer = Display::span(
4275
                                                    $real_list[$i_answer_id_auto]
4276
                                                );
4277
                                            }
4278
                                        }
4279
                                    } else {
4280
                                        $user_answer = Display::span(
4281
                                            $real_list[$s_user_answer],
4282
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
4283
                                        );
4284
                                    }
4285
                                }
4286
                            } elseif ($answerType == DRAGGABLE) {
4287
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4288
                            } else {
4289
                                $user_answer = Display::span(
4290
                                    get_lang('Incorrect').' &nbsp;',
4291
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
4292
                                );
4293
                            }
4294
4295
                            if ($show_result) {
4296
                                if ($showTotalScoreAndUserChoicesInLastAttempt === false) {
4297
                                    $user_answer = '';
4298
                                }
4299
                                echo '<tr>';
4300
                                echo '<td>'.$s_answer_label.'</td>';
4301
                                echo '<td>'.$user_answer;
4302
4303
                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4304
                                    if (isset($real_list[$i_answer_correct_answer]) &&
4305
                                        $showTotalScoreAndUserChoicesInLastAttempt === true
4306
                                    ) {
4307
                                        echo Display::span(
4308
                                            $real_list[$i_answer_correct_answer],
4309
                                            ['style' => 'color: #008000; font-weight: bold;']
4310
                                        );
4311
                                    }
4312
                                }
4313
                                echo '</td>';
4314
                                echo '</tr>';
4315
                            }
4316
                        }
4317
                        break(2); // break the switch and the "for" condition
4318
                    } else {
4319
                        if ($answerCorrect) {
4320
                            if (isset($choice[$answerAutoId]) &&
4321
                                $answerCorrect == $choice[$answerAutoId]
4322
                            ) {
4323
                                $questionScore += $answerWeighting;
4324
                                $totalScore += $answerWeighting;
4325
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4326
                            } else {
4327
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4328
                                    $user_answer = Display::span(
4329
                                        $answerMatching[$choice[$answerAutoId]],
4330
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4331
                                    );
4332
                                }
4333
                            }
4334
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4335
                        }
4336
                    }
4337
                    break;
4338
                case HOT_SPOT:
4339
                    if ($from_database) {
4340
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4341
                        // Check auto id
4342
                        $sql = "SELECT hotspot_correct
4343
                                FROM $TBL_TRACK_HOTSPOT
4344
                                WHERE
4345
                                    hotspot_exe_id = $exeId AND
4346
                                    hotspot_question_id= $questionId AND
4347
                                    hotspot_answer_id = ".intval($answerAutoId)."
4348
                                ORDER BY hotspot_id ASC";
4349
                        $result = Database::query($sql);
4350
                        if (Database::num_rows($result)) {
4351
                            $studentChoice = Database::result(
4352
                                $result,
4353
                                0,
4354
                                'hotspot_correct'
4355
                            );
4356
4357
                            if ($studentChoice) {
4358
                                $questionScore += $answerWeighting;
4359
                                $totalScore += $answerWeighting;
4360
                            }
4361
                        } else {
4362
                            // If answer.id is different:
4363
                            $sql = "SELECT hotspot_correct
4364
                                FROM $TBL_TRACK_HOTSPOT
4365
                                WHERE
4366
                                    hotspot_exe_id = $exeId AND
4367
                                    hotspot_question_id= $questionId AND
4368
                                    hotspot_answer_id = ".intval($answerId)."
4369
                                ORDER BY hotspot_id ASC";
4370
                            $result = Database::query($sql);
4371
4372
                            if (Database::num_rows($result)) {
4373
                                $studentChoice = Database::result(
4374
                                    $result,
4375
                                    0,
4376
                                    'hotspot_correct'
4377
                                );
4378
4379
                                if ($studentChoice) {
4380
                                    $questionScore += $answerWeighting;
4381
                                    $totalScore += $answerWeighting;
4382
                                }
4383
                            } else {
4384
                                // check answer.iid
4385
                                if (!empty($answerIid)) {
4386
                                    $sql = "SELECT hotspot_correct
4387
                                            FROM $TBL_TRACK_HOTSPOT
4388
                                            WHERE
4389
                                                hotspot_exe_id = $exeId AND
4390
                                                hotspot_question_id= $questionId AND
4391
                                                hotspot_answer_id = ".intval($answerIid)."
4392
                                            ORDER BY hotspot_id ASC";
4393
                                    $result = Database::query($sql);
4394
4395
                                    $studentChoice = Database::result(
4396
                                        $result,
4397
                                        0,
4398
                                        'hotspot_correct'
4399
                                    );
4400
4401
                                    if ($studentChoice) {
4402
                                        $questionScore += $answerWeighting;
4403
                                        $totalScore += $answerWeighting;
4404
                                    }
4405
                                }
4406
                            }
4407
                        }
4408
                    } else {
4409
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4410
                            $choice[$answerAutoId] = 0;
4411
                            $choice[$answerIid] = 0;
4412
                        } else {
4413
                            $studentChoice = $choice[$answerAutoId];
4414
                            if (empty($studentChoice)) {
4415
                                $studentChoice = $choice[$answerIid];
4416
                            }
4417
                            $choiceIsValid = false;
4418
                            if (!empty($studentChoice)) {
4419
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4420
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4421
                                $choicePoint = Geometry::decodePoint($studentChoice);
4422
4423
                                switch ($hotspotType) {
4424
                                    case 'square':
4425
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4426
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4427
                                        break;
4428
                                    case 'circle':
4429
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4430
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4431
                                        break;
4432
                                    case 'poly':
4433
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4434
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4435
                                        break;
4436
                                }
4437
                            }
4438
4439
                            $choice[$answerAutoId] = 0;
4440
                            if ($choiceIsValid) {
4441
                                $questionScore += $answerWeighting;
4442
                                $totalScore += $answerWeighting;
4443
                                $choice[$answerAutoId] = 1;
4444
                                $choice[$answerIid] = 1;
4445
                            }
4446
                        }
4447
                    }
4448
                    break;
4449
                // @todo never added to chamilo
4450
                //for hotspot with fixed order
4451
                case HOT_SPOT_ORDER:
4452
                    $studentChoice = $choice['order'][$answerId];
4453
                    if ($studentChoice == $answerId) {
4454
                        $questionScore  += $answerWeighting;
4455
                        $totalScore     += $answerWeighting;
4456
                        $studentChoice = true;
4457
                    } else {
4458
                        $studentChoice = false;
4459
                    }
4460
                    break;
4461
                // for hotspot with delineation
4462
                case HOT_SPOT_DELINEATION:
4463
                    if ($from_database) {
4464
                        // getting the user answer
4465
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4466
                        $query = "SELECT hotspot_correct, hotspot_coordinate
4467
                                    FROM $TBL_TRACK_HOTSPOT
4468
                                    WHERE
4469
                                        hotspot_exe_id = '".$exeId."' AND
4470
                                        hotspot_question_id= '".$questionId."' AND
4471
                                        hotspot_answer_id='1'";
4472
                        //by default we take 1 because it's a delineation
4473
                        $resq = Database::query($query);
4474
                        $row = Database::fetch_array($resq, 'ASSOC');
4475
4476
                        $choice = $row['hotspot_correct'];
4477
                        $user_answer = $row['hotspot_coordinate'];
4478
4479
                        // THIS is very important otherwise the poly_compile will throw an error!!
4480
                        // round-up the coordinates
4481
                        $coords = explode('/', $user_answer);
4482
                        $user_array = '';
4483
                        foreach ($coords as $coord) {
4484
                            list($x, $y) = explode(';', $coord);
4485
                            $user_array .= round($x).';'.round($y).'/';
4486
                        }
4487
                        $user_array = substr($user_array, 0, -1);
4488
                    } else {
4489
                        if (!empty($studentChoice)) {
4490
                            $newquestionList[] = $questionId;
4491
                        }
4492
4493
                        if ($answerId === 1) {
4494
                            $studentChoice = $choice[$answerId];
4495
                            $questionScore += $answerWeighting;
4496
4497
                            if ($hotspot_delineation_result[1] == 1) {
4498
                                $totalScore += $answerWeighting; //adding the total
4499
                            }
4500
                        }
4501
                    }
4502
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4503
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4504
                    break;
4505
                case ANNOTATION:
4506
                    if ($from_database) {
4507
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4508
                                 WHERE 
4509
                                    exe_id = $exeId AND 
4510
                                    question_id= ".$questionId;
4511
                        $resq = Database::query($sql);
4512
                        $data = Database::fetch_array($resq);
4513
4514
                        $questionScore = empty($data['marks']) ? 0 : $data['marks'];
4515
                        $totalScore += $questionScore == -1 ? 0 : $questionScore;
4516
4517
                        $arrques = $questionName;
4518
                        break;
4519
                    }
4520
4521
                    $studentChoice = $choice;
4522
4523
                    if ($studentChoice) {
4524
                        $questionScore = 0;
4525
                        $totalScore += 0;
4526
                    }
4527
                    break;
4528
            } // end switch Answertype
4529
4530
            if ($show_result) {
4531
                if ($debug) {
4532
                    error_log('Showing questions $from '.$from);
4533
                }
4534
                if ($from == 'exercise_result') {
4535
                    //display answers (if not matching type, or if the answer is correct)
4536
                    if (!in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4537
                        $answerCorrect
4538
                    ) {
4539
                        if (in_array(
4540
                            $answerType,
4541
                            [
4542
                                UNIQUE_ANSWER,
4543
                                UNIQUE_ANSWER_IMAGE,
4544
                                UNIQUE_ANSWER_NO_OPTION,
4545
                                MULTIPLE_ANSWER,
4546
                                MULTIPLE_ANSWER_COMBINATION,
4547
                                GLOBAL_MULTIPLE_ANSWER,
4548
                                READING_COMPREHENSION,
4549
                            ]
4550
                        )) {
4551
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4552
                                $feedback_type,
4553
                                $answerType,
4554
                                $studentChoice,
4555
                                $answer,
4556
                                $answerComment,
4557
                                $answerCorrect,
4558
                                0,
4559
                                0,
4560
                                0,
4561
                                $results_disabled,
4562
                                $showTotalScoreAndUserChoicesInLastAttempt,
4563
                                $this->export
4564
                            );
4565
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4566
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4567
                                $feedback_type,
4568
                                $answerType,
4569
                                $studentChoice,
4570
                                $answer,
4571
                                $answerComment,
4572
                                $answerCorrect,
4573
                                0,
4574
                                $questionId,
4575
                                0,
4576
                                $results_disabled,
4577
                                $showTotalScoreAndUserChoicesInLastAttempt
4578
                            );
4579
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
4580
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4581
                                $feedback_type,
4582
                                $answerType,
4583
                                $studentChoice,
4584
                                $answer,
4585
                                $answerComment,
4586
                                $answerCorrect,
4587
                                0,
4588
                                0,
4589
                                0,
4590
                                $results_disabled,
4591
                                $showTotalScoreAndUserChoicesInLastAttempt
4592
                            );
4593
                        } elseif ($answerType == FILL_IN_BLANKS) {
4594
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4595
                                $feedback_type,
4596
                                $answer,
4597
                                0,
4598
                                0,
4599
                                $results_disabled,
4600
                                '',
4601
                                $showTotalScoreAndUserChoicesInLastAttempt
4602
                            );
4603
                        } elseif ($answerType == CALCULATED_ANSWER) {
4604
                            ExerciseShowFunctions::display_calculated_answer(
4605
                                $feedback_type,
4606
                                $answer,
4607
                                0,
4608
                                0,
4609
                                $results_disabled,
4610
                                $showTotalScoreAndUserChoicesInLastAttempt
4611
                            );
4612
                        } elseif ($answerType == FREE_ANSWER) {
4613
                            ExerciseShowFunctions::display_free_answer(
4614
                                $feedback_type,
4615
                                $choice,
4616
                                $exeId,
4617
                                $questionId,
4618
                                $questionScore,
4619
                                $results_disabled
4620
                            );
4621
                        } elseif ($answerType == ORAL_EXPRESSION) {
4622
                            // to store the details of open questions in an array to be used in mail
4623
                            /** @var OralExpression $objQuestionTmp */
4624
                            ExerciseShowFunctions::display_oral_expression_answer(
4625
                                $feedback_type,
4626
                                $choice,
4627
                                0,
4628
                                0,
4629
                                $objQuestionTmp->getFileUrl(true),
4630
                                $results_disabled,
4631
                                $questionScore
4632
                            );
4633
                        } elseif ($answerType == HOT_SPOT) {
4634
                            /**
4635
                             * @var int $correctAnswerId
4636
                             * @var TrackEHotspot $hotspot
4637
                             */
4638
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4639
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4640
                                    break;
4641
                                }
4642
                            }
4643
4644
                            ExerciseShowFunctions::display_hotspot_answer(
4645
                                $feedback_type,
4646
                                ++$correctAnswerId,
4647
                                $answer,
4648
                                $studentChoice,
4649
                                $answerComment,
4650
                                $results_disabled,
4651
                                $correctAnswerId,
4652
                                $showTotalScoreAndUserChoicesInLastAttempt
4653
                            );
4654
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4655
                            ExerciseShowFunctions::display_hotspot_order_answer(
4656
                                $feedback_type,
4657
                                $answerId,
4658
                                $answer,
4659
                                $studentChoice,
4660
                                $answerComment
4661
                            );
4662
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4663
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4664
4665
                            //round-up the coordinates
4666
                            $coords = explode('/', $user_answer);
4667
                            $user_array = '';
4668
                            foreach ($coords as $coord) {
4669
                                list($x, $y) = explode(';', $coord);
4670
                                $user_array .= round($x).';'.round($y).'/';
4671
                            }
4672
                            $user_array = substr($user_array, 0, -1);
4673
4674
                            if ($next) {
4675
                                $user_answer = $user_array;
4676
                                // we compare only the delineation not the other points
4677
                                $answer_question = $_SESSION['hotspot_coord'][1];
4678
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4679
4680
                                //calculating the area
4681
                                $poly_user = convert_coordinates($user_answer, '/');
4682
                                $poly_answer = convert_coordinates($answer_question, '|');
4683
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4684
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4685
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4686
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4687
4688
                                $overlap = $poly_results['both'];
4689
                                $poly_answer_area = $poly_results['s1'];
4690
                                $poly_user_area = $poly_results['s2'];
4691
                                $missing = $poly_results['s1Only'];
4692
                                $excess = $poly_results['s2Only'];
4693
4694
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4695
                                // //this is an area in pixels
4696
                                if ($debug > 0) {
4697
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
4698
                                }
4699
4700
                                if ($overlap < 1) {
4701
                                    //shortcut to avoid complicated calculations
4702
                                    $final_overlap = 0;
4703
                                    $final_missing = 100;
4704
                                    $final_excess = 100;
4705
                                } else {
4706
                                    // the final overlap is the percentage of the initial polygon
4707
                                    // that is overlapped by the user's polygon
4708
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4709
                                    if ($debug > 1) {
4710
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
4711
                                    }
4712
                                    // the final missing area is the percentage of the initial polygon
4713
                                    // that is not overlapped by the user's polygon
4714
                                    $final_missing = 100 - $final_overlap;
4715
                                    if ($debug > 1) {
4716
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
4717
                                    }
4718
                                    // the final excess area is the percentage of the initial polygon's size
4719
                                    // that is covered by the user's polygon outside of the initial polygon
4720
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4721
                                    if ($debug > 1) {
4722
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
4723
                                    }
4724
                                }
4725
4726
                                //checking the destination parameters parsing the "@@"
4727
                                $destination_items = explode(
4728
                                    '@@',
4729
                                    $answerDestination
4730
                                );
4731
                                $threadhold_total = $destination_items[0];
4732
                                $threadhold_items = explode(
4733
                                    ';',
4734
                                    $threadhold_total
4735
                                );
4736
                                $threadhold1 = $threadhold_items[0]; // overlap
4737
                                $threadhold2 = $threadhold_items[1]; // excess
4738
                                $threadhold3 = $threadhold_items[2]; //missing
4739
4740
                                // if is delineation
4741
                                if ($answerId === 1) {
4742
                                    //setting colors
4743
                                    if ($final_overlap >= $threadhold1) {
4744
                                        $overlap_color = true; //echo 'a';
4745
                                    }
4746
                                    //echo $excess.'-'.$threadhold2;
4747
                                    if ($final_excess <= $threadhold2) {
4748
                                        $excess_color = true; //echo 'b';
4749
                                    }
4750
                                    //echo '--------'.$missing.'-'.$threadhold3;
4751
                                    if ($final_missing <= $threadhold3) {
4752
                                        $missing_color = true; //echo 'c';
4753
                                    }
4754
4755
                                    // if pass
4756
                                    if ($final_overlap >= $threadhold1 &&
4757
                                        $final_missing <= $threadhold3 &&
4758
                                        $final_excess <= $threadhold2
4759
                                    ) {
4760
                                        $next = 1; //go to the oars
4761
                                        $result_comment = get_lang('Acceptable');
4762
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4763
                                    } else {
4764
                                        $next = 0;
4765
                                        $result_comment = get_lang('Unacceptable');
4766
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4767
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4768
                                        //checking the destination parameters parsing the "@@"
4769
                                        $destination_items = explode('@@', $answerDestination);
4770
                                    }
4771
                                } elseif ($answerId > 1) {
4772
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4773
                                        if ($debug > 0) {
4774
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
4775
                                        }
4776
                                        //type no error shouldn't be treated
4777
                                        $next = 1;
4778
                                        continue;
4779
                                    }
4780
                                    if ($debug > 0) {
4781
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
4782
                                    }
4783
                                    //check the intersection between the oar and the user
4784
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4785
                                    //echo 'official';print_r($x_list);print_r($y_list);
4786
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4787
                                    $inter = $result['success'];
4788
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4789
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4790
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4791
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4792
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
4793
4794
                                    if ($overlap == false) {
4795
                                        //all good, no overlap
4796
                                        $next = 1;
4797
                                        continue;
4798
                                    } else {
4799
                                        if ($debug > 0) {
4800
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
4801
                                        }
4802
                                        $organs_at_risk_hit++;
4803
                                        //show the feedback
4804
                                        $next = 0;
4805
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4806
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4807
4808
                                        $destination_items = explode('@@', $answerDestination);
4809
                                        $try_hotspot = $destination_items[1];
4810
                                        $lp_hotspot = $destination_items[2];
4811
                                        $select_question_hotspot = $destination_items[3];
4812
                                        $url_hotspot = $destination_items[4];
4813
                                    }
4814
                                }
4815
                            } else {
4816
                                // the first delineation feedback
4817
                                if ($debug > 0) {
4818
                                    error_log(__LINE__.' first', 0);
4819
                                }
4820
                            }
4821
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4822
                            echo '<tr>';
4823
                            echo Display::tag('td', $answerMatching[$answerId]);
4824
                            echo Display::tag(
4825
                                'td',
4826
                                "$user_answer / ".Display::tag(
4827
                                    'strong',
4828
                                    $answerMatching[$answerCorrect],
4829
                                    ['style' => 'color: #008000; font-weight: bold;']
4830
                                )
4831
                            );
4832
                            echo '</tr>';
4833
                        } elseif ($answerType == ANNOTATION) {
4834
                            ExerciseShowFunctions::displayAnnotationAnswer(
4835
                                $feedback_type,
4836
                                $exeId,
4837
                                $questionId,
4838
                                $questionScore,
4839
                                $results_disabled
4840
                            );
4841
                        }
4842
                    }
4843
                } else {
4844
                    if ($debug) {
4845
                        error_log('Showing questions $from '.$from);
4846
                    }
4847
4848
                    switch ($answerType) {
4849
                        case UNIQUE_ANSWER:
4850
                        case UNIQUE_ANSWER_IMAGE:
4851
                        case UNIQUE_ANSWER_NO_OPTION:
4852
                        case MULTIPLE_ANSWER:
4853
                        case GLOBAL_MULTIPLE_ANSWER:
4854
                        case MULTIPLE_ANSWER_COMBINATION:
4855
                        case READING_COMPREHENSION:
4856
                            if ($answerId == 1) {
4857
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4858
                                    $feedback_type,
4859
                                    $answerType,
4860
                                    $studentChoice,
4861
                                    $answer,
4862
                                    $answerComment,
4863
                                    $answerCorrect,
4864
                                    $exeId,
4865
                                    $questionId,
4866
                                    $answerId,
4867
                                    $results_disabled,
4868
                                    $showTotalScoreAndUserChoicesInLastAttempt,
4869
                                    $this->export
4870
                                );
4871
                            } else {
4872
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4873
                                    $feedback_type,
4874
                                    $answerType,
4875
                                    $studentChoice,
4876
                                    $answer,
4877
                                    $answerComment,
4878
                                    $answerCorrect,
4879
                                    $exeId,
4880
                                    $questionId,
4881
                                    '',
4882
                                    $results_disabled,
4883
                                    $showTotalScoreAndUserChoicesInLastAttempt,
4884
                                    $this->export
4885
                                );
4886
                            }
4887
                            break;
4888
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4889
                            if ($answerId == 1) {
4890
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4891
                                    $feedback_type,
4892
                                    $answerType,
4893
                                    $studentChoice,
4894
                                    $answer,
4895
                                    $answerComment,
4896
                                    $answerCorrect,
4897
                                    $exeId,
4898
                                    $questionId,
4899
                                    $answerId,
4900
                                    $results_disabled,
4901
                                    $showTotalScoreAndUserChoicesInLastAttempt
4902
                                );
4903
                            } else {
4904
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4905
                                    $feedback_type,
4906
                                    $answerType,
4907
                                    $studentChoice,
4908
                                    $answer,
4909
                                    $answerComment,
4910
                                    $answerCorrect,
4911
                                    $exeId,
4912
                                    $questionId,
4913
                                    '',
4914
                                    $results_disabled,
4915
                                    $showTotalScoreAndUserChoicesInLastAttempt
4916
                                );
4917
                            }
4918
                            break;
4919
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4920
                            if ($answerId == 1) {
4921
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4922
                                    $feedback_type,
4923
                                    $answerType,
4924
                                    $studentChoice,
4925
                                    $answer,
4926
                                    $answerComment,
4927
                                    $answerCorrect,
4928
                                    $exeId,
4929
                                    $questionId,
4930
                                    $answerId,
4931
                                    $results_disabled,
4932
                                    $showTotalScoreAndUserChoicesInLastAttempt
4933
                                );
4934
                            } else {
4935
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4936
                                    $feedback_type,
4937
                                    $answerType,
4938
                                    $studentChoice,
4939
                                    $answer,
4940
                                    $answerComment,
4941
                                    $answerCorrect,
4942
                                    $exeId,
4943
                                    $questionId,
4944
                                    '',
4945
                                    $results_disabled,
4946
                                    $showTotalScoreAndUserChoicesInLastAttempt
4947
                                );
4948
                            }
4949
                            break;
4950
                        case FILL_IN_BLANKS:
4951
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4952
                                $feedback_type,
4953
                                $answer,
4954
                                $exeId,
4955
                                $questionId,
4956
                                $results_disabled,
4957
                                $str,
4958
                                $showTotalScoreAndUserChoicesInLastAttempt
4959
                            );
4960
                            break;
4961
                        case CALCULATED_ANSWER:
4962
                            ExerciseShowFunctions::display_calculated_answer(
4963
                                $feedback_type,
4964
                                $answer,
4965
                                $exeId,
4966
                                $questionId,
4967
                                $results_disabled,
4968
                                '',
4969
                                $showTotalScoreAndUserChoicesInLastAttempt
4970
                            );
4971
                            break;
4972
                        case FREE_ANSWER:
4973
                            echo ExerciseShowFunctions::display_free_answer(
4974
                                $feedback_type,
4975
                                $choice,
4976
                                $exeId,
4977
                                $questionId,
4978
                                $questionScore,
4979
                                $results_disabled
4980
                            );
4981
                            break;
4982
                        case ORAL_EXPRESSION:
4983
                            echo '<tr>
4984
                                <td valign="top">'.
4985
                                ExerciseShowFunctions::display_oral_expression_answer(
4986
                                    $feedback_type,
4987
                                    $choice,
4988
                                    $exeId,
4989
                                    $questionId,
4990
                                    $objQuestionTmp->getFileUrl(),
4991
                                    $results_disabled,
4992
                                    $questionScore
4993
                                ).'</td>
4994
                                </tr>
4995
                                </table>';
4996
                            break;
4997
                        case HOT_SPOT:
4998
                            ExerciseShowFunctions::display_hotspot_answer(
4999
                                $feedback_type,
5000
                                $answerId,
5001
                                $answer,
5002
                                $studentChoice,
5003
                                $answerComment,
5004
                                $results_disabled,
5005
                                $answerId,
5006
                                $showTotalScoreAndUserChoicesInLastAttempt
5007
                            );
5008
                            break;
5009
                        case HOT_SPOT_DELINEATION:
5010
                            $user_answer = $user_array;
5011
                            if ($next) {
5012
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
5013
                                // Save into db
5014
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
5015
                                 * hotspot_user_id,
5016
                                 *  hotspot_course_code,
5017
                                 *  hotspot_exe_id,
5018
                                 *  hotspot_question_id,
5019
                                 *  hotspot_answer_id,
5020
                                 *  hotspot_correct,
5021
                                 *  hotspot_coordinate
5022
                                 *  )
5023
                                VALUES (
5024
                                 * '".Database::escape_string($_user['user_id'])."',
5025
                                 *  '".Database::escape_string($_course['id'])."',
5026
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
5027
                                 *  '".Database::escape_string($answerId)."',
5028
                                 *  '".Database::escape_string($studentChoice)."',
5029
                                 *  '".Database::escape_string($user_array)."')";
5030
                                $result = Database::query($sql,__FILE__,__LINE__);
5031
                                 */
5032
                                $user_answer = $user_array;
5033
                                // we compare only the delineation not the other points
5034
                                $answer_question = $_SESSION['hotspot_coord'][1];
5035
                                $answerDestination = $_SESSION['hotspot_dest'][1];
5036
5037
                                // calculating the area
5038
                                $poly_user = convert_coordinates($user_answer, '/');
5039
                                $poly_answer = convert_coordinates($answer_question, '|');
5040
                                $max_coord = poly_get_max($poly_user, $poly_answer);
5041
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
5042
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5043
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
5044
5045
                                $overlap = $poly_results['both'];
5046
                                $poly_answer_area = $poly_results['s1'];
5047
                                $poly_user_area = $poly_results['s2'];
5048
                                $missing = $poly_results['s1Only'];
5049
                                $excess = $poly_results['s2Only'];
5050
                                if ($debug > 0) {
5051
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
5052
                                }
5053
                                if ($overlap < 1) {
5054
                                    //shortcut to avoid complicated calculations
5055
                                    $final_overlap = 0;
5056
                                    $final_missing = 100;
5057
                                    $final_excess = 100;
5058
                                } else {
5059
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
5060
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
5061
                                    if ($debug > 1) {
5062
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
5063
                                    }
5064
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
5065
                                    $final_missing = 100 - $final_overlap;
5066
                                    if ($debug > 1) {
5067
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
5068
                                    }
5069
                                    // 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
5070
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
5071
                                    if ($debug > 1) {
5072
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
5073
                                    }
5074
                                }
5075
5076
                                // Checking the destination parameters parsing the "@@"
5077
                                $destination_items = explode('@@', $answerDestination);
5078
                                $threadhold_total = $destination_items[0];
5079
                                $threadhold_items = explode(';', $threadhold_total);
5080
                                $threadhold1 = $threadhold_items[0]; // overlap
5081
                                $threadhold2 = $threadhold_items[1]; // excess
5082
                                $threadhold3 = $threadhold_items[2]; //missing
5083
                                // if is delineation
5084
                                if ($answerId === 1) {
5085
                                    //setting colors
5086
                                    if ($final_overlap >= $threadhold1) {
5087
                                        $overlap_color = true; //echo 'a';
5088
                                    }
5089
                                    //echo $excess.'-'.$threadhold2;
5090
                                    if ($final_excess <= $threadhold2) {
5091
                                        $excess_color = true; //echo 'b';
5092
                                    }
5093
                                    //echo '--------'.$missing.'-'.$threadhold3;
5094
                                    if ($final_missing <= $threadhold3) {
5095
                                        $missing_color = true; //echo 'c';
5096
                                    }
5097
5098
                                    // if pass
5099
                                    if ($final_overlap >= $threadhold1 &&
5100
                                        $final_missing <= $threadhold3 &&
5101
                                        $final_excess <= $threadhold2
5102
                                    ) {
5103
                                        $next = 1; //go to the oars
5104
                                        $result_comment = get_lang('Acceptable');
5105
                                        $final_answer = 1; // do not update with  update_exercise_attempt
5106
                                    } else {
5107
                                        $next = 0;
5108
                                        $result_comment = get_lang('Unacceptable');
5109
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
5110
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
5111
                                        //checking the destination parameters parsing the "@@"
5112
                                        $destination_items = explode('@@', $answerDestination);
5113
                                    }
5114
                                } elseif ($answerId > 1) {
5115
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
5116
                                        if ($debug > 0) {
5117
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
5118
                                        }
5119
                                        //type no error shouldn't be treated
5120
                                        $next = 1;
5121
                                        continue;
5122
                                    }
5123
                                    if ($debug > 0) {
5124
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
5125
                                    }
5126
                                    //check the intersection between the oar and the user
5127
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
5128
                                    //echo 'official';print_r($x_list);print_r($y_list);
5129
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
5130
                                    $inter = $result['success'];
5131
5132
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
5133
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
5134
5135
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
5136
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
5137
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5138
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
5139
5140
                                    if ($overlap == false) {
5141
                                        //all good, no overlap
5142
                                        $next = 1;
5143
                                        continue;
5144
                                    } else {
5145
                                        if ($debug > 0) {
5146
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
5147
                                        }
5148
                                        $organs_at_risk_hit++;
5149
                                        //show the feedback
5150
                                        $next = 0;
5151
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
5152
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
5153
5154
                                        $destination_items = explode('@@', $answerDestination);
5155
                                        $try_hotspot = $destination_items[1];
5156
                                        $lp_hotspot = $destination_items[2];
5157
                                        $select_question_hotspot = $destination_items[3];
5158
                                        $url_hotspot = $destination_items[4];
5159
                                    }
5160
                                }
5161
                            } else {	// the first delineation feedback
5162
                                if ($debug > 0) {
5163
                                    error_log(__LINE__.' first', 0);
5164
                                }
5165
                            }
5166
                            break;
5167
                        case HOT_SPOT_ORDER:
5168
                            ExerciseShowFunctions::display_hotspot_order_answer(
5169
                                $feedback_type,
5170
                                $answerId,
5171
                                $answer,
5172
                                $studentChoice,
5173
                                $answerComment
5174
                            );
5175
                            break;
5176
                        case DRAGGABLE:
5177
                        case MATCHING_DRAGGABLE:
5178
                        case MATCHING:
5179
                            echo '<tr>';
5180
                            echo Display::tag('td', $answerMatching[$answerId]);
5181
                            echo Display::tag(
5182
                                'td',
5183
                                "$user_answer / ".Display::tag(
5184
                                    'strong',
5185
                                    $answerMatching[$answerCorrect],
5186
                                    ['style' => 'color: #008000; font-weight: bold;']
5187
                                )
5188
                            );
5189
                            echo '</tr>';
5190
5191
                            break;
5192
                        case ANNOTATION:
5193
                            ExerciseShowFunctions::displayAnnotationAnswer(
5194
                                $feedback_type,
5195
                                $exeId,
5196
                                $questionId,
5197
                                $questionScore,
5198
                                $results_disabled
5199
                            );
5200
                            break;
5201
                    }
5202
                }
5203
            }
5204
            if ($debug) {
5205
                error_log(' ------ ');
5206
            }
5207
        } // end for that loops over all answers of the current question
5208
5209
        if ($debug) {
5210
            error_log('-- end answer loop --');
5211
        }
5212
5213
        $final_answer = true;
5214
5215
        foreach ($real_answers as $my_answer) {
5216
            if (!$my_answer) {
5217
                $final_answer = false;
5218
            }
5219
        }
5220
5221
        //we add the total score after dealing with the answers
5222
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
5223
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
5224
        ) {
5225
            if ($final_answer) {
5226
                //getting only the first score where we save the weight of all the question
5227
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
5228
                $questionScore += $answerWeighting;
5229
                $totalScore += $answerWeighting;
5230
            }
5231
        }
5232
5233
        //Fixes multiple answer question in order to be exact
5234
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5235
        /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
5236
             $diff = @array_diff($answer_correct_array, $real_answers);
5237
5238
             // All good answers or nothing works like exact
5239
5240
             $counter = 1;
5241
             $correct_answer = true;
5242
             foreach ($real_answers as $my_answer) {
5243
                 if ($debug)
5244
                     error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
5245
                 if ($my_answer != $answer_correct_array[$counter]) {
5246
                     $correct_answer = false;
5247
                     break;
5248
                 }
5249
                 $counter++;
5250
             }
5251
5252
             if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
5253
             if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
5254
             if ($debug) error_log(" correct_answer: ".$correct_answer);
5255
5256
             if ($correct_answer == false) {
5257
                 $questionScore = 0;
5258
             }
5259
5260
             // This makes the result non exact
5261
             if (!empty($diff)) {
5262
                 $questionScore = 0;
5263
             }
5264
         }*/
5265
5266
        $extra_data = [
5267
            'final_overlap' => $final_overlap,
5268
            'final_missing' => $final_missing,
5269
            'final_excess' => $final_excess,
5270
            'overlap_color' => $overlap_color,
5271
            'missing_color' => $missing_color,
5272
            'excess_color' => $excess_color,
5273
            'threadhold1' => $threadhold1,
5274
            'threadhold2' => $threadhold2,
5275
            'threadhold3' => $threadhold3,
5276
        ];
5277
        if ($from == 'exercise_result') {
5278
            // if answer is hotspot. To the difference of exercise_show.php,
5279
            //  we use the results from the session (from_db=0)
5280
            // TODO Change this, because it is wrong to show the user
5281
            //  some results that haven't been stored in the database yet
5282
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION) {
5283
                if ($debug) {
5284
                    error_log('$from AND this is a hotspot kind of question ');
5285
                }
5286
                $my_exe_id = 0;
5287
                $from_database = 0;
5288
                if ($answerType == HOT_SPOT_DELINEATION) {
5289
                    if (0) {
5290
                        if ($overlap_color) {
5291
                            $overlap_color = 'green';
5292
                        } else {
5293
                            $overlap_color = 'red';
5294
                        }
5295
                        if ($missing_color) {
5296
                            $missing_color = 'green';
5297
                        } else {
5298
                            $missing_color = 'red';
5299
                        }
5300
                        if ($excess_color) {
5301
                            $excess_color = 'green';
5302
                        } else {
5303
                            $excess_color = 'red';
5304
                        }
5305
                        if (!is_numeric($final_overlap)) {
5306
                            $final_overlap = 0;
5307
                        }
5308
                        if (!is_numeric($final_missing)) {
5309
                            $final_missing = 0;
5310
                        }
5311
                        if (!is_numeric($final_excess)) {
5312
                            $final_excess = 0;
5313
                        }
5314
5315
                        if ($final_overlap > 100) {
5316
                            $final_overlap = 100;
5317
                        }
5318
5319
                        $table_resume = '<table class="data_table">
5320
                                <tr class="row_odd" >
5321
                                    <td></td>
5322
                                    <td ><b>' . get_lang('Requirements').'</b></td>
5323
                                    <td><b>' . get_lang('YourAnswer').'</b></td>
5324
                                </tr>
5325
                                <tr class="row_even">
5326
                                    <td><b>' . get_lang('Overlap').'</b></td>
5327
                                    <td>' . get_lang('Min').' '.$threadhold1.'</td>
5328
                                    <td><div style="color:' . $overlap_color.'">'
5329
                                        . (($final_overlap < 0) ? 0 : intval($final_overlap)).'</div></td>
5330
                                </tr>
5331
                                <tr>
5332
                                    <td><b>' . get_lang('Excess').'</b></td>
5333
                                    <td>' . get_lang('Max').' '.$threadhold2.'</td>
5334
                                    <td><div style="color:' . $excess_color.'">'
5335
                                        . (($final_excess < 0) ? 0 : intval($final_excess)).'</div></td>
5336
                                </tr>
5337
                                <tr class="row_even">
5338
                                    <td><b>' . get_lang('Missing').'</b></td>
5339
                                    <td>' . get_lang('Max').' '.$threadhold3.'</td>
5340
                                    <td><div style="color:' . $missing_color.'">'
5341
                                        . (($final_missing < 0) ? 0 : intval($final_missing)).'</div></td>
5342
                                </tr>
5343
                            </table>';
5344
                        if ($next == 0) {
5345
                            $try = $try_hotspot;
5346
                            $lp = $lp_hotspot;
5347
                            $destinationid = $select_question_hotspot;
5348
                            $url = $url_hotspot;
5349
                        } else {
5350
                            //show if no error
5351
                            //echo 'no error';
5352
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
5353
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
5354
                        }
5355
5356
                        echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>
5357
                            <p style="text-align:center">';
5358
5359
                        $message = '<p>'.get_lang('YourDelineation').'</p>';
5360
                        $message .= $table_resume;
5361
                        $message .= '<br />'.get_lang('ResultIs').' '.$result_comment.'<br />';
5362
                        if ($organs_at_risk_hit > 0) {
5363
                            $message .= '<p><b>'.get_lang('OARHit').'</b></p>';
5364
                        }
5365
                        $message .= '<p>'.$comment.'</p>';
5366
                        echo $message;
5367
                    } else {
5368
                        echo $hotspot_delineation_result[0]; //prints message
5369
                        $from_database = 1; // the hotspot_solution.swf needs this variable
5370
                    }
5371
5372
                    //save the score attempts
5373
                    if (1) {
5374
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5375
                        $final_answer = $hotspot_delineation_result[1];
5376
                        if ($final_answer == 0) {
5377
                            $questionScore = 0;
5378
                        }
5379
                        // we always insert the answer_id 1 = delineation
5380
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5381
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5382
                        $hotspotValue = (int) $hotspot_delineation_result[1] === 1 ? 1 : 0;
5383
                        Event::saveExerciseAttemptHotspot(
5384
                            $exeId,
5385
                            $quesId,
5386
                            1,
5387
                            $hotspotValue,
5388
                            $exerciseResultCoordinates[$quesId]
5389
                        );
5390
                    } else {
5391
                        if ($final_answer == 0) {
5392
                            $questionScore = 0;
5393
                            $answer = 0;
5394
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5395
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5396
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5397
                                    Event::saveExerciseAttemptHotspot(
5398
                                        $exeId,
5399
                                        $quesId,
5400
                                        $idx,
5401
                                        0,
5402
                                        $val
5403
                                    );
5404
                                }
5405
                            }
5406
                        } else {
5407
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5408
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5409
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5410
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5411
                                    Event::saveExerciseAttemptHotspot(
5412
                                        $exeId,
5413
                                        $quesId,
5414
                                        $idx,
5415
                                        $hotspotValue,
5416
                                        $val
5417
                                    );
5418
                                }
5419
                            }
5420
                        }
5421
                    }
5422
                    $my_exe_id = $exeId;
5423
                }
5424
            }
5425
5426
            $relPath = api_get_path(WEB_CODE_PATH);
5427
5428
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5429
                // We made an extra table for the answers
5430
5431
                if ($show_result) {
5432
5433
                    //	if ($origin != 'learnpath') {
5434
                    echo '</table></td></tr>';
5435
                    echo "
5436
                        <tr>
5437
                            <td colspan=\"2\">
5438
                                <p><em>" . get_lang('HotSpot')."</em></p>
5439
                                <div id=\"hotspot-solution-$questionId\"></div>
5440
                                <script>
5441
                                    $(document).on('ready', function () {
5442
                                        new HotspotQuestion({
5443
                                            questionId: $questionId,
5444
                                            exerciseId: $exeId,
5445
                                            selector: '#hotspot-solution-$questionId',
5446
                                            for: 'solution',
5447
                                            relPath: '$relPath'
5448
                                        });
5449
                                    });
5450
                                </script>
5451
                            </td>
5452
                        </tr>
5453
                    ";
5454
                    //	}
5455
                }
5456
            } elseif ($answerType == ANNOTATION) {
5457
                if ($show_result) {
5458
                    echo '
5459
                        <p><em>' . get_lang('Annotation').'</em></p>
5460
                        <div id="annotation-canvas-'.$questionId.'"></div>
5461
                        <script>
5462
                            AnnotationQuestion({
5463
                                questionId: parseInt('.$questionId.'),
5464
                                exerciseId: parseInt('.$exeId.'),
5465
                                relPath: \''.$relPath.'\'
5466
                            });
5467
                        </script>
5468
                    ';
5469
                }
5470
            }
5471
5472
            //if ($origin != 'learnpath') {
5473
            if ($show_result && $answerType != ANNOTATION) {
5474
                echo '</table>';
5475
            }
5476
            //	}
5477
        }
5478
        unset($objAnswerTmp);
5479
5480
        $totalWeighting += $questionWeighting;
5481
        // Store results directly in the database
5482
        // For all in one page exercises, the results will be
5483
        // stored by exercise_results.php (using the session)
5484
        if ($saved_results) {
5485
            if ($debug) {
5486
                error_log("Save question results $saved_results");
5487
            }
5488
            if ($debug) {
5489
                error_log(print_r($choice, 1));
5490
            }
5491
5492
            if (empty($choice)) {
5493
                $choice = 0;
5494
            }
5495
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5496
                if ($choice != 0) {
5497
                    $reply = array_keys($choice);
5498
                    for ($i = 0; $i < sizeof($reply); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
5499
                        $ans = $reply[$i];
5500
                        Event::saveQuestionAttempt(
5501
                            $questionScore,
5502
                            $ans.':'.$choice[$ans],
5503
                            $quesId,
5504
                            $exeId,
5505
                            $i,
5506
                            $this->id
5507
                        );
5508
                        if ($debug) {
5509
                            error_log('result =>'.$questionScore.' '.$ans.':'.$choice[$ans]);
5510
                        }
5511
                    }
5512
                } else {
5513
                    Event::saveQuestionAttempt(
5514
                        $questionScore,
5515
                        0,
5516
                        $quesId,
5517
                        $exeId,
5518
                        0,
5519
                        $this->id
5520
                    );
5521
                }
5522
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5523
                if ($choice != 0) {
5524
                    $reply = array_keys($choice);
5525
5526
                    if ($debug) {
5527
                        error_log("reply ".print_r($reply, 1)."");
5528
                    }
5529
                    for ($i = 0; $i < sizeof($reply); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
5530
                        $ans = $reply[$i];
5531
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5532
                    }
5533
                } else {
5534
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5535
                }
5536
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5537
                if ($choice != 0) {
5538
                    $reply = array_keys($choice);
5539
                    for ($i = 0; $i < sizeof($reply); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
5540
                        $ans = $reply[$i];
5541
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5542
                    }
5543
                } else {
5544
                    Event::saveQuestionAttempt(
5545
                        $questionScore,
5546
                        0,
5547
                        $quesId,
5548
                        $exeId,
5549
                        0,
5550
                        $this->id
5551
                    );
5552
                }
5553
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5554
                if (isset($matching)) {
5555
                    foreach ($matching as $j => $val) {
5556
                        Event::saveQuestionAttempt(
5557
                            $questionScore,
5558
                            $val,
5559
                            $quesId,
5560
                            $exeId,
5561
                            $j,
5562
                            $this->id
5563
                        );
5564
                    }
5565
                }
5566
            } elseif ($answerType == FREE_ANSWER) {
5567
                $answer = $choice;
5568
                Event::saveQuestionAttempt(
5569
                    $questionScore,
5570
                    $answer,
5571
                    $quesId,
5572
                    $exeId,
5573
                    0,
5574
                    $this->id
5575
                );
5576
            } elseif ($answerType == ORAL_EXPRESSION) {
5577
                $answer = $choice;
5578
                Event::saveQuestionAttempt(
5579
                    $questionScore,
5580
                    $answer,
5581
                    $quesId,
5582
                    $exeId,
5583
                    0,
5584
                    $this->id,
5585
                    false,
5586
                    $objQuestionTmp->getAbsoluteFilePath()
5587
                );
5588
            } elseif (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION])) {
5589
                $answer = $choice;
5590
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5591
                //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
5592
            } elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
5593
                $answer = [];
5594
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5595
                    Database::delete(
5596
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5597
                        [
5598
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5599
                                $exeId,
5600
                                $questionId,
5601
                                api_get_course_int_id()
5602
                            ]
5603
                        ]
5604
                    );
5605
5606
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5607
                        $answer[] = $val;
5608
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5609
                        Event::saveExerciseAttemptHotspot(
5610
                            $exeId,
5611
                            $quesId,
5612
                            $idx,
5613
                            $hotspotValue,
5614
                            $val,
5615
                            false,
5616
                            $this->id
5617
                        );
5618
                    }
5619
                }
5620
5621
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5622
            } else {
5623
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5624
            }
5625
        }
5626
5627
        if ($propagate_neg == 0 && $questionScore < 0) {
5628
            $questionScore = 0;
5629
        }
5630
5631
        if ($saved_results) {
5632
            $stat_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5633
            $sql = 'UPDATE '.$stat_table.' SET
5634
                        exe_result = exe_result + ' . floatval($questionScore).'
5635
                    WHERE exe_id = ' . $exeId;
5636
            Database::query($sql);
5637
        }
5638
5639
        $return_array = [
5640
            'score' => $questionScore,
5641
            'weight' => $questionWeighting,
5642
            'extra' => $extra_data,
5643
            'open_question' => $arrques,
5644
            'open_answer' => $arrans,
5645
            'answer_type' => $answerType,
5646
        ];
5647
5648
        return $return_array;
5649
    }
5650
5651
    /**
5652
     * Sends a notification when a user ends an examn
5653
     *
5654
     * @param string $type 'start' or 'end' of an exercise
5655
     * @param array $question_list_answers
5656
     * @param string $origin
5657
     * @param int $exe_id
5658
     * @param float $score
5659
     * @param float $weight
5660
     * @return bool
5661
     */
5662
    public function send_mail_notification_for_exam(
5663
        $type = 'end',
5664
        $question_list_answers,
5665
        $origin,
5666
        $exe_id,
5667
        $score = null,
5668
        $weight  = null
5669
    ) {
5670
        $setting = api_get_course_setting('email_alert_manager_on_new_quiz');
5671
5672
        if (empty($setting) && empty($this->getNotifications())) {
5673
            return false;
5674
        }
5675
5676
        $settingFromExercise = $this->getNotifications();
5677
        if (!empty($settingFromExercise)) {
5678
            $setting = $settingFromExercise;
5679
        }
5680
5681
        // Email configuration settings
5682
        $courseCode = api_get_course_id();
5683
        $courseInfo = api_get_course_info($courseCode);
5684
5685
        if (empty($courseInfo)) {
5686
            return false;
5687
        }
5688
5689
        $sessionId = api_get_session_id();
5690
        $sendStart = false;
5691
        $sendEnd = false;
5692
        $sendEndOpenQuestion = false;
5693
        $sendEndOralQuestion = false;
5694
5695
        foreach ($setting as $option) {
5696
            switch ($option) {
5697
                case 0:
5698
                    return false;
5699
                    break;
5700
                case 1: // End
5701
                    if ($type == 'end') {
5702
                        $sendEnd = true;
5703
                    }
5704
                    break;
5705
                case 2: // start
5706
                    if ($type == 'start') {
5707
                        $sendStart = true;
5708
                    }
5709
                    break;
5710
                case 3: // end + open
5711
                    if ($type == 'end') {
5712
                        $sendEndOpenQuestion = true;
5713
                    }
5714
                    break;
5715
                case 4: // end + oral
5716
                    if ($type == 'end') {
5717
                        $sendEndOralQuestion = true;
5718
                    }
5719
                    break;
5720
            }
5721
        }
5722
5723
        $user_info = api_get_user_info(api_get_user_id());
5724
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.api_get_cidreq().'&id_session='.$sessionId.'&id='.$exe_id.'&action=qualify';
5725
5726
        if (!empty($sessionId)) {
5727
            $addGeneralCoach = true;
5728
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
5729
            if ($setting === true) {
5730
                $addGeneralCoach = false;
5731
            }
5732
            $teachers = CourseManager::get_coach_list_from_course_code(
5733
                $courseCode,
5734
                $sessionId,
5735
                $addGeneralCoach
5736
            );
5737
        } else {
5738
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5739
        }
5740
5741
        if ($sendEndOpenQuestion) {
5742
            $this->send_notification_for_open_questions(
5743
                $question_list_answers,
5744
                $origin,
5745
                $exe_id,
5746
                $user_info,
5747
                $url,
5748
                $teachers
5749
            );
5750
        }
5751
5752
        if ($sendEndOralQuestion) {
5753
            $this->send_notification_for_oral_questions(
5754
                $question_list_answers,
5755
                $origin,
5756
                $exe_id,
5757
                $user_info,
5758
                $url,
5759
                $teachers
5760
            );
5761
        }
5762
5763
        if (!$sendEnd && !$sendStart) {
5764
            return false;
5765
        }
5766
5767
        $scoreLabel = '';
5768
        if ($sendEnd &&
5769
            api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true
5770
        ) {
5771
            $notificationPercentage = api_get_configuration_value('send_notification_score_in_percentage');
5772
            $scoreLabel = ExerciseLib::show_score($score, $weight, $notificationPercentage, true);
5773
            $scoreLabel = "<tr>
5774
                            <td>".get_lang('Score')."</td>
5775
                            <td>&nbsp;$scoreLabel</td>
5776
                        </tr>";
5777
        }
5778
5779
        if ($sendEnd) {
5780
            $msg = get_lang('ExerciseAttempted').'<br /><br />';
5781
        } else {
5782
            $msg = get_lang('StudentStartExercise').'<br /><br />';
5783
        }
5784
5785
        $msg .= get_lang('AttemptDetails').' : <br /><br />
5786
                    <table>
5787
                        <tr>
5788
                            <td>'.get_lang('CourseName').'</td>
5789
                            <td>#course#</td>
5790
                        </tr>
5791
                        <tr>
5792
                            <td>'.get_lang('Exercise').'</td>
5793
                            <td>&nbsp;#exercise#</td>
5794
                        </tr>
5795
                        <tr>
5796
                            <td>'.get_lang('StudentName').'</td>
5797
                            <td>&nbsp;#student_complete_name#</td>
5798
                        </tr>
5799
                        <tr>
5800
                            <td>'.get_lang('StudentEmail').'</td>
5801
                            <td>&nbsp;#email#</td>
5802
                        </tr>
5803
                        '.$scoreLabel.'
5804
                    </table>';
5805
5806
        $variables = [
5807
            '#email#' => $user_info['email'],
5808
            '#exercise#' => $this->exercise,
5809
            '#student_complete_name#' => $user_info['complete_name'],
5810
            '#course#' => $courseInfo['title']
5811
        ];
5812
        if ($origin != 'learnpath' && $sendEnd) {
5813
            $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5814
            $variables['#url#'] = $url;
5815
        }
5816
5817
        $mail_content = str_replace(array_keys($variables), array_values($variables), $msg);
5818
5819
        if ($sendEnd) {
5820
            $subject = get_lang('ExerciseAttempted');
5821
        } else {
5822
            $subject = get_lang('StudentStartExercise');
5823
        }
5824
5825
        if (!empty($teachers)) {
5826
            foreach ($teachers as $user_id => $teacher_data) {
5827
                MessageManager::send_message_simple(
5828
                    $user_id,
5829
                    $subject,
5830
                    $mail_content
5831
                );
5832
            }
5833
        }
5834
    }
5835
5836
    /**
5837
     * Sends a notification when a user ends an examn
5838
     * @param array $question_list_answers
5839
     * @param string $origin
5840
     * @param int $exe_id
5841
     */
5842
    private function send_notification_for_open_questions(
5843
        $question_list_answers,
5844
        $origin,
5845
        $exe_id,
5846
        $user_info,
5847
        $url_email,
5848
        $teachers
5849
    ) {
5850
        // Email configuration settings
5851
        $courseCode = api_get_course_id();
5852
        $course_info = api_get_course_info($courseCode);
5853
5854
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
5855
                    .get_lang('AttemptDetails').' : <br /><br />'
5856
                    .'<table>'
5857
                        .'<tr>'
5858
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5859
                            .'<td>&nbsp;<b>#course#</b></td>'
5860
                        .'</tr>'
5861
                        .'<tr>'
5862
                            .'<td>'.get_lang('TestAttempted').'</td>'
5863
                            .'<td>&nbsp;#exercise#</td>'
5864
                        .'</tr>'
5865
                        .'<tr>'
5866
                            .'<td>'.get_lang('StudentName').'</td>'
5867
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5868
                        .'</tr>'
5869
                        .'<tr>'
5870
                            .'<td>'.get_lang('StudentEmail').'</td>'
5871
                            .'<td>&nbsp;#mail#</td>'
5872
                        .'</tr>'
5873
                    .'</table>';
5874
        $open_question_list = null;
5875
        foreach ($question_list_answers as $item) {
5876
            $question = $item['question'];
5877
            $answer = $item['answer'];
5878
            $answer_type = $item['answer_type'];
5879
5880
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
5881
                $open_question_list .=
5882
                    '<tr>'
5883
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5884
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5885
                    .'</tr>'
5886
                    .'<tr>'
5887
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5888
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5889
                    .'</tr>';
5890
            }
5891
        }
5892
5893
        if (!empty($open_question_list)) {
5894
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
5895
                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
5896
            $msg .= $open_question_list;
5897
            $msg .= '</table><br />';
5898
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5899
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5900
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5901
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5902
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5903
5904
            if ($origin != 'learnpath') {
5905
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5906
            }
5907
            $msg1 = str_replace("#url#", $url_email, $msg);
5908
            $mail_content = $msg1;
5909
            $subject = get_lang('OpenQuestionsAttempted');
5910
5911
            if (!empty($teachers)) {
5912
                foreach ($teachers as $user_id => $teacher_data) {
5913
                    MessageManager::send_message_simple(
5914
                        $user_id,
5915
                        $subject,
5916
                        $mail_content
5917
                    );
5918
                }
5919
            }
5920
        }
5921
    }
5922
5923
    /**
5924
     * Send notification for oral questions
5925
     * @param array $question_list_answers
5926
     * @param string $origin
5927
     * @param int $exe_id
5928
     * @param array $user_info
5929
     * @param string $url_email
5930
     * @param array $teachers
5931
     */
5932
    private function send_notification_for_oral_questions(
5933
        $question_list_answers,
5934
        $origin,
5935
        $exe_id,
5936
        $user_info,
5937
        $url_email,
5938
        $teachers
5939
    ) {
5940
        // Email configuration settings
5941
        $courseCode = api_get_course_id();
5942
        $course_info = api_get_course_info($courseCode);
5943
5944
        $oral_question_list = null;
5945
        foreach ($question_list_answers as $item) {
5946
            $question = $item['question'];
5947
            $answer = $item['answer'];
5948
            $answer_type = $item['answer_type'];
5949
5950
            if (!empty($question) && !empty($answer) && $answer_type == ORAL_EXPRESSION) {
5951
                $oral_question_list .= '<br /><table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'
5952
                    .'<tr>'
5953
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5954
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5955
                    .'</tr>'
5956
                    .'<tr>'
5957
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5958
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5959
                    .'</tr></table>';
5960
            }
5961
        }
5962
5963
        if (!empty($oral_question_list)) {
5964
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
5965
                    '.get_lang('AttemptDetails').' : <br /><br />'
5966
                    .'<table>'
5967
                        .'<tr>'
5968
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5969
                            .'<td>&nbsp;<b>#course#</b></td>'
5970
                        .'</tr>'
5971
                        .'<tr>'
5972
                            .'<td>'.get_lang('TestAttempted').'</td>'
5973
                            .'<td>&nbsp;#exercise#</td>'
5974
                        .'</tr>'
5975
                        .'<tr>'
5976
                            .'<td>'.get_lang('StudentName').'</td>'
5977
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5978
                        .'</tr>'
5979
                        .'<tr>'
5980
                            .'<td>'.get_lang('StudentEmail').'</td>'
5981
                            .'<td>&nbsp;#mail#</td>'
5982
                        .'</tr>'
5983
                    .'</table>';
5984
            $msg .= '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'), $oral_question_list).'<br />';
5985
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5986
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5987
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5988
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5989
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5990
5991
            if ($origin != 'learnpath') {
5992
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5993
            }
5994
            $msg1 = str_replace("#url#", $url_email, $msg);
5995
            $mail_content = $msg1;
5996
            $subject = get_lang('OralQuestionsAttempted');
5997
5998
            if (!empty($teachers)) {
5999
                foreach ($teachers as $user_id => $teacher_data) {
6000
                    MessageManager::send_message_simple(
6001
                        $user_id,
6002
                        $subject,
6003
                        $mail_content
6004
                    );
6005
                }
6006
            }
6007
        }
6008
    }
6009
6010
    /**
6011
     * @param array $user_data result of api_get_user_info()
6012
     * @param string $start_date
6013
     * @param null $duration
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $duration is correct as it would always require null to be passed?
Loading history...
6014
     * @param string $ip Optional. The user IP
6015
     * @return string
6016
     */
6017
    public function show_exercise_result_header(
6018
        $user_data,
6019
        $start_date = null,
6020
        $duration = null,
6021
        $ip = null
6022
    ) {
6023
        $array = [];
6024
        if (!empty($user_data)) {
6025
            $array[] = [
6026
                'title' => get_lang('Name'),
6027
                'content' => $user_data['complete_name']
6028
            ];
6029
            $array[] = [
6030
                'title' => get_lang('Username'),
6031
                'content' => $user_data['username']
6032
            ];
6033
            if (!empty($user_data['official_code'])) {
6034
                $array[] = [
6035
                    'title' => get_lang('OfficialCode'),
6036
                    'content' => $user_data['official_code']
6037
                ];
6038
            }
6039
        }
6040
        // Description can be very long and is generally meant to explain
6041
        //   rules *before* the exam. Leaving here to make display easier if
6042
        //   necessary
6043
        /*
6044
        if (!empty($this->description)) {
6045
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
6046
        }
6047
        */
6048
        if (!empty($start_date)) {
6049
            $array[] = ['title' => get_lang('StartDate'), 'content' => $start_date];
6050
        }
6051
6052
        if (!empty($duration)) {
6053
            $array[] = ['title' => get_lang('Duration'), 'content' => $duration];
6054
        }
6055
6056
        if (!empty($ip)) {
6057
            $array[] = ['title' => get_lang('IP'), 'content' => $ip];
6058
        }
6059
6060
        $icon = Display::return_icon(
6061
            'test-quiz.png',
6062
            get_lang('Result'),
6063
            null,
6064
            ICON_SIZE_MEDIUM
6065
        );
6066
6067
        $html = '<div class="question-result">';
6068
6069
        if (api_get_configuration_value('save_titles_as_html')) {
6070
            $html .= $this->get_formated_title();
6071
            $html .= Display::page_header(get_lang('Result'));
6072
        } else {
6073
            $html .= Display::page_header(
6074
                $icon.PHP_EOL.$this->exercise.' : '.get_lang('Result')
6075
            );
6076
        }
6077
6078
        $hide = api_get_configuration_value('hide_user_info_in_quiz_result');
6079
6080
        if ($hide === false) {
6081
            $html .= Display::description($array);
6082
        }
6083
6084
        $html .= "</div>";
6085
        return $html;
6086
    }
6087
6088
    /**
6089
     * Create a quiz from quiz data
6090
     * @param string  Title
6091
     * @param int     Time before it expires (in minutes)
6092
     * @param int     Type of exercise
6093
     * @param int     Whether it's randomly picked questions (1) or not (0)
6094
     * @param int     Whether the exercise is visible to the user (1) or not (0)
6095
     * @param int     Whether the results are show to the user (0) or not (1)
6096
     * @param int     Maximum number of attempts (0 if no limit)
6097
     * @param int     Feedback type
6098
     * @todo this was function was added due the import exercise via CSV
6099
     * @return    int New exercise ID
6100
     */
6101
    public function createExercise(
6102
        $title,
6103
        $expired_time = 0,
6104
        $type = 2,
6105
        $random = 0,
6106
        $active = 1,
6107
        $results_disabled = 0,
6108
        $max_attempt = 0,
6109
        $feedback = 3,
6110
        $propagateNegative = 0
6111
    ) {
6112
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
6113
        $type = intval($type);
6114
        $random = intval($random);
6115
        $active = intval($active);
6116
        $results_disabled = intval($results_disabled);
6117
        $max_attempt = intval($max_attempt);
6118
        $feedback = intval($feedback);
6119
        $expired_time = intval($expired_time);
6120
        $title = Database::escape_string($title);
6121
        $propagateNegative = intval($propagateNegative);
6122
        $sessionId = api_get_session_id();
6123
        $course_id = api_get_course_int_id();
6124
        // Save a new quiz
6125
        $sql = "INSERT INTO $tbl_quiz (
6126
                c_id,
6127
                title,
6128
                type,
6129
                random,
6130
                active,
6131
                results_disabled,
6132
                max_attempt,
6133
                start_time,
6134
                end_time,
6135
                feedback_type,
6136
                expired_time,
6137
                session_id,
6138
                propagate_neg
6139
            )
6140
            VALUES (
6141
                '$course_id',
6142
                '$title',
6143
                $type,
6144
                $random,
6145
                $active,
6146
                $results_disabled,
6147
                $max_attempt,
6148
                '',
6149
                '',
6150
                $feedback,
6151
                $expired_time,
6152
                $sessionId,
6153
                $propagateNegative
6154
            )";
6155
        Database::query($sql);
6156
        $quiz_id = Database::insert_id();
6157
6158
        if ($quiz_id) {
6159
            $sql = "UPDATE $tbl_quiz SET id = iid WHERE iid = {$quiz_id} ";
6160
            Database::query($sql);
6161
        }
6162
6163
        return $quiz_id;
6164
    }
6165
6166
    /**
6167
     * Returns the exercise result
6168
     * @param 	int		attempt id
6169
     * @return 	float 	exercise result
6170
     */
6171
    public function get_exercise_result($exe_id)
6172
    {
6173
        $result = [];
6174
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
6175
6176
        if (!empty($track_exercise_info)) {
6177
            $totalScore = 0;
6178
            $objExercise = new Exercise();
6179
            $objExercise->read($track_exercise_info['exe_exo_id']);
6180
            if (!empty($track_exercise_info['data_tracking'])) {
6181
                $question_list = explode(',', $track_exercise_info['data_tracking']);
6182
            }
6183
            foreach ($question_list as $questionId) {
6184
                $question_result = $objExercise->manage_answer(
6185
                    $exe_id,
6186
                    $questionId,
6187
                    '',
6188
                    'exercise_show',
6189
                    [],
6190
                    false,
6191
                    true,
6192
                    false,
6193
                    $objExercise->selectPropagateNeg()
6194
                );
6195
                $totalScore += $question_result['score'];
6196
            }
6197
6198
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
6199
                $totalScore = 0;
6200
            }
6201
            $result = [
6202
                'score' => $totalScore,
6203
                'weight' => $track_exercise_info['exe_weighting']
6204
            ];
6205
        }
6206
        return $result;
6207
    }
6208
6209
    /**
6210
     * Checks if the exercise is visible due a lot of conditions
6211
     * visibility, time limits, student attempts
6212
     * Return associative array
6213
     * value : true if exercise visible
6214
     * message : HTML formatted message
6215
     * rawMessage : text message
6216
     * @param int $lpId
6217
     * @param int $lpItemId
6218
     * @param int $lpItemViewId
6219
     * @param bool $filterByAdmin
6220
     * @return array
6221
     */
6222
    public function is_visible(
6223
        $lpId = 0,
6224
        $lpItemId = 0,
6225
        $lpItemViewId = 0,
6226
        $filterByAdmin = true
6227
    ) {
6228
        // 1. By default the exercise is visible
6229
        $isVisible = true;
6230
        $message = null;
6231
6232
        // 1.1 Admins and teachers can access to the exercise
6233
        if ($filterByAdmin) {
6234
            if (api_is_platform_admin() || api_is_course_admin()) {
6235
                return ['value' => true, 'message' => ''];
6236
            }
6237
        }
6238
6239
        // Deleted exercise.
6240
        if ($this->active == -1) {
6241
            return [
6242
                'value' => false,
6243
                'message' => Display::return_message(
6244
                    get_lang('ExerciseNotFound'),
6245
                    'warning',
6246
                    false
6247
                ),
6248
                'rawMessage' => get_lang('ExerciseNotFound')
6249
            ];
6250
        }
6251
6252
        // Checking visibility in the item_property table.
6253
        $visibility = api_get_item_visibility(
6254
            api_get_course_info(),
6255
            TOOL_QUIZ,
6256
            $this->id,
6257
            api_get_session_id()
6258
        );
6259
6260
        if ($visibility == 0 || $visibility == 2) {
6261
            $this->active = 0;
6262
        }
6263
6264
        // 2. If the exercise is not active.
6265
        if (empty($lpId)) {
6266
            // 2.1 LP is OFF
6267
            if ($this->active == 0) {
6268
                return [
6269
                    'value' => false,
6270
                    'message' => Display::return_message(
6271
                        get_lang('ExerciseNotFound'),
6272
                        'warning',
6273
                        false
6274
                    ),
6275
                    'rawMessage' => get_lang('ExerciseNotFound')
6276
                ];
6277
            }
6278
        } else {
6279
            // 2.1 LP is loaded
6280
            if ($this->active == 0 &&
6281
                !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())
6282
            ) {
6283
                return [
6284
                    'value' => false,
6285
                    'message' => Display::return_message(
6286
                        get_lang('ExerciseNotFound'),
6287
                        'warning',
6288
                        false
6289
                    ),
6290
                    'rawMessage' => get_lang('ExerciseNotFound')
6291
                ];
6292
            }
6293
        }
6294
6295
        //3. We check if the time limits are on
6296
        if (!empty($this->start_time) || !empty($this->end_time)) {
6297
            $limitTimeExists = true;
6298
        } else {
6299
            $limitTimeExists = false;
6300
        }
6301
6302
        if ($limitTimeExists) {
6303
            $timeNow = time();
6304
            $existsStartDate = false;
6305
            $nowIsAfterStartDate = true;
6306
            $existsEndDate = false;
6307
            $nowIsBeforeEndDate = true;
6308
6309
            if (!empty($this->start_time)) {
6310
                $existsStartDate = true;
6311
            }
6312
6313
            if (!empty($this->end_time)) {
6314
                $existsEndDate = true;
6315
            }
6316
6317
            // check if we are before-or-after end-or-start date
6318
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
6319
                $nowIsAfterStartDate = false;
6320
            }
6321
6322
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
6323
                $nowIsBeforeEndDate = false;
6324
            }
6325
6326
            // lets check all cases
6327
            if ($existsStartDate && !$existsEndDate) {
6328
                // exists start date and dont exists end date
6329
                if ($nowIsAfterStartDate) {
6330
                    // after start date, no end date
6331
                    $isVisible = true;
6332
                    $message = sprintf(
6333
                        get_lang('ExerciseAvailableSinceX'),
6334
                        api_convert_and_format_date($this->start_time)
6335
                    );
6336
                } else {
6337
                    // before start date, no end date
6338
                    $isVisible = false;
6339
                    $message = sprintf(
6340
                        get_lang('ExerciseAvailableFromX'),
6341
                        api_convert_and_format_date($this->start_time)
6342
                    );
6343
                }
6344
            } elseif (!$existsStartDate && $existsEndDate) {
6345
                // doesnt exist start date, exists end date
6346
                if ($nowIsBeforeEndDate) {
6347
                    // before end date, no start date
6348
                    $isVisible = true;
6349
                    $message = sprintf(
6350
                        get_lang('ExerciseAvailableUntilX'),
6351
                        api_convert_and_format_date($this->end_time)
6352
                    );
6353
                } else {
6354
                    // after end date, no start date
6355
                    $isVisible = false;
6356
                    $message = sprintf(
6357
                        get_lang('ExerciseAvailableUntilX'),
6358
                        api_convert_and_format_date($this->end_time)
6359
                    );
6360
                }
6361
            } elseif ($existsStartDate && $existsEndDate) {
6362
                // exists start date and end date
6363
                if ($nowIsAfterStartDate) {
6364
                    if ($nowIsBeforeEndDate) {
6365
                        // after start date and before end date
6366
                        $isVisible = true;
6367
                        $message = sprintf(
6368
                            get_lang('ExerciseIsActivatedFromXToY'),
6369
                            api_convert_and_format_date($this->start_time),
6370
                            api_convert_and_format_date($this->end_time)
6371
                        );
6372
                    } else {
6373
                        // after start date and after end date
6374
                        $isVisible = false;
6375
                        $message = sprintf(
6376
                            get_lang('ExerciseWasActivatedFromXToY'),
6377
                            api_convert_and_format_date($this->start_time),
6378
                            api_convert_and_format_date($this->end_time)
6379
                        );
6380
                    }
6381
                } else {
6382
                    if ($nowIsBeforeEndDate) {
6383
                        // before start date and before end date
6384
                        $isVisible = false;
6385
                        $message = sprintf(
6386
                            get_lang('ExerciseWillBeActivatedFromXToY'),
6387
                            api_convert_and_format_date($this->start_time),
6388
                            api_convert_and_format_date($this->end_time)
6389
                        );
6390
                    }
6391
                    // case before start date and after end date is impossible
6392
                }
6393
            } elseif (!$existsStartDate && !$existsEndDate) {
6394
                // doesnt exist start date nor end date
6395
                $isVisible = true;
6396
                $message = '';
6397
            }
6398
        }
6399
6400
        // 4. We check if the student have attempts
6401
        $exerciseAttempts = $this->selectAttempts();
6402
6403
        if ($isVisible) {
6404
            if ($exerciseAttempts > 0) {
6405
                $attemptCount = Event::get_attempt_count_not_finished(
6406
                    api_get_user_id(),
6407
                    $this->id,
6408
                    $lpId,
6409
                    $lpItemId,
6410
                    $lpItemViewId
6411
                );
6412
6413
                if ($attemptCount >= $exerciseAttempts) {
6414
                    $message = sprintf(
6415
                        get_lang('ReachedMaxAttempts'),
6416
                        $this->name,
6417
                        $exerciseAttempts
6418
                    );
6419
                    $isVisible = false;
6420
                }
6421
            }
6422
        }
6423
6424
        $rawMessage = '';
6425
        if (!empty($message)) {
6426
            $rawMessage = $message;
6427
            $message = Display::return_message($message, 'warning', false);
6428
        }
6429
6430
        return [
6431
            'value' => $isVisible,
6432
            'message' => $message,
6433
            'rawMessage' => $rawMessage
6434
        ];
6435
    }
6436
6437
    public function added_in_lp()
6438
    {
6439
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
6440
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
6441
                WHERE 
6442
                    c_id = {$this->course_id} AND 
6443
                    item_type = '".TOOL_QUIZ."' AND 
6444
                    path = '{$this->id}'";
6445
        $result = Database::query($sql);
6446
        if (Database::num_rows($result) > 0) {
6447
            return true;
6448
        }
6449
        return false;
6450
    }
6451
6452
    /**
6453
     * Returns an array with the media list
6454
     * @param array question list
6455
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
6456
     * <code>
6457
     * array (size=2)
6458
     *  999 =>
6459
     *    array (size=3)
6460
     *      0 => int 7
6461
     *      1 => int 6
6462
     *      2 => int 3254
6463
     *  100 =>
6464
     *   array (size=1)
6465
     *      0 => int 5
6466
     *  </code>
6467
     */
6468
    private function setMediaList($questionList)
6469
    {
6470
        $mediaList = [];
6471
        if (!empty($questionList)) {
6472
            foreach ($questionList as $questionId) {
6473
                $objQuestionTmp = Question::read($questionId, $this->course_id);
6474
6475
                // If a media question exists
6476
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
6477
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
6478
                } else {
6479
                    //Always the last item
6480
                    $mediaList[999][] = $objQuestionTmp->id;
6481
                }
6482
            }
6483
        }
6484
        $this->mediaList = $mediaList;
6485
    }
6486
6487
    /**
6488
     * Returns an array with this form
6489
     * @example
6490
     * <code>
6491
     * array (size=3)
6492
     * 999 =>
6493
     * array (size=3)
6494
     * 0 => int 3422
6495
     * 1 => int 3423
6496
     * 2 => int 3424
6497
     * 100 =>
6498
     * array (size=2)
6499
     * 0 => int 3469
6500
     * 1 => int 3470
6501
     * 101 =>
6502
     * array (size=1)
6503
     * 0 => int 3482
6504
     * </code>
6505
     * The array inside the key 999 means the question list that belongs to the media id = 999,
6506
     * this case is special because 999 means "no media".
6507
     * @return array
6508
     */
6509
    public function getMediaList()
6510
    {
6511
        return $this->mediaList;
6512
    }
6513
6514
    /**
6515
     * Is media question activated?
6516
     * @return bool
6517
     */
6518
    public function mediaIsActivated()
6519
    {
6520
        $mediaQuestions = $this->getMediaList();
6521
        $active = false;
6522
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6523
            $media_count = count($mediaQuestions);
6524
            if ($media_count > 1) {
6525
                return true;
6526
            } elseif ($media_count == 1) {
6527
                if (isset($mediaQuestions[999])) {
6528
                    return false;
6529
                } else {
6530
                    return true;
6531
                }
6532
            }
6533
        }
6534
6535
        return $active;
6536
    }
6537
6538
    /**
6539
     * Gets question list from the exercise
6540
     *
6541
     * @return array
6542
     */
6543
    public function getQuestionList()
6544
    {
6545
        return $this->questionList;
6546
    }
6547
6548
    /**
6549
     * Question list with medias compressed like this
6550
     * @example
6551
     * <code>
6552
     * array(
6553
     *      question_id_1,
6554
     *      question_id_2,
6555
     *      media_id, <- this media id contains question ids
6556
     *      question_id_3,
6557
     * )
6558
     * </code>
6559
     * @return array
6560
     */
6561
    public function getQuestionListWithMediasCompressed()
6562
    {
6563
        return $this->questionList;
6564
    }
6565
6566
    /**
6567
     * Question list with medias uncompressed like this
6568
     * @example
6569
     * <code>
6570
     * array(
6571
     *      question_id,
6572
     *      question_id,
6573
     *      question_id, <- belongs to a media id
6574
     *      question_id, <- belongs to a media id
6575
     *      question_id,
6576
     * )
6577
     * </code>
6578
     * @return array
6579
     */
6580
    public function getQuestionListWithMediasUncompressed()
6581
    {
6582
        return $this->questionListUncompressed;
6583
    }
6584
6585
    /**
6586
     * Sets the question list when the exercise->read() is executed
6587
     * @param   bool    $adminView  Whether to view the set the list of *all* questions or just the normal student view
6588
     */
6589
    public function setQuestionList($adminView = false)
6590
    {
6591
        // Getting question list.
6592
        $questionList = $this->selectQuestionList(true, $adminView);
6593
        $this->setMediaList($questionList);
6594
        $this->questionList = $this->transformQuestionListWithMedias(
6595
            $questionList,
6596
            false
6597
        );
6598
        $this->questionListUncompressed = $this->transformQuestionListWithMedias(
6599
            $questionList,
6600
            true
6601
        );
6602
    }
6603
6604
    /**
6605
     * @params array question list
6606
     * @params bool expand or not question list (true show all questions,
6607
     * false show media question id instead of the question ids)
6608
     **/
6609
    public function transformQuestionListWithMedias(
6610
        $question_list,
6611
        $expand_media_questions = false
6612
    ) {
6613
        $new_question_list = [];
6614
        if (!empty($question_list)) {
6615
            $media_questions = $this->getMediaList();
6616
6617
            $media_active = $this->mediaIsActivated($media_questions);
6618
6619
            if ($media_active) {
6620
                $counter = 1;
6621
                foreach ($question_list as $question_id) {
6622
                    $add_question = true;
6623
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6624
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6625
                            $add_question = false;
6626
                            if (!in_array($media_id, $new_question_list)) {
6627
                                $new_question_list[$counter] = $media_id;
6628
                                $counter++;
6629
                            }
6630
                            break;
6631
                        }
6632
                    }
6633
                    if ($add_question) {
6634
                        $new_question_list[$counter] = $question_id;
6635
                        $counter++;
6636
                    }
6637
                }
6638
                if ($expand_media_questions) {
6639
                    $media_key_list = array_keys($media_questions);
6640
                    foreach ($new_question_list as &$question_id) {
6641
                        if (in_array($question_id, $media_key_list)) {
6642
                            $question_id = $media_questions[$question_id];
6643
                        }
6644
                    }
6645
                    $new_question_list = array_flatten($new_question_list);
6646
                }
6647
            } else {
6648
                $new_question_list = $question_list;
6649
            }
6650
        }
6651
6652
        return $new_question_list;
6653
    }
6654
6655
    /**
6656
     * Get question list depend on the random settings.
6657
     *
6658
     * @return array
6659
     */
6660
    public function get_validated_question_list()
6661
    {
6662
        $result = [];
6663
        $isRandomByCategory = $this->isRandomByCat();
6664
        if ($isRandomByCategory == 0) {
6665
            if ($this->isRandom()) {
6666
                $result = $this->selectRandomList();
6667
            } else {
6668
                $result = $this->selectQuestionList();
6669
            }
6670
        } else {
6671
            if ($this->isRandom()) {
6672
                // USE question categories
6673
                // get questions by category for this exercise
6674
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6675
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6676
                // value is the array of question id of this category
6677
                $questionList = [];
6678
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6679
                $isRandomByCategory = $this->selectRandomByCat();
6680
                // We sort categories based on the term between [] in the head
6681
                // of the category's description
6682
                /* examples of categories :
6683
                 * [biologie] Maitriser les mecanismes de base de la genetique
6684
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6685
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6686
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6687
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6688
                 * [chimie] Connaître les charges des particules
6689
                 * We want that in the order of the groups defined by the term
6690
                 * between brackets at the beginning of the category title
6691
                */
6692
                // If test option is Grouped By Categories
6693
                if ($isRandomByCategory == 2) {
6694
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6695
                }
6696
                while (list($cat_id, $tabquestion) = each($tabCategoryQuestions)) {
6697
                    $number_of_random_question = $this->random;
6698
                    if ($this->random == -1) {
6699
                        $number_of_random_question = count($this->questionList);
6700
                    }
6701
                    $questionList = array_merge(
6702
                        $questionList,
6703
                        TestCategory::getNElementsFromArray(
6704
                            $tabquestion,
6705
                            $number_of_random_question
6706
                        )
6707
                    );
6708
                }
6709
                // shuffle the question list if test is not grouped by categories
6710
                if ($isRandomByCategory == 1) {
6711
                    shuffle($questionList); // or not
6712
                }
6713
                $result = $questionList;
6714
            } 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...
6715
                // Problem, random by category has been selected and
6716
                // we have no $this->isRandom number of question selected
6717
                // Should not happened
6718
            }
6719
        }
6720
6721
        return $result;
6722
    }
6723
6724
    public function get_question_list($expand_media_questions = false)
6725
    {
6726
        $question_list = $this->get_validated_question_list();
6727
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6728
        return $question_list;
6729
    }
6730
6731
    public function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6732
    {
6733
        $new_question_list = [];
6734
        if (!empty($question_list)) {
6735
            $media_questions = $this->getMediaList();
6736
            $media_active = $this->mediaIsActivated($media_questions);
6737
6738
            if ($media_active) {
6739
                $counter = 1;
6740
                foreach ($question_list as $question_id) {
6741
                    $add_question = true;
6742
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6743
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6744
                            $add_question = false;
6745
                            if (!in_array($media_id, $new_question_list)) {
6746
                                $new_question_list[$counter] = $media_id;
6747
                                $counter++;
6748
                            }
6749
                            break;
6750
                        }
6751
                    }
6752
                    if ($add_question) {
6753
                        $new_question_list[$counter] = $question_id;
6754
                        $counter++;
6755
                    }
6756
                }
6757
                if ($expand_media_questions) {
6758
                    $media_key_list = array_keys($media_questions);
6759
                    foreach ($new_question_list as &$question_id) {
6760
                        if (in_array($question_id, $media_key_list)) {
6761
                            $question_id = $media_questions[$question_id];
6762
                        }
6763
                    }
6764
                    $new_question_list = array_flatten($new_question_list);
6765
                }
6766
            } else {
6767
                $new_question_list = $question_list;
6768
            }
6769
        }
6770
        return $new_question_list;
6771
    }
6772
6773
    /**
6774
     * @param int $exe_id
6775
     * @return array
6776
     */
6777
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6778
    {
6779
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6780
        $exe_id = intval($exe_id);
6781
        $sql_track = "SELECT * FROM $track_exercises WHERE exe_id = $exe_id ";
6782
        $result = Database::query($sql_track);
6783
        $new_array = [];
6784
        if (Database::num_rows($result) > 0) {
6785
            $new_array = Database::fetch_array($result, 'ASSOC');
6786
            $new_array['duration'] = null;
6787
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6788
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6789
6790
            if (!empty($start_date) && !empty($end_date)) {
6791
                $start_date = api_strtotime($start_date, 'UTC');
6792
                $end_date = api_strtotime($end_date, 'UTC');
6793
                if ($start_date && $end_date) {
6794
                    $mytime = $end_date - $start_date;
6795
                    $new_learnpath_item = new learnpathItem(null);
6796
                    $time_attemp = $new_learnpath_item->get_scorm_time('js', $mytime);
6797
                    $h = get_lang('h');
6798
                    $time_attemp = str_replace('NaN', '00'.$h.'00\'00"', $time_attemp);
6799
                    $new_array['duration'] = $time_attemp;
6800
                }
6801
            }
6802
        }
6803
        return $new_array;
6804
    }
6805
6806
    /**
6807
     * @param int $exe_id
6808
     * @param int $question_id
6809
     * @param string $action
6810
     */
6811
    public function editQuestionToRemind($exe_id, $question_id, $action = 'add')
6812
    {
6813
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6814
        $question_id = intval($question_id);
6815
        $exe_id = intval($exe_id);
6816
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6817
        if ($exercise_info) {
6818
            if (empty($exercise_info['questions_to_check'])) {
6819
                if ($action == 'add') {
6820
                    $sql = "UPDATE $track_exercises 
6821
                            SET questions_to_check = '$question_id' 
6822
                            WHERE exe_id = $exe_id ";
6823
                    Database::query($sql);
6824
                }
6825
            } else {
6826
                $remind_list = explode(',', $exercise_info['questions_to_check']);
6827
                $remind_list_string = '';
6828
                if ($action == 'add') {
6829
                    if (!in_array($question_id, $remind_list)) {
6830
                        $newRemindList = [];
6831
                        $remind_list[] = $question_id;
6832
                        $questionListInSession = Session::read('questionList');
6833
                        if (!empty($questionListInSession)) {
6834
                            foreach ($questionListInSession as $originalQuestionId) {
6835
                                if (in_array($originalQuestionId, $remind_list)) {
6836
                                    $newRemindList[] = $originalQuestionId;
6837
                                }
6838
                            }
6839
                        }
6840
                        $remind_list_string = implode(',', $newRemindList);
6841
                    }
6842
                } elseif ($action == 'delete') {
6843
                    if (!empty($remind_list)) {
6844
                        if (in_array($question_id, $remind_list)) {
6845
                            $remind_list = array_flip($remind_list);
6846
                            unset($remind_list[$question_id]);
6847
                            $remind_list = array_flip($remind_list);
6848
6849
                            if (!empty($remind_list)) {
6850
                                sort($remind_list);
6851
                                array_filter($remind_list);
6852
                                $remind_list_string = implode(',', $remind_list);
6853
                            }
6854
                        }
6855
                    }
6856
                }
6857
                $value = Database::escape_string($remind_list_string);
6858
                $sql = "UPDATE $track_exercises 
6859
                        SET questions_to_check = '$value' 
6860
                        WHERE exe_id = $exe_id ";
6861
                Database::query($sql);
6862
            }
6863
        }
6864
    }
6865
6866
    /**
6867
     * @param string $answer
6868
     * @return mixed
6869
     */
6870
    public function fill_in_blank_answer_to_array($answer)
6871
    {
6872
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6873
        $teacher_answer_list = $teacher_answer_list[0];
6874
        return $teacher_answer_list;
6875
    }
6876
6877
    /**
6878
     * @param string $answer
6879
     * @return string
6880
     */
6881
    public function fill_in_blank_answer_to_string($answer)
6882
    {
6883
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6884
        $result = '';
6885
        if (!empty($teacher_answer_list)) {
6886
            $i = 0;
6887
            foreach ($teacher_answer_list as $teacher_item) {
6888
                $value = null;
6889
                //Cleaning student answer list
6890
                $value = strip_tags($teacher_item);
6891
                $value = api_substr($value, 1, api_strlen($value) - 2);
6892
                $value = explode('/', $value);
6893
                if (!empty($value[0])) {
6894
                    $value = trim($value[0]);
6895
                    $value = str_replace('&nbsp;', '', $value);
6896
                    $result .= $value;
6897
                }
6898
            }
6899
        }
6900
        return $result;
6901
    }
6902
6903
    /**
6904
     * @return string
6905
     */
6906
    public function return_time_left_div()
6907
    {
6908
        $html = '<div id="clock_warning" style="display:none">';
6909
        $html .= Display::return_message(
6910
            get_lang('ReachedTimeLimit'),
6911
            'warning'
6912
        );
6913
        $html .= ' ';
6914
        $html .= sprintf(
6915
            get_lang('YouWillBeRedirectedInXSeconds'),
6916
            '<span id="counter_to_redirect" class="red_alert"></span>'
6917
        );
6918
        $html .= '</div>';
6919
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6920
        return $html;
6921
    }
6922
6923
    /**
6924
     * @return int
6925
     */
6926
    public function get_count_question_list()
6927
    {
6928
        // Real question count
6929
        $question_count = 0;
6930
        $question_list = $this->get_question_list();
6931
        if (!empty($question_list)) {
6932
            $question_count = count($question_list);
6933
        }
6934
        return $question_count;
6935
    }
6936
6937
    /**
6938
     * Get categories added in the exercise--category matrix
6939
     * @return array
6940
     */
6941
    public function get_categories_in_exercise()
6942
    {
6943
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6944
        if (!empty($this->id)) {
6945
            $sql = "SELECT * FROM $table
6946
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6947
            $result = Database::query($sql);
6948
            $list = [];
6949
            if (Database::num_rows($result)) {
6950
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6951
                    $list[$row['category_id']] = $row;
6952
                }
6953
                return $list;
6954
            }
6955
        }
6956
        return [];
6957
    }
6958
6959
    /**
6960
     * Get total number of question that will be parsed when using the category/exercise
6961
     * @return int
6962
     */
6963
    public function getNumberQuestionExerciseCategory()
6964
    {
6965
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6966
        if (!empty($this->id)) {
6967
            $sql = "SELECT SUM(count_questions) count_questions
6968
                    FROM $table
6969
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6970
            $result = Database::query($sql);
6971
            if (Database::num_rows($result)) {
6972
                $row = Database::fetch_array($result);
6973
                return $row['count_questions'];
6974
            }
6975
        }
6976
        return 0;
6977
    }
6978
6979
    /**
6980
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table
6981
     * @param array $categories
6982
     */
6983
    public function save_categories_in_exercise($categories)
6984
    {
6985
        if (!empty($categories) && !empty($this->id)) {
6986
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6987
            $sql = "DELETE FROM $table
6988
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6989
            Database::query($sql);
6990
            if (!empty($categories)) {
6991
                foreach ($categories as $category_id => $count_questions) {
6992
                    $params = [
6993
                        'c_id' => $this->course_id,
6994
                        'exercise_id' => $this->id,
6995
                        'category_id' => $category_id,
6996
                        'count_questions' => $count_questions
6997
                    ];
6998
                    Database::insert($table, $params);
6999
                }
7000
            }
7001
        }
7002
    }
7003
7004
    /**
7005
     * @param array $questionList
7006
     * @param int $currentQuestion
7007
     * @param array $conditions
7008
     * @param string $link
7009
     * @return string
7010
     */
7011
    public function progressExercisePaginationBar(
7012
        $questionList,
7013
        $currentQuestion,
7014
        $conditions,
7015
        $link
7016
    ) {
7017
        $mediaQuestions = $this->getMediaList();
7018
7019
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
7020
        $counter = 0;
7021
        $nextValue = 0;
7022
        $wasMedia = false;
7023
        $before = 0;
7024
        $counterNoMedias = 0;
7025
        foreach ($questionList as $questionId) {
7026
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
7027
7028
            if (!empty($nextValue)) {
7029
                if ($wasMedia) {
7030
                    $nextValue = $nextValue - $before + 1;
7031
                }
7032
            }
7033
7034
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
7035
                $fixedValue = $counterNoMedias;
7036
7037
                $html .= Display::progressPaginationBar(
7038
                    $nextValue,
7039
                    $mediaQuestions[$questionId],
7040
                    $currentQuestion,
7041
                    $fixedValue,
7042
                    $conditions,
7043
                    $link,
7044
                    true,
7045
                    true
7046
                );
7047
7048
                $counter += count($mediaQuestions[$questionId]) - 1;
7049
                $before = count($questionList);
7050
                $wasMedia = true;
7051
                $nextValue += count($questionList);
7052
            } else {
7053
                $html .= Display::parsePaginationItem(
7054
                    $questionId,
7055
                    $isCurrent,
7056
                    $conditions,
7057
                    $link,
7058
                    $counter
7059
                );
7060
                $counter++;
7061
                $nextValue++;
7062
                $wasMedia = false;
7063
            }
7064
            $counterNoMedias++;
7065
        }
7066
        $html .= '</ul></div>';
7067
7068
        return $html;
7069
    }
7070
7071
7072
    /**
7073
     *  Shows a list of numbers that represents the question to answer in a exercise
7074
     *
7075
     * @param array $categories
7076
     * @param int $current
7077
     * @param array $conditions
7078
     * @param string $link
7079
     * @return string
7080
     */
7081
    public function progressExercisePaginationBarWithCategories(
7082
        $categories,
7083
        $current,
7084
        $conditions = [],
7085
        $link = null
7086
    ) {
7087
        $html = null;
7088
        $counterNoMedias = 0;
7089
        $nextValue = 0;
7090
        $wasMedia = false;
7091
        $before = 0;
7092
7093
        if (!empty($categories)) {
7094
            $selectionType = $this->getQuestionSelectionType();
7095
            $useRootAsCategoryTitle = false;
7096
7097
            // Grouping questions per parent category see BT#6540
7098
            if (in_array(
7099
                $selectionType,
7100
                [
7101
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
7102
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM
7103
                ]
7104
            )) {
7105
                $useRootAsCategoryTitle = true;
7106
            }
7107
7108
            // If the exercise is set to only show the titles of the categories
7109
            // at the root of the tree, then pre-order the categories tree by
7110
            // removing children and summing their questions into the parent
7111
            // categories
7112
            if ($useRootAsCategoryTitle) {
7113
                // The new categories list starts empty
7114
                $newCategoryList = [];
7115
                foreach ($categories as $category) {
7116
                    $rootElement = $category['root'];
7117
7118
                    if (isset($category['parent_info'])) {
7119
                        $rootElement = $category['parent_info']['id'];
7120
                    }
7121
7122
                    //$rootElement = $category['id'];
7123
                    // If the current category's ancestor was never seen
7124
                    // before, then declare it and assign the current
7125
                    // category to it.
7126
                    if (!isset($newCategoryList[$rootElement])) {
7127
                        $newCategoryList[$rootElement] = $category;
7128
                    } else {
7129
                        // If it was already seen, then merge the previous with
7130
                        // the current category
7131
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
7132
                        $category['question_list'] = array_merge($oldQuestionList, $category['question_list']);
7133
                        $newCategoryList[$rootElement] = $category;
7134
                    }
7135
                }
7136
                // Now use the newly built categories list, with only parents
7137
                $categories = $newCategoryList;
7138
            }
7139
7140
            foreach ($categories as $category) {
7141
                $questionList = $category['question_list'];
7142
                // Check if in this category there questions added in a media
7143
                $mediaQuestionId = $category['media_question'];
7144
                $isMedia = false;
7145
                $fixedValue = null;
7146
7147
                // Media exists!
7148
                if ($mediaQuestionId != 999) {
7149
                    $isMedia = true;
7150
                    $fixedValue = $counterNoMedias;
7151
                }
7152
7153
                //$categoryName = $category['path']; << show the path
7154
                $categoryName = $category['name'];
7155
7156
                if ($useRootAsCategoryTitle) {
7157
                    if (isset($category['parent_info'])) {
7158
                        $categoryName = $category['parent_info']['title'];
7159
                    }
7160
                }
7161
                $html .= '<div class="row">';
7162
                $html .= '<div class="span2">'.$categoryName.'</div>';
7163
                $html .= '<div class="span8">';
7164
7165
                if (!empty($nextValue)) {
7166
                    if ($wasMedia) {
7167
                        $nextValue = $nextValue - $before + 1;
7168
                    }
7169
                }
7170
                $html .= Display::progressPaginationBar(
7171
                    $nextValue,
7172
                    $questionList,
7173
                    $current,
7174
                    $fixedValue,
7175
                    $conditions,
7176
                    $link,
7177
                    $isMedia,
7178
                    true
7179
                );
7180
                $html .= '</div>';
7181
                $html .= '</div>';
7182
7183
                if ($mediaQuestionId == 999) {
7184
                    $counterNoMedias += count($questionList);
7185
                } else {
7186
                    $counterNoMedias++;
7187
                }
7188
7189
                $nextValue += count($questionList);
7190
                $before = count($questionList);
7191
7192
                if ($mediaQuestionId != 999) {
7193
                    $wasMedia = true;
7194
                } else {
7195
                    $wasMedia = false;
7196
                }
7197
            }
7198
        }
7199
        return $html;
7200
    }
7201
7202
    /**
7203
     * Renders a question list
7204
     *
7205
     * @param array $questionList (with media questions compressed)
7206
     * @param int $currentQuestion
7207
     * @param array $exerciseResult
7208
     * @param array $attemptList
7209
     * @param array $remindList
7210
     */
7211
    public function renderQuestionList(
7212
        $questionList,
7213
        $currentQuestion,
7214
        $exerciseResult,
7215
        $attemptList,
7216
        $remindList
7217
    ) {
7218
        $mediaQuestions = $this->getMediaList();
7219
        $i = 0;
7220
7221
        // Normal question list render (medias compressed)
7222
        foreach ($questionList as $questionId) {
7223
            $i++;
7224
            // For sequential exercises
7225
7226
            if ($this->type == ONE_PER_PAGE) {
7227
                // If it is not the right question, goes to the next loop iteration
7228
                if ($currentQuestion != $i) {
7229
                    continue;
7230
                } else {
7231
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
7232
                        // if the user has already answered this question
7233
                        if (isset($exerciseResult[$questionId])) {
7234
                            echo Display::return_message(
7235
                                get_lang('AlreadyAnswered'),
7236
                                'normal'
7237
                            );
7238
                            break;
7239
                        }
7240
                    }
7241
                }
7242
            }
7243
7244
            // The $questionList contains the media id we check
7245
            // if this questionId is a media question type
7246
            if (isset($mediaQuestions[$questionId]) &&
7247
                $mediaQuestions[$questionId] != 999
7248
            ) {
7249
                // The question belongs to a media
7250
                $mediaQuestionList = $mediaQuestions[$questionId];
7251
                $objQuestionTmp = Question::read($questionId);
7252
7253
                $counter = 1;
7254
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
7255
                    echo $objQuestionTmp->show_media_content();
7256
7257
                    $countQuestionsInsideMedia = count($mediaQuestionList);
7258
7259
                    // Show questions that belongs to a media
7260
                    if (!empty($mediaQuestionList)) {
7261
                        // In order to parse media questions we use letters a, b, c, etc.
7262
                        $letterCounter = 97;
7263
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
7264
                            $isLastQuestionInMedia = false;
7265
                            if ($counter == $countQuestionsInsideMedia) {
7266
                                $isLastQuestionInMedia = true;
7267
                            }
7268
                            $this->renderQuestion(
7269
                                $questionIdInsideMedia,
7270
                                $attemptList,
7271
                                $remindList,
7272
                                chr($letterCounter),
7273
                                $currentQuestion,
7274
                                $mediaQuestionList,
7275
                                $isLastQuestionInMedia,
7276
                                $questionList
7277
                            );
7278
                            $letterCounter++;
7279
                            $counter++;
7280
                        }
7281
                    }
7282
                } else {
7283
                    $this->renderQuestion(
7284
                        $questionId,
7285
                        $attemptList,
7286
                        $remindList,
7287
                        $i,
7288
                        $currentQuestion,
7289
                        null,
7290
                        null,
7291
                        $questionList
7292
                    );
7293
                    $i++;
7294
                }
7295
            } else {
7296
                // Normal question render.
7297
                $this->renderQuestion(
7298
                    $questionId,
7299
                    $attemptList,
7300
                    $remindList,
7301
                    $i,
7302
                    $currentQuestion,
7303
                    null,
7304
                    null,
7305
                    $questionList
7306
                );
7307
            }
7308
7309
            // For sequential exercises.
7310
            if ($this->type == ONE_PER_PAGE) {
7311
                // quits the loop
7312
                break;
7313
            }
7314
        }
7315
        // end foreach()
7316
7317
        if ($this->type == ALL_ON_ONE_PAGE) {
7318
            $exercise_actions = $this->show_button($questionId, $currentQuestion);
7319
            echo Display::div($exercise_actions, ['class' => 'exercise_actions']);
7320
        }
7321
    }
7322
7323
    /**
7324
     * @param int $questionId
7325
     * @param array $attemptList
7326
     * @param array $remindList
7327
     * @param int $i
7328
     * @param int $current_question
7329
     * @param array $questions_in_media
7330
     * @param bool $last_question_in_media
7331
     * @param array $realQuestionList
7332
     * @param bool $generateJS
7333
     */
7334
    public function renderQuestion(
7335
        $questionId,
7336
        $attemptList,
7337
        $remindList,
7338
        $i,
7339
        $current_question,
7340
        $questions_in_media = [],
7341
        $last_question_in_media = false,
7342
        $realQuestionList,
7343
        $generateJS = true
7344
    ) {
7345
        // With this option on the question is loaded via AJAX
7346
        //$generateJS = true;
7347
        //$this->loadQuestionAJAX = true;
7348
7349
        if ($generateJS && $this->loadQuestionAJAX) {
7350
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
7351
            $params = [
7352
                'questionId' => $questionId,
7353
                'attemptList'=> $attemptList,
7354
                'remindList' => $remindList,
7355
                'i' => $i,
7356
                'current_question' => $current_question,
7357
                'questions_in_media' => $questions_in_media,
7358
                'last_question_in_media' => $last_question_in_media
7359
            ];
7360
            $params = json_encode($params);
7361
7362
            $script = '<script>
7363
            $(function(){
7364
                var params = '.$params.';
7365
                $.ajax({
7366
                    type: "GET",
7367
                    async: false,
7368
                    data: params,
7369
                    url: "'.$url.'",
7370
                    success: function(return_value) {
7371
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
7372
                    }
7373
                });
7374
            });
7375
            </script>
7376
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
7377
            echo $script;
7378
        } else {
7379
            global $origin;
7380
            $question_obj = Question::read($questionId);
7381
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
7382
            $remind_highlight = null;
7383
7384
            // Hides questions when reviewing a ALL_ON_ONE_PAGE exercise
7385
            // see #4542 no_remind_highlight class hide with jquery
7386
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
7387
                $remind_highlight = 'no_remind_highlight';
7388
                if (in_array($question_obj->type, Question::question_type_no_review())) {
7389
                    return null;
7390
                }
7391
            }
7392
7393
            $attributes = ['id' =>'remind_list['.$questionId.']'];
7394
            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...
7395
                //$attributes['checked'] = 1;
7396
                //$remind_highlight = ' remind_highlight ';
7397
            }
7398
7399
            // Showing the question
7400
7401
            $exercise_actions = null;
7402
7403
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
7404
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
7405
7406
            // Shows the question + possible answers
7407
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
7408
            echo $this->showQuestion(
7409
                $question_obj,
7410
                false,
7411
                $origin,
7412
                $i,
7413
                $showTitle,
7414
                false,
7415
                $user_choice,
7416
                false,
7417
                null,
7418
                false,
7419
                $this->getModelType(),
7420
                $this->categoryMinusOne
7421
            );
7422
7423
            // Button save and continue
7424
            switch ($this->type) {
7425
                case ONE_PER_PAGE:
7426
                    $exercise_actions .= $this->show_button(
7427
                        $questionId,
7428
                        $current_question,
7429
                        null,
7430
                        $remindList
7431
                    );
7432
                    break;
7433
                case ALL_ON_ONE_PAGE:
7434
                    $button = [
7435
                        Display::button(
7436
                            'save_now',
7437
                            get_lang('SaveForNow'),
7438
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7439
                        ),
7440
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>'
7441
                    ];
7442
                    $exercise_actions .= Display::div(
7443
                        implode(PHP_EOL, $button),
7444
                        ['class'=>'exercise_save_now_button']
7445
                    );
7446
                    break;
7447
            }
7448
7449
            if (!empty($questions_in_media)) {
7450
                $count_of_questions_inside_media = count($questions_in_media);
7451
                if ($count_of_questions_inside_media > 1) {
7452
                    $button = [
7453
                        Display::button(
7454
                            'save_now',
7455
                            get_lang('SaveForNow'),
7456
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7457
                        ),
7458
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;'
7459
                    ];
7460
                    $exercise_actions = Display::div(
7461
                        implode(PHP_EOL, $button),
7462
                        ['class'=>'exercise_save_now_button']
7463
                    );
7464
                }
7465
7466
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
7467
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
7468
                }
7469
            }
7470
7471
            // Checkbox review answers
7472
            if ($this->review_answers &&
7473
                !in_array($question_obj->type, Question::question_type_no_review())
7474
            ) {
7475
                $remind_question_div = Display::tag(
7476
                    'label',
7477
                    Display::input(
7478
                        'checkbox',
7479
                        'remind_list['.$questionId.']',
7480
                        '',
7481
                        $attributes
7482
                    ).get_lang('ReviewQuestionLater'),
7483
                    [
7484
                        'class' => 'checkbox',
7485
                        'for' => 'remind_list['.$questionId.']'
7486
                    ]
7487
                );
7488
                $exercise_actions .= Display::div(
7489
                    $remind_question_div,
7490
                    ['class' => 'exercise_save_now_button']
7491
                );
7492
            }
7493
7494
            echo Display::div(' ', ['class'=>'clear']);
7495
7496
            $paginationCounter = null;
7497
            if ($this->type == ONE_PER_PAGE) {
7498
                if (empty($questions_in_media)) {
7499
                    $paginationCounter = Display::paginationIndicator(
7500
                        $current_question,
7501
                        count($realQuestionList)
7502
                    );
7503
                } else {
7504
                    if ($last_question_in_media) {
7505
                        $paginationCounter = Display::paginationIndicator(
7506
                            $current_question,
7507
                            count($realQuestionList)
7508
                        );
7509
                    }
7510
                }
7511
            }
7512
7513
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
7514
            echo Display::div($exercise_actions, ['class'=>'form-actions']);
7515
            echo '</div>';
7516
        }
7517
    }
7518
7519
    /**
7520
     * Returns an array of categories details for the questions of the current
7521
     * exercise.
7522
     * @return array
7523
     */
7524
    public function getQuestionWithCategories()
7525
    {
7526
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
7527
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
7528
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7529
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7530
        $sql = "SELECT DISTINCT cat.*
7531
                FROM $TBL_EXERCICE_QUESTION e
7532
                INNER JOIN $TBL_QUESTIONS q
7533
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7534
                INNER JOIN $categoryRelTable catRel
7535
                ON (catRel.question_id = e.question_id)
7536
                INNER JOIN $categoryTable cat
7537
                ON (cat.id = catRel.category_id)
7538
                WHERE
7539
                  e.c_id = {$this->course_id} AND
7540
                  e.exercice_id	= ".intval($this->id);
7541
7542
        $result = Database::query($sql);
7543
        $categoriesInExercise = [];
7544
        if (Database::num_rows($result)) {
7545
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
7546
        }
7547
7548
        return $categoriesInExercise;
7549
    }
7550
7551
    /**
7552
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option
7553
     */
7554
    public function get_max_score()
7555
    {
7556
        $out_max_score = 0;
7557
        // list of question's id !!! the array key start at 1 !!!
7558
        $questionList = $this->selectQuestionList(true);
7559
7560
        // test is randomQuestions - see field random of test
7561
        if ($this->random > 0 && $this->randomByCat == 0) {
7562
            $numberRandomQuestions = $this->random;
7563
            $questionScoreList = [];
7564
            foreach ($questionList as $questionId) {
7565
                $tmpobj_question = Question::read($questionId);
7566
                if (is_object($tmpobj_question)) {
7567
                    $questionScoreList[] = $tmpobj_question->weighting;
7568
                }
7569
            }
7570
7571
            rsort($questionScoreList);
7572
            // add the first $numberRandomQuestions value of score array to get max_score
7573
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
7574
                $out_max_score += $questionScoreList[$i];
7575
            }
7576
        } elseif ($this->random > 0 && $this->randomByCat > 0) {
7577
            // test is random by category
7578
            // get the $numberRandomQuestions best score question of each category
7579
            $numberRandomQuestions = $this->random;
7580
            $tab_categories_scores = [];
7581
            foreach ($questionList as $questionId) {
7582
                $question_category_id = TestCategory::getCategoryForQuestion($questionId);
7583
                if (!is_array($tab_categories_scores[$question_category_id])) {
7584
                    $tab_categories_scores[$question_category_id] = [];
7585
                }
7586
                $tmpobj_question = Question::read($questionId);
7587
                if (is_object($tmpobj_question)) {
7588
                    $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
7589
                }
7590
            }
7591
7592
            // here we've got an array with first key, the category_id, second key, score of question for this cat
7593
            while (list($key, $tab_scores) = each($tab_categories_scores)) {
7594
                rsort($tab_scores);
7595
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
7596
                    $out_max_score += $tab_scores[$i];
7597
                }
7598
            }
7599
        } else {
7600
            // standard test, just add each question score
7601
            foreach ($questionList as $questionId) {
7602
                $question = Question::read($questionId, $this->course_id);
7603
                $out_max_score += $question->weighting;
7604
            }
7605
        }
7606
7607
        return $out_max_score;
7608
    }
7609
7610
    /**
7611
    * @return string
7612
    */
7613
    public function get_formated_title()
7614
    {
7615
        if (api_get_configuration_value('save_titles_as_html')) {
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...
7616
        }
7617
        return api_html_entity_decode($this->selectTitle());
7618
    }
7619
7620
    /**
7621
     * @param string $title
7622
     * @return string
7623
     */
7624
    public static function get_formated_title_variable($title)
7625
    {
7626
        return api_html_entity_decode($title);
7627
    }
7628
7629
    /**
7630
     * @return string
7631
     */
7632
    public function format_title()
7633
    {
7634
        return api_htmlentities($this->title);
7635
    }
7636
7637
    /**
7638
     * @param string $title
7639
     * @return string
7640
     */
7641
    public static function format_title_variable($title)
7642
    {
7643
        return api_htmlentities($title);
7644
    }
7645
7646
    /**
7647
     * @param int $courseId
7648
     * @param int $sessionId
7649
     * @return array exercises
7650
     */
7651
    public function getExercisesByCourseSession($courseId, $sessionId)
7652
    {
7653
        $courseId = intval($courseId);
7654
        $sessionId = intval($sessionId);
7655
7656
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7657
        $sql = "SELECT * FROM $tbl_quiz cq
7658
                WHERE
7659
                    cq.c_id = %s AND
7660
                    (cq.session_id = %s OR cq.session_id = 0) AND
7661
                    cq.active = 0
7662
                ORDER BY cq.id";
7663
        $sql = sprintf($sql, $courseId, $sessionId);
7664
7665
        $result = Database::query($sql);
7666
7667
        $rows = [];
7668
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7669
            $rows[] = $row;
7670
        }
7671
7672
        return $rows;
7673
    }
7674
7675
    /**
7676
     *
7677
     * @param int $courseId
7678
     * @param int $sessionId
7679
     * @param array $quizId
7680
     * @return array exercises
7681
     */
7682
    public function getExerciseAndResult($courseId, $sessionId, $quizId = [])
7683
    {
7684
        if (empty($quizId)) {
7685
            return [];
7686
        }
7687
7688
        $sessionId = intval($sessionId);
7689
7690
        $ids = is_array($quizId) ? $quizId : [$quizId];
7691
        $ids = array_map('intval', $ids);
7692
        $ids = implode(',', $ids);
7693
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7694
        if ($sessionId != 0) {
7695
            $sql = "SELECT * FROM $track_exercises te
7696
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7697
              WHERE
7698
              te.id = %s AND
7699
              te.session_id = %s AND
7700
              cq.id IN (%s)
7701
              ORDER BY cq.id";
7702
7703
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
7704
        } else {
7705
            $sql = "SELECT * FROM $track_exercises te
7706
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7707
              WHERE
7708
              te.id = %s AND
7709
              cq.id IN (%s)
7710
              ORDER BY cq.id";
7711
            $sql = sprintf($sql, $courseId, $ids);
7712
        }
7713
        $result = Database::query($sql);
7714
        $rows = [];
7715
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7716
            $rows[] = $row;
7717
        }
7718
7719
        return $rows;
7720
    }
7721
7722
    /**
7723
     * @param $exeId
7724
     * @param $exercise_stat_info
7725
     * @param $remindList
7726
     * @param $currentQuestion
7727
     * @return int|null
7728
     */
7729
    public static function getNextQuestionId(
7730
        $exeId,
7731
        $exercise_stat_info,
7732
        $remindList,
7733
        $currentQuestion
7734
    ) {
7735
        $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete');
7736
7737
        if (isset($result[$exeId])) {
7738
            $result = $result[$exeId];
7739
        } else {
7740
            return null;
7741
        }
7742
7743
        $data_tracking = $exercise_stat_info['data_tracking'];
7744
        $data_tracking = explode(',', $data_tracking);
7745
7746
        // if this is the final question do nothing.
7747
        if ($currentQuestion == count($data_tracking)) {
7748
            return null;
7749
        }
7750
7751
        $currentQuestion = $currentQuestion - 1;
7752
7753
        if (!empty($result['question_list'])) {
7754
            $answeredQuestions = [];
7755
            foreach ($result['question_list'] as $question) {
7756
                if (!empty($question['answer'])) {
7757
                    $answeredQuestions[] = $question['question_id'];
7758
                }
7759
            }
7760
7761
            // Checking answered questions
7762
            $counterAnsweredQuestions = 0;
7763
            foreach ($data_tracking as $questionId) {
7764
                if (!in_array($questionId, $answeredQuestions)) {
7765
                    if ($currentQuestion != $counterAnsweredQuestions) {
7766
                        break;
7767
                    }
7768
                }
7769
                $counterAnsweredQuestions++;
7770
            }
7771
7772
            $counterRemindListQuestions = 0;
7773
            // Checking questions saved in the reminder list
7774
            if (!empty($remindList)) {
7775
                foreach ($data_tracking as $questionId) {
7776
                    if (in_array($questionId, $remindList)) {
7777
                        // Skip the current question
7778
                        if ($currentQuestion != $counterRemindListQuestions) {
7779
                            break;
7780
                        }
7781
                    }
7782
                    $counterRemindListQuestions++;
7783
                }
7784
7785
                if ($counterRemindListQuestions < $currentQuestion) {
7786
                    return null;
7787
                }
7788
7789
                if (!empty($counterRemindListQuestions)) {
7790
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
7791
                        return $counterAnsweredQuestions;
7792
                    } else {
7793
                        return $counterRemindListQuestions;
7794
                    }
7795
                }
7796
            }
7797
7798
            return $counterAnsweredQuestions;
7799
        }
7800
    }
7801
7802
    /**
7803
     * Gets the position of a questionId in the question list
7804
     * @param $questionId
7805
     * @return int
7806
     */
7807
    public function getPositionInCompressedQuestionList($questionId)
7808
    {
7809
        $questionList = $this->getQuestionListWithMediasCompressed();
7810
        $mediaQuestions = $this->getMediaList();
7811
        $position = 1;
7812
        foreach ($questionList as $id) {
7813
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
7814
                $mediaQuestionList = $mediaQuestions[$id];
7815
                if (in_array($questionId, $mediaQuestionList)) {
7816
                    return $position;
7817
                } else {
7818
                    $position++;
7819
                }
7820
            } else {
7821
                if ($id == $questionId) {
7822
                    return $position;
7823
                } else {
7824
                    $position++;
7825
                }
7826
            }
7827
        }
7828
        return 1;
7829
    }
7830
7831
    /**
7832
     * Get the correct answers in all attempts
7833
     * @param int $learnPathId
7834
     * @param int $learnPathItemId
7835
     * @return array
7836
     */
7837
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
7838
    {
7839
        $attempts = Event::getExerciseResultsByUser(
7840
            api_get_user_id(),
7841
            $this->id,
7842
            api_get_course_int_id(),
7843
            api_get_session_id(),
7844
            $learnPathId,
7845
            $learnPathItemId,
7846
            'asc'
7847
        );
7848
7849
        $corrects = [];
7850
7851
        foreach ($attempts as $attempt) {
7852
            foreach ($attempt['question_list'] as $answers) {
7853
                foreach ($answers as $answer) {
7854
                    $objAnswer = new Answer($answer['question_id']);
7855
7856
                    switch ($objAnswer->getQuestionType()) {
7857
                        case FILL_IN_BLANKS:
7858
                            $isCorrect = FillBlanks::isCorrect($answer['answer']);
7859
                            break;
7860
                        case MATCHING:
7861
                        case DRAGGABLE:
7862
                        case MATCHING_DRAGGABLE:
7863
                            $isCorrect = Matching::isCorrect(
7864
                                $answer['position'],
7865
                                $answer['answer'],
7866
                                $answer['question_id']
7867
                            );
7868
                            break;
7869
                        default:
7870
                            $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
7871
                    }
7872
7873
                    if ($isCorrect) {
7874
                        $corrects[$answer['question_id']][] = $answer;
7875
                    }
7876
                }
7877
            }
7878
        }
7879
7880
        return $corrects;
7881
    }
7882
7883
    /**
7884
     * Get the title without HTML tags
7885
     * @return string
7886
     */
7887
    private function getUnformattedTitle()
7888
    {
7889
        return strip_tags(api_html_entity_decode($this->title));
7890
    }
7891
7892
    /**
7893
     * @return bool
7894
     */
7895
    public function showPreviousButton()
7896
    {
7897
        $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
7898
        if ($allow === false) {
7899
            return true;
7900
        }
7901
7902
        return $this->showPreviousButton;
7903
    }
7904
7905
    /**
7906
     * @param bool $showPreviousButton
7907
     * @return Exercise
7908
     */
7909
    public function setShowPreviousButton($showPreviousButton)
7910
    {
7911
        $this->showPreviousButton = $showPreviousButton;
7912
7913
        return $this;
7914
    }
7915
7916
    /**
7917
     * @param array $notifications
7918
     */
7919
    public function setNotifications($notifications)
7920
    {
7921
        $this->notifications = $notifications;
7922
    }
7923
7924
    /**
7925
     * @return array
7926
     */
7927
    public function getNotifications()
7928
    {
7929
        return $this->notifications;
7930
    }
7931
}
7932