Completed
Push — master ( 4dac83...c22b6a )
by Julito
09:28 queued 12s
created

Exercise::getNumberQuestionExerciseCategory()   A

Complexity

Conditions 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nop 0
dl 0
loc 16
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\GradebookLink;
6
use Chamilo\CoreBundle\Entity\TrackEExerciseConfirmation;
7
use Chamilo\CoreBundle\Entity\TrackEHotspot;
8
use Chamilo\CoreBundle\Framework\Container;
9
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
10
use Chamilo\CourseBundle\Entity\CExerciseCategory;
11
use Chamilo\CourseBundle\Entity\CQuiz;
12
use Chamilo\CourseBundle\Entity\CQuizCategory;
13
use ChamiloSession as Session;
14
use Doctrine\DBAL\Types\Type;
15
16
/**
17
 * Class Exercise.
18
 *
19
 * Allows to instantiate an object of type Exercise
20
 *
21
 * @todo use getters and setters correctly
22
 *
23
 * @author Olivier Brouckaert
24
 * @author Julio Montoya Cleaning exercises
25
 * Modified by Hubert Borderiou #294
26
 */
27
class Exercise
28
{
29
    public const PAGINATION_ITEMS_PER_PAGE = 20;
30
    public $iId;
31
    public $id;
32
    public $name;
33
    public $title;
34
    public $exercise;
35
    public $description;
36
    public $sound;
37
    public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
38
    public $random;
39
    public $random_answers;
40
    public $active;
41
    public $timeLimit;
42
    public $attempts;
43
    public $feedback_type;
44
    public $end_time;
45
    public $start_time;
46
    public $questionList; // array with the list of this exercise's questions
47
    /* including question list of the media */
48
    public $questionListUncompressed;
49
    public $results_disabled;
50
    public $expired_time;
51
    public $course;
52
    public $course_id;
53
    public $propagate_neg;
54
    public $saveCorrectAnswers;
55
    public $review_answers;
56
    public $randomByCat;
57
    public $text_when_finished;
58
    public $display_category_name;
59
    public $pass_percentage;
60
    public $edit_exercise_in_lp = false;
61
    public $is_gradebook_locked = false;
62
    public $exercise_was_added_in_lp = false;
63
    public $lpList = [];
64
    public $force_edit_exercise_in_lp = false;
65
    public $categories;
66
    public $categories_grouping = true;
67
    public $endButton = 0;
68
    public $categoryWithQuestionList;
69
    public $mediaList;
70
    public $loadQuestionAJAX = false;
71
    // Notification send to the teacher.
72
    public $emailNotificationTemplate = null;
73
    // Notification send to the student.
74
    public $emailNotificationTemplateToUser = null;
75
    public $countQuestions = 0;
76
    public $fastEdition = false;
77
    public $modelType = 1;
78
    public $questionSelectionType = EX_Q_SELECTION_ORDERED;
79
    public $hideQuestionTitle = 0;
80
    public $scoreTypeModel = 0;
81
    public $categoryMinusOne = true; // Shows the category -1: See BT#6540
82
    public $globalCategoryId = null;
83
    public $onSuccessMessage = null;
84
    public $onFailedMessage = null;
85
    public $emailAlert;
86
    public $notifyUserByEmail = '';
87
    public $sessionId = 0;
88
    public $questionFeedbackEnabled = false;
89
    public $questionTypeWithFeedback;
90
    public $showPreviousButton;
91
    public $notifications;
92
    public $export = false;
93
    public $autolaunch;
94
    public $exerciseCategoryId;
95
    public $pageResultConfiguration;
96
    public $preventBackwards;
97
98
    /**
99
     * Constructor of the class.
100
     *
101
     * @param int $courseId
102
     *
103
     * @author Olivier Brouckaert
104
     */
105
    public function __construct($courseId = 0)
106
    {
107
        $this->iId = 0;
108
        $this->id = 0;
109
        $this->exercise = '';
110
        $this->description = '';
111
        $this->sound = '';
112
        $this->type = ALL_ON_ONE_PAGE;
113
        $this->random = 0;
114
        $this->random_answers = 0;
115
        $this->active = 1;
116
        $this->questionList = [];
117
        $this->timeLimit = 0;
118
        $this->end_time = '';
119
        $this->start_time = '';
120
        $this->results_disabled = 1;
121
        $this->expired_time = 0;
122
        $this->propagate_neg = 0;
123
        $this->saveCorrectAnswers = 0;
124
        $this->review_answers = false;
125
        $this->randomByCat = 0;
126
        $this->text_when_finished = '';
127
        $this->display_category_name = 0;
128
        $this->pass_percentage = 0;
129
        $this->modelType = 1;
130
        $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
131
        $this->endButton = 0;
132
        $this->scoreTypeModel = 0;
133
        $this->globalCategoryId = null;
134
        $this->notifications = [];
135
        $this->exerciseCategoryId = 0;
136
        $this->pageResultConfiguration;
137
        $this->preventBackwards = 0;
138
139
        if (!empty($courseId)) {
140
            $courseInfo = api_get_course_info_by_id($courseId);
141
        } else {
142
            $courseInfo = api_get_course_info();
143
        }
144
        $this->course_id = $courseInfo['real_id'];
145
        $this->course = $courseInfo;
146
        $this->sessionId = api_get_session_id();
147
148
        // ALTER TABLE c_quiz_question ADD COLUMN feedback text;
149
        $this->questionFeedbackEnabled = api_get_configuration_value('allow_quiz_question_feedback');
150
        $this->showPreviousButton = true;
151
    }
152
153
    /**
154
     * Reads exercise information from the data base.
155
     *
156
     * @author Olivier Brouckaert
157
     *
158
     * @param int  $id                - exercise Id
159
     * @param bool $parseQuestionList
160
     *
161
     * @return bool - true if exercise exists, otherwise false
162
     */
163
    public function read($id, $parseQuestionList = true)
164
    {
165
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
166
        $tableLpItem = Database::get_course_table(TABLE_LP_ITEM);
167
168
        $id = (int) $id;
169
        if (empty($this->course_id)) {
170
            return false;
171
        }
172
173
        $sql = "SELECT * FROM $table
174
                WHERE c_id = ".$this->course_id.' AND id = '.$id;
175
        $result = Database::query($sql);
176
177
        // if the exercise has been found
178
        if ($object = Database::fetch_object($result)) {
179
            $this->iId = $object->iid;
180
            $this->id = $id;
181
            $this->exercise = $object->title;
182
            $this->name = $object->title;
183
            $this->title = $object->title;
184
            $this->description = $object->description;
185
            $this->sound = $object->sound;
186
            $this->type = $object->type;
187
            if (empty($this->type)) {
188
                $this->type = ONE_PER_PAGE;
189
            }
190
            $this->random = $object->random;
191
            $this->random_answers = $object->random_answers;
192
            $this->active = $object->active;
193
            $this->results_disabled = $object->results_disabled;
194
            $this->attempts = $object->max_attempt;
195
            $this->feedback_type = $object->feedback_type;
196
            $this->sessionId = $object->session_id;
197
            $this->propagate_neg = $object->propagate_neg;
198
            $this->saveCorrectAnswers = $object->save_correct_answers;
199
            $this->randomByCat = $object->random_by_category;
200
            $this->text_when_finished = $object->text_when_finished;
201
            $this->display_category_name = $object->display_category_name;
202
            $this->pass_percentage = $object->pass_percentage;
203
            $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
204
            $this->review_answers = isset($object->review_answers) && 1 == $object->review_answers ? true : false;
205
            $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
206
            $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null;
207
            $this->hideQuestionTitle = isset($object->hide_question_title) ? (int) $object->hide_question_title : 0;
208
            $this->autolaunch = isset($object->autolaunch) ? (int) $object->autolaunch : 0;
209
            $this->exerciseCategoryId = isset($object->exercise_category_id) ? (int) $object->exercise_category_id : null;
210
            $this->preventBackwards = isset($object->prevent_backwards) ? (int) $object->prevent_backwards : 0;
211
            $this->exercise_was_added_in_lp = false;
212
            $this->lpList = [];
213
            $this->notifications = [];
214
            if (!empty($object->notifications)) {
215
                $this->notifications = explode(',', $object->notifications);
216
            }
217
218
            if (!empty($object->page_result_configuration)) {
219
                $this->pageResultConfiguration = $object->page_result_configuration;
220
            }
221
222
            if (isset($object->show_previous_button)) {
223
                $this->showPreviousButton = 1 == $object->show_previous_button ? true : false;
224
            }
225
226
            $list = self::getLpListFromExercise($id, $this->course_id);
227
            if (!empty($list)) {
228
                $this->exercise_was_added_in_lp = true;
229
                $this->lpList = $list;
230
            }
231
232
            $this->force_edit_exercise_in_lp = api_get_configuration_value('force_edit_exercise_in_lp');
233
            $this->edit_exercise_in_lp = true;
234
            if ($this->exercise_was_added_in_lp) {
235
                $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
236
            }
237
238
            if (!empty($object->end_time)) {
239
                $this->end_time = $object->end_time;
240
            }
241
            if (!empty($object->start_time)) {
242
                $this->start_time = $object->start_time;
243
            }
244
245
            // Control time
246
            $this->expired_time = $object->expired_time;
247
248
            // Checking if question_order is correctly set
249
            if ($parseQuestionList) {
250
                $this->setQuestionList(true);
251
            }
252
253
            //overload questions list with recorded questions list
254
            //load questions only for exercises of type 'one question per page'
255
            //this is needed only is there is no questions
256
257
            // @todo not sure were in the code this is used somebody mess with the exercise tool
258
            // @todo don't know who add that config and why $_configuration['live_exercise_tracking']
259
            /*global $_configuration, $questionList;
260
            if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST'
261
                && defined('QUESTION_LIST_ALREADY_LOGGED') &&
262
                isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']
263
            ) {
264
                $this->questionList = $questionList;
265
            }*/
266
            return true;
267
        }
268
269
        return false;
270
    }
271
272
    /**
273
     * @return string
274
     */
275
    public function getCutTitle()
276
    {
277
        $title = $this->getUnformattedTitle();
278
279
        return cut($title, EXERCISE_MAX_NAME_SIZE);
280
    }
281
282
    /**
283
     * returns the exercise ID.
284
     *
285
     * @author Olivier Brouckaert
286
     *
287
     * @return int - exercise ID
288
     */
289
    public function selectId()
290
    {
291
        return $this->id;
292
    }
293
294
    /**
295
     * returns the exercise title.
296
     *
297
     * @author Olivier Brouckaert
298
     *
299
     * @param bool $unformattedText Optional. Get the title without HTML tags
300
     *
301
     * @return string - exercise title
302
     */
303
    public function selectTitle($unformattedText = false)
304
    {
305
        if ($unformattedText) {
306
            return $this->getUnformattedTitle();
307
        }
308
309
        return $this->exercise;
310
    }
311
312
    /**
313
     * returns the number of attempts setted.
314
     *
315
     * @return int - exercise attempts
316
     */
317
    public function selectAttempts()
318
    {
319
        return $this->attempts;
320
    }
321
322
    /**
323
     * Returns the number of FeedbackType
324
     *  0: Feedback , 1: DirectFeedback, 2: NoFeedback.
325
     *
326
     * @return int - exercise attempts
327
     */
328
    public function getFeedbackType()
329
    {
330
        return (int) $this->feedback_type;
331
    }
332
333
    /**
334
     * returns the time limit.
335
     *
336
     * @return int
337
     */
338
    public function selectTimeLimit()
339
    {
340
        return $this->timeLimit;
341
    }
342
343
    /**
344
     * returns the exercise description.
345
     *
346
     * @author Olivier Brouckaert
347
     *
348
     * @return string - exercise description
349
     */
350
    public function selectDescription()
351
    {
352
        return $this->description;
353
    }
354
355
    /**
356
     * returns the exercise sound file.
357
     *
358
     * @author Olivier Brouckaert
359
     *
360
     * @return string - exercise description
361
     */
362
    public function selectSound()
363
    {
364
        return $this->sound;
365
    }
366
367
    /**
368
     * returns the exercise type.
369
     *
370
     * @author Olivier Brouckaert
371
     *
372
     * @return int - exercise type
373
     */
374
    public function selectType()
375
    {
376
        return $this->type;
377
    }
378
379
    /**
380
     * @return int
381
     */
382
    public function getModelType()
383
    {
384
        return $this->modelType;
385
    }
386
387
    /**
388
     * @return int
389
     */
390
    public function selectEndButton()
391
    {
392
        return $this->endButton;
393
    }
394
395
    /**
396
     * @author hubert borderiou 30-11-11
397
     *
398
     * @return int : do we display the question category name for students
399
     */
400
    public function selectDisplayCategoryName()
401
    {
402
        return $this->display_category_name;
403
    }
404
405
    /**
406
     * @return int
407
     */
408
    public function selectPassPercentage()
409
    {
410
        return $this->pass_percentage;
411
    }
412
413
    /**
414
     * Modify object to update the switch display_category_name.
415
     *
416
     * @author hubert borderiou 30-11-11
417
     *
418
     * @param int $value is an integer 0 or 1
419
     */
420
    public function updateDisplayCategoryName($value)
421
    {
422
        $this->display_category_name = $value;
423
    }
424
425
    /**
426
     * @author hubert borderiou 28-11-11
427
     *
428
     * @return string html text : the text to display ay the end of the test
429
     */
430
    public function getTextWhenFinished()
431
    {
432
        return $this->text_when_finished;
433
    }
434
435
    /**
436
     * @param string $text
437
     *
438
     * @author hubert borderiou 28-11-11
439
     */
440
    public function updateTextWhenFinished($text)
441
    {
442
        $this->text_when_finished = $text;
443
    }
444
445
    /**
446
     * return 1 or 2 if randomByCat.
447
     *
448
     * @author hubert borderiou
449
     *
450
     * @return int - quiz random by category
451
     */
452
    public function getRandomByCategory()
453
    {
454
        return $this->randomByCat;
455
    }
456
457
    /**
458
     * return 0 if no random by cat
459
     * return 1 if random by cat, categories shuffled
460
     * return 2 if random by cat, categories sorted by alphabetic order.
461
     *
462
     * @author hubert borderiou
463
     *
464
     * @return int - quiz random by category
465
     */
466
    public function isRandomByCat()
467
    {
468
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
469
        if (EXERCISE_CATEGORY_RANDOM_SHUFFLED == $this->randomByCat) {
470
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
471
        } elseif (EXERCISE_CATEGORY_RANDOM_ORDERED == $this->randomByCat) {
472
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
473
        }
474
475
        return $res;
476
    }
477
478
    /**
479
     * return nothing
480
     * update randomByCat value for object.
481
     *
482
     * @param int $random
483
     *
484
     * @author hubert borderiou
485
     */
486
    public function updateRandomByCat($random)
487
    {
488
        $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
489
        if (in_array(
490
            $random,
491
            [
492
                EXERCISE_CATEGORY_RANDOM_SHUFFLED,
493
                EXERCISE_CATEGORY_RANDOM_ORDERED,
494
                EXERCISE_CATEGORY_RANDOM_DISABLED,
495
            ]
496
        )) {
497
            $this->randomByCat = $random;
498
        }
499
    }
500
501
    /**
502
     * Tells if questions are selected randomly, and if so returns the draws.
503
     *
504
     * @author Carlos Vargas
505
     *
506
     * @return int - results disabled exercise
507
     */
508
    public function selectResultsDisabled()
509
    {
510
        return $this->results_disabled;
511
    }
512
513
    /**
514
     * tells if questions are selected randomly, and if so returns the draws.
515
     *
516
     * @author Olivier Brouckaert
517
     *
518
     * @return bool
519
     */
520
    public function isRandom()
521
    {
522
        $isRandom = false;
523
        // "-1" means all questions will be random
524
        if ($this->random > 0 || -1 == $this->random) {
525
            $isRandom = true;
526
        }
527
528
        return $isRandom;
529
    }
530
531
    /**
532
     * returns random answers status.
533
     *
534
     * @author Juan Carlos Rana
535
     */
536
    public function getRandomAnswers()
537
    {
538
        return $this->random_answers;
539
    }
540
541
    /**
542
     * Same as isRandom() but has a name applied to values different than 0 or 1.
543
     *
544
     * @return int
545
     */
546
    public function getShuffle()
547
    {
548
        return $this->random;
549
    }
550
551
    /**
552
     * returns the exercise status (1 = enabled ; 0 = disabled).
553
     *
554
     * @author Olivier Brouckaert
555
     *
556
     * @return int - 1 if enabled, otherwise 0
557
     */
558
    public function selectStatus()
559
    {
560
        return $this->active;
561
    }
562
563
    /**
564
     * If false the question list will be managed as always if true
565
     * the question will be filtered
566
     * depending of the exercise settings (table c_quiz_rel_category).
567
     *
568
     * @param bool $status active or inactive grouping
569
     */
570
    public function setCategoriesGrouping($status)
571
    {
572
        $this->categories_grouping = (bool) $status;
573
    }
574
575
    /**
576
     * @return int
577
     */
578
    public function getHideQuestionTitle()
579
    {
580
        return $this->hideQuestionTitle;
581
    }
582
583
    /**
584
     * @param $value
585
     */
586
    public function setHideQuestionTitle($value)
587
    {
588
        $this->hideQuestionTitle = (int) $value;
589
    }
590
591
    /**
592
     * @return int
593
     */
594
    public function getScoreTypeModel()
595
    {
596
        return $this->scoreTypeModel;
597
    }
598
599
    /**
600
     * @param int $value
601
     */
602
    public function setScoreTypeModel($value)
603
    {
604
        $this->scoreTypeModel = (int) $value;
605
    }
606
607
    /**
608
     * @return int
609
     */
610
    public function getGlobalCategoryId()
611
    {
612
        return $this->globalCategoryId;
613
    }
614
615
    /**
616
     * @param int $value
617
     */
618
    public function setGlobalCategoryId($value)
619
    {
620
        if (is_array($value) && isset($value[0])) {
621
            $value = $value[0];
622
        }
623
        $this->globalCategoryId = (int) $value;
624
    }
625
626
    /**
627
     * @param int    $start
628
     * @param int    $limit
629
     * @param int    $sidx
630
     * @param string $sord
631
     * @param array  $whereCondition
632
     * @param array  $extraFields
633
     *
634
     * @return array
635
     */
636
    public function getQuestionListPagination(
637
        $start,
638
        $limit,
639
        $sidx,
640
        $sord,
641
        $whereCondition = [],
642
        $extraFields = []
643
    ) {
644
        if (!empty($this->id)) {
645
            $category_list = TestCategory::getListOfCategoriesNameForTest(
646
                $this->id,
647
                false
648
            );
649
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
650
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
651
652
            $sql = "SELECT q.iid
653
                    FROM $TBL_EXERCICE_QUESTION e
654
                    INNER JOIN $TBL_QUESTIONS  q
655
                    ON (e.question_id = q.id AND e.c_id = ".$this->course_id." )
656
					WHERE e.exercice_id	= '".$this->id."' ";
657
658
            $orderCondition = ' ORDER BY question_order ';
659
660
            if (!empty($sidx) && !empty($sord)) {
661
                if ('question' === $sidx) {
662
                    if (in_array(strtolower($sord), ['desc', 'asc'])) {
663
                        $orderCondition = " ORDER BY q.$sidx $sord";
664
                    }
665
                }
666
            }
667
668
            $sql .= $orderCondition;
669
            $limitCondition = null;
670
            if (isset($start) && isset($limit)) {
671
                $start = (int) $start;
672
                $limit = (int) $limit;
673
                $limitCondition = " LIMIT $start, $limit";
674
            }
675
            $sql .= $limitCondition;
676
            $result = Database::query($sql);
677
            $questions = [];
678
            if (Database::num_rows($result)) {
679
                if (!empty($extraFields)) {
680
                    $extraFieldValue = new ExtraFieldValue('question');
681
                }
682
                while ($question = Database::fetch_array($result, 'ASSOC')) {
683
                    /** @var Question $objQuestionTmp */
684
                    $objQuestionTmp = Question::read($question['iid']);
685
                    $category_labels = TestCategory::return_category_labels(
686
                        $objQuestionTmp->category_list,
687
                        $category_list
688
                    );
689
690
                    if (empty($category_labels)) {
691
                        $category_labels = '-';
692
                    }
693
694
                    // Question type
695
                    $typeImg = $objQuestionTmp->getTypePicture();
696
                    $typeExpl = $objQuestionTmp->getExplanation();
697
698
                    $question_media = null;
699
                    if (!empty($objQuestionTmp->parent_id)) {
700
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
701
                        $question_media = Question::getMediaLabel($objQuestionMedia->question);
702
                    }
703
704
                    $questionType = Display::tag(
705
                        'div',
706
                        Display::return_icon($typeImg, $typeExpl, [], ICON_SIZE_MEDIUM).$question_media
707
                    );
708
709
                    $question = [
710
                        'id' => $question['iid'],
711
                        'question' => $objQuestionTmp->selectTitle(),
712
                        'type' => $questionType,
713
                        'category' => Display::tag(
714
                            'div',
715
                            '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'
716
                        ),
717
                        'score' => $objQuestionTmp->selectWeighting(),
718
                        'level' => $objQuestionTmp->level,
719
                    ];
720
721
                    if (!empty($extraFields)) {
722
                        foreach ($extraFields as $extraField) {
723
                            $value = $extraFieldValue->get_values_by_handler_and_field_id(
724
                                $question['id'],
725
                                $extraField['id']
726
                            );
727
                            $stringValue = null;
728
                            if ($value) {
729
                                $stringValue = $value['field_value'];
730
                            }
731
                            $question[$extraField['field_variable']] = $stringValue;
732
                        }
733
                    }
734
                    $questions[] = $question;
735
                }
736
            }
737
738
            return $questions;
739
        }
740
    }
741
742
    /**
743
     * Get question count per exercise from DB (any special treatment).
744
     *
745
     * @return int
746
     */
747
    public function getQuestionCount()
748
    {
749
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
750
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
751
        $sql = "SELECT count(q.id) as count
752
                FROM $TBL_EXERCICE_QUESTION e
753
                INNER JOIN $TBL_QUESTIONS q
754
                ON (e.question_id = q.id AND e.c_id = q.c_id)
755
                WHERE
756
                    e.c_id = {$this->course_id} AND
757
                    e.exercice_id = ".$this->id;
758
        $result = Database::query($sql);
759
760
        $count = 0;
761
        if (Database::num_rows($result)) {
762
            $row = Database::fetch_array($result);
763
            $count = (int) $row['count'];
764
        }
765
766
        return $count;
767
    }
768
769
    /**
770
     * @return array
771
     */
772
    public function getQuestionOrderedListByName()
773
    {
774
        if (empty($this->course_id) || empty($this->id)) {
775
            return [];
776
        }
777
778
        $exerciseQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
779
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
780
781
        // Getting question list from the order (question list drag n drop interface ).
782
        $sql = "SELECT e.question_id
783
                FROM $exerciseQuestionTable e
784
                INNER JOIN $questionTable 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 q.question";
790
        $result = Database::query($sql);
791
        $list = [];
792
        if (Database::num_rows($result)) {
793
            $list = Database::store_result($result, 'ASSOC');
794
        }
795
796
        return $list;
797
    }
798
799
    /**
800
     * Selecting question list depending in the exercise-category
801
     * relationship (category table in exercise settings).
802
     *
803
     * @param array $question_list
804
     * @param int   $questionSelectionType
805
     *
806
     * @return array
807
     */
808
    public function getQuestionListWithCategoryListFilteredByCategorySettings(
809
        $question_list,
810
        $questionSelectionType
811
    ) {
812
        $result = [
813
            'question_list' => [],
814
            'category_with_questions_list' => [],
815
        ];
816
817
        // Order/random categories
818
        $cat = new TestCategory();
819
820
        // Setting category order.
821
        switch ($questionSelectionType) {
822
            case EX_Q_SELECTION_ORDERED: // 1
823
            case EX_Q_SELECTION_RANDOM:  // 2
824
                // This options are not allowed here.
825
                break;
826
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
827
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
828
                    $this,
829
                    $this->course['real_id'],
830
                    'title ASC',
831
                    false,
832
                    true
833
                );
834
835
                $questions_by_category = TestCategory::getQuestionsByCat(
836
                    $this->id,
837
                    $question_list,
838
                    $categoriesAddedInExercise
839
                );
840
841
                $question_list = $this->pickQuestionsPerCategory(
842
                    $categoriesAddedInExercise,
843
                    $question_list,
844
                    $questions_by_category,
845
                    true,
846
                    false
847
                );
848
849
                break;
850
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
851
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
852
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
853
                    $this,
854
                    $this->course['real_id'],
855
                    null,
856
                    true,
857
                    true
858
                );
859
                $questions_by_category = TestCategory::getQuestionsByCat(
860
                    $this->id,
861
                    $question_list,
862
                    $categoriesAddedInExercise
863
                );
864
                $question_list = $this->pickQuestionsPerCategory(
865
                    $categoriesAddedInExercise,
866
                    $question_list,
867
                    $questions_by_category,
868
                    true,
869
                    false
870
                );
871
872
                break;
873
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
874
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
875
                    $this,
876
                    $this->course['real_id'],
877
                    'title ASC',
878
                    false,
879
                    true
880
                );
881
                $questions_by_category = TestCategory::getQuestionsByCat(
882
                    $this->id,
883
                    $question_list,
884
                    $categoriesAddedInExercise
885
                );
886
                $question_list = $this->pickQuestionsPerCategory(
887
                    $categoriesAddedInExercise,
888
                    $question_list,
889
                    $questions_by_category,
890
                    true,
891
                    true
892
                );
893
894
                break;
895
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
896
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
897
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
898
                    $this,
899
                    $this->course['real_id'],
900
                    null,
901
                    true,
902
                    true
903
                );
904
905
                $questions_by_category = TestCategory::getQuestionsByCat(
906
                    $this->id,
907
                    $question_list,
908
                    $categoriesAddedInExercise
909
                );
910
911
                $question_list = $this->pickQuestionsPerCategory(
912
                    $categoriesAddedInExercise,
913
                    $question_list,
914
                    $questions_by_category,
915
                    true,
916
                    true
917
                );
918
919
                break;
920
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
921
                break;
922
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
923
                break;
924
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
925
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
926
                    $this,
927
                    $this->course['real_id'],
928
                    'root ASC, lft ASC',
929
                    false,
930
                    true
931
                );
932
                $questions_by_category = TestCategory::getQuestionsByCat(
933
                    $this->id,
934
                    $question_list,
935
                    $categoriesAddedInExercise
936
                );
937
                $question_list = $this->pickQuestionsPerCategory(
938
                    $categoriesAddedInExercise,
939
                    $question_list,
940
                    $questions_by_category,
941
                    true,
942
                    false
943
                );
944
945
                break;
946
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
947
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
948
                    $this,
949
                    $this->course['real_id'],
950
                    'root, lft ASC',
951
                    false,
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
                    true
965
                );
966
967
                break;
968
        }
969
970
        $result['question_list'] = isset($question_list) ? $question_list : [];
971
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : [];
972
        $parentsLoaded = [];
973
        // Adding category info in the category list with question list:
974
        if (!empty($questions_by_category)) {
975
            $newCategoryList = [];
976
            $em = Database::getManager();
977
            $repo = $em->getRepository('ChamiloCourseBundle:CQuizCategory');
978
979
            foreach ($questions_by_category as $categoryId => $questionList) {
980
                $cat = new TestCategory();
981
                $cat = $cat->getCategory($categoryId);
982
                if ($cat) {
983
                    $cat = (array) $cat;
984
                    $cat['iid'] = $cat['id'];
985
                }
986
987
                $categoryParentInfo = null;
988
                // Parent is not set no loop here
989
                if (isset($cat['parent_id']) && !empty($cat['parent_id'])) {
990
                    /** @var CQuizCategory $categoryEntity */
991
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
992
                        $categoryEntity = $em->find('ChamiloCourseBundle:CQuizCategory', $cat['parent_id']);
993
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
994
                    } else {
995
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
996
                    }
997
                    $path = $repo->getPath($categoryEntity);
998
999
                    $index = 0;
1000
                    if ($this->categoryMinusOne) {
1001
                        //$index = 1;
1002
                    }
1003
1004
                    /** @var CQuizCategory $categoryParent */
1005
                    foreach ($path as $categoryParent) {
1006
                        $visibility = $categoryParent->getVisibility();
1007
                        if (0 == $visibility) {
1008
                            $categoryParentId = $categoryId;
1009
                            $categoryTitle = $cat['title'];
1010
                            if (count($path) > 1) {
1011
                                continue;
1012
                            }
1013
                        } else {
1014
                            $categoryParentId = $categoryParent->getIid();
1015
                            $categoryTitle = $categoryParent->getTitle();
1016
                        }
1017
1018
                        $categoryParentInfo['id'] = $categoryParentId;
1019
                        $categoryParentInfo['iid'] = $categoryParentId;
1020
                        $categoryParentInfo['parent_path'] = null;
1021
                        $categoryParentInfo['title'] = $categoryTitle;
1022
                        $categoryParentInfo['name'] = $categoryTitle;
1023
                        $categoryParentInfo['parent_id'] = null;
1024
1025
                        break;
1026
                    }
1027
                }
1028
                $cat['parent_info'] = $categoryParentInfo;
1029
                $newCategoryList[$categoryId] = [
1030
                    'category' => $cat,
1031
                    'question_list' => $questionList,
1032
                ];
1033
            }
1034
1035
            $result['category_with_questions_list'] = $newCategoryList;
1036
        }
1037
1038
        return $result;
1039
    }
1040
1041
    /**
1042
     * returns the array with the question ID list.
1043
     *
1044
     * @param bool $fromDatabase Whether the results should be fetched in the database or just from memory
1045
     * @param bool $adminView    Whether we should return all questions (admin view) or
1046
     *                           just a list limited by the max number of random questions
1047
     *
1048
     * @author Olivier Brouckaert
1049
     *
1050
     * @return array - question ID list
1051
     */
1052
    public function selectQuestionList($fromDatabase = false, $adminView = false)
1053
    {
1054
        if ($fromDatabase && !empty($this->id)) {
1055
            $nbQuestions = $this->getQuestionCount();
1056
            $questionSelectionType = $this->getQuestionSelectionType();
1057
1058
            switch ($questionSelectionType) {
1059
                case EX_Q_SELECTION_ORDERED:
1060
                    $questionList = $this->getQuestionOrderedList($adminView);
1061
1062
                    break;
1063
                case EX_Q_SELECTION_RANDOM:
1064
                    // Not a random exercise, or if there are not at least 2 questions
1065
                    if (0 == $this->random || $nbQuestions < 2) {
1066
                        $questionList = $this->getQuestionOrderedList($adminView);
1067
                    } else {
1068
                        $questionList = $this->getRandomList($adminView);
1069
                    }
1070
1071
                    break;
1072
                default:
1073
                    $questionList = $this->getQuestionOrderedList($adminView);
1074
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1075
                        $questionList,
1076
                        $questionSelectionType
1077
                    );
1078
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1079
                    $questionList = $result['question_list'];
1080
1081
                    break;
1082
            }
1083
1084
            return $questionList;
1085
        }
1086
1087
        return $this->questionList;
1088
    }
1089
1090
    /**
1091
     * returns the number of questions in this exercise.
1092
     *
1093
     * @author Olivier Brouckaert
1094
     *
1095
     * @return int - number of questions
1096
     */
1097
    public function selectNbrQuestions()
1098
    {
1099
        return count($this->questionList);
1100
    }
1101
1102
    /**
1103
     * @return int
1104
     */
1105
    public function selectPropagateNeg()
1106
    {
1107
        return $this->propagate_neg;
1108
    }
1109
1110
    /**
1111
     * @return int
1112
     */
1113
    public function getSaveCorrectAnswers()
1114
    {
1115
        return $this->saveCorrectAnswers;
1116
    }
1117
1118
    /**
1119
     * Selects questions randomly in the question list.
1120
     *
1121
     * @author Olivier Brouckaert
1122
     * @author Hubert Borderiou 15 nov 2011
1123
     *
1124
     * @param bool $adminView Whether we should return all
1125
     *                        questions (admin view) or just a list limited by the max number of random questions
1126
     *
1127
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1128
     *               without randomizing, otherwise, returns the list with questions selected randomly
1129
     */
1130
    public function getRandomList($adminView = false)
1131
    {
1132
        $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1133
        $question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1134
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1135
1136
        // Random with limit
1137
        $randomLimit = " ORDER BY RAND() LIMIT $random";
1138
1139
        // Random with no limit
1140
        if (-1 == $random) {
1141
            $randomLimit = ' ORDER BY RAND() ';
1142
        }
1143
1144
        // Admin see the list in default order
1145
        if (true === $adminView) {
1146
            // If viewing it as admin for edition, don't show it randomly, use title + id
1147
            $randomLimit = 'ORDER BY e.question_order';
1148
        }
1149
1150
        $sql = "SELECT e.question_id
1151
                FROM $quizRelQuestion e
1152
                INNER JOIN $question q
1153
                ON (e.question_id= q.id AND e.c_id = q.c_id)
1154
                WHERE
1155
                    e.c_id = {$this->course_id} AND
1156
                    e.exercice_id = '".Database::escape_string($this->id)."'
1157
                    $randomLimit ";
1158
        $result = Database::query($sql);
1159
        $questionList = [];
1160
        while ($row = Database::fetch_object($result)) {
1161
            $questionList[] = $row->question_id;
1162
        }
1163
1164
        return $questionList;
1165
    }
1166
1167
    /**
1168
     * returns 'true' if the question ID is in the question list.
1169
     *
1170
     * @author Olivier Brouckaert
1171
     *
1172
     * @param int $questionId - question ID
1173
     *
1174
     * @return bool - true if in the list, otherwise false
1175
     */
1176
    public function isInList($questionId)
1177
    {
1178
        $inList = false;
1179
        if (is_array($this->questionList)) {
1180
            $inList = in_array($questionId, $this->questionList);
1181
        }
1182
1183
        return $inList;
1184
    }
1185
1186
    /**
1187
     * If current exercise has a question.
1188
     *
1189
     * @param int $questionId
1190
     *
1191
     * @return int
1192
     */
1193
    public function hasQuestion($questionId)
1194
    {
1195
        $questionId = (int) $questionId;
1196
1197
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1198
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1199
        $sql = "SELECT q.id
1200
                FROM $TBL_EXERCICE_QUESTION e
1201
                INNER JOIN $TBL_QUESTIONS q
1202
                ON (e.question_id = q.id AND e.c_id = q.c_id)
1203
                WHERE
1204
                    q.id = $questionId AND
1205
                    e.c_id = {$this->course_id} AND
1206
                    e.exercice_id = ".$this->id;
1207
1208
        $result = Database::query($sql);
1209
1210
        return Database::num_rows($result) > 0;
1211
    }
1212
1213
    public function hasQuestionWithTypeNotInList(array $questionTypeList)
1214
    {
1215
        if (empty($questionTypeList)) {
1216
            return false;
1217
        }
1218
1219
        $questionTypeToString = implode("','", array_map('intval', $questionTypeList));
1220
1221
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1222
        $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
1223
        $sql = "SELECT q.id
1224
                FROM $table e
1225
                INNER JOIN $tableQuestion q
1226
                ON (e.question_id = q.id AND e.c_id = q.c_id)
1227
                WHERE
1228
                    q.type NOT IN ('$questionTypeToString')  AND
1229
                    e.c_id = {$this->course_id} AND
1230
                    e.exercice_id = ".$this->id;
1231
1232
        $result = Database::query($sql);
1233
1234
        return Database::num_rows($result) > 0;
1235
    }
1236
1237
    /**
1238
     * changes the exercise title.
1239
     *
1240
     * @author Olivier Brouckaert
1241
     *
1242
     * @param string $title - exercise title
1243
     */
1244
    public function updateTitle($title)
1245
    {
1246
        $this->title = $this->exercise = $title;
1247
    }
1248
1249
    /**
1250
     * changes the exercise max attempts.
1251
     *
1252
     * @param int $attempts - exercise max attempts
1253
     */
1254
    public function updateAttempts($attempts)
1255
    {
1256
        $this->attempts = $attempts;
1257
    }
1258
1259
    /**
1260
     * changes the exercise feedback type.
1261
     *
1262
     * @param int $feedback_type
1263
     */
1264
    public function updateFeedbackType($feedback_type)
1265
    {
1266
        $this->feedback_type = $feedback_type;
1267
    }
1268
1269
    /**
1270
     * changes the exercise description.
1271
     *
1272
     * @author Olivier Brouckaert
1273
     *
1274
     * @param string $description - exercise description
1275
     */
1276
    public function updateDescription($description)
1277
    {
1278
        $this->description = $description;
1279
    }
1280
1281
    /**
1282
     * changes the exercise expired_time.
1283
     *
1284
     * @author Isaac flores
1285
     *
1286
     * @param int $expired_time The expired time of the quiz
1287
     */
1288
    public function updateExpiredTime($expired_time)
1289
    {
1290
        $this->expired_time = $expired_time;
1291
    }
1292
1293
    /**
1294
     * @param $value
1295
     */
1296
    public function updatePropagateNegative($value)
1297
    {
1298
        $this->propagate_neg = $value;
1299
    }
1300
1301
    /**
1302
     * @param int $value
1303
     */
1304
    public function updateSaveCorrectAnswers($value)
1305
    {
1306
        $this->saveCorrectAnswers = (int) $value;
1307
    }
1308
1309
    /**
1310
     * @param $value
1311
     */
1312
    public function updateReviewAnswers($value)
1313
    {
1314
        $this->review_answers = isset($value) && $value ? true : false;
1315
    }
1316
1317
    /**
1318
     * @param $value
1319
     */
1320
    public function updatePassPercentage($value)
1321
    {
1322
        $this->pass_percentage = $value;
1323
    }
1324
1325
    /**
1326
     * @param string $text
1327
     */
1328
    public function updateEmailNotificationTemplate($text)
1329
    {
1330
        $this->emailNotificationTemplate = $text;
1331
    }
1332
1333
    /**
1334
     * @param string $text
1335
     */
1336
    public function setEmailNotificationTemplateToUser($text)
1337
    {
1338
        $this->emailNotificationTemplateToUser = $text;
1339
    }
1340
1341
    /**
1342
     * @param string $value
1343
     */
1344
    public function setNotifyUserByEmail($value)
1345
    {
1346
        $this->notifyUserByEmail = $value;
1347
    }
1348
1349
    /**
1350
     * @param int $value
1351
     */
1352
    public function updateEndButton($value)
1353
    {
1354
        $this->endButton = (int) $value;
1355
    }
1356
1357
    /**
1358
     * @param string $value
1359
     */
1360
    public function setOnSuccessMessage($value)
1361
    {
1362
        $this->onSuccessMessage = $value;
1363
    }
1364
1365
    /**
1366
     * @param string $value
1367
     */
1368
    public function setOnFailedMessage($value)
1369
    {
1370
        $this->onFailedMessage = $value;
1371
    }
1372
1373
    /**
1374
     * @param $value
1375
     */
1376
    public function setModelType($value)
1377
    {
1378
        $this->modelType = (int) $value;
1379
    }
1380
1381
    /**
1382
     * @param int $value
1383
     */
1384
    public function setQuestionSelectionType($value)
1385
    {
1386
        $this->questionSelectionType = (int) $value;
1387
    }
1388
1389
    /**
1390
     * @return int
1391
     */
1392
    public function getQuestionSelectionType()
1393
    {
1394
        return (int) $this->questionSelectionType;
1395
    }
1396
1397
    /**
1398
     * @param array $categories
1399
     */
1400
    public function updateCategories($categories)
1401
    {
1402
        if (!empty($categories)) {
1403
            $categories = array_map('intval', $categories);
1404
            $this->categories = $categories;
1405
        }
1406
    }
1407
1408
    /**
1409
     * changes the exercise sound file.
1410
     *
1411
     * @author Olivier Brouckaert
1412
     *
1413
     * @param string $sound  - exercise sound file
1414
     * @param string $delete - ask to delete the file
1415
     */
1416
    public function updateSound($sound, $delete)
1417
    {
1418
        global $audioPath, $documentPath;
1419
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1420
1421
        if ($sound['size'] && (strstr($sound['type'], 'audio') || strstr($sound['type'], 'video'))) {
1422
            $this->sound = $sound['name'];
1423
1424
            if (@move_uploaded_file($sound['tmp_name'], $audioPath.'/'.$this->sound)) {
1425
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1426
                        WHERE
1427
                            c_id = ".$this->course_id." AND
1428
                            path = '".str_replace($documentPath, '', $audioPath).'/'.$this->sound."'";
1429
                $result = Database::query($sql);
1430
1431
                if (!Database::num_rows($result)) {
1432
                    DocumentManager::addDocument(
1433
                        $this->course,
1434
                        str_replace($documentPath, '', $audioPath).'/'.$this->sound,
1435
                        'file',
1436
                        $sound['size'],
1437
                        $sound['name']
1438
                    );
1439
                }
1440
            }
1441
        } elseif ($delete && is_file($audioPath.'/'.$this->sound)) {
1442
            $this->sound = '';
1443
        }
1444
    }
1445
1446
    /**
1447
     * changes the exercise type.
1448
     *
1449
     * @author Olivier Brouckaert
1450
     *
1451
     * @param int $type - exercise type
1452
     */
1453
    public function updateType($type)
1454
    {
1455
        $this->type = $type;
1456
    }
1457
1458
    /**
1459
     * sets to 0 if questions are not selected randomly
1460
     * if questions are selected randomly, sets the draws.
1461
     *
1462
     * @author Olivier Brouckaert
1463
     *
1464
     * @param int $random - 0 if not random, otherwise the draws
1465
     */
1466
    public function setRandom($random)
1467
    {
1468
        $this->random = $random;
1469
    }
1470
1471
    /**
1472
     * sets to 0 if answers are not selected randomly
1473
     * if answers are selected randomly.
1474
     *
1475
     * @author Juan Carlos Rana
1476
     *
1477
     * @param int $random_answers - random answers
1478
     */
1479
    public function updateRandomAnswers($random_answers)
1480
    {
1481
        $this->random_answers = $random_answers;
1482
    }
1483
1484
    /**
1485
     * enables the exercise.
1486
     *
1487
     * @author Olivier Brouckaert
1488
     */
1489
    public function enable()
1490
    {
1491
        $this->active = 1;
1492
    }
1493
1494
    /**
1495
     * disables the exercise.
1496
     *
1497
     * @author Olivier Brouckaert
1498
     */
1499
    public function disable()
1500
    {
1501
        $this->active = 0;
1502
    }
1503
1504
    /**
1505
     * Set disable results.
1506
     */
1507
    public function disable_results()
1508
    {
1509
        $this->results_disabled = true;
1510
    }
1511
1512
    /**
1513
     * Enable results.
1514
     */
1515
    public function enable_results()
1516
    {
1517
        $this->results_disabled = false;
1518
    }
1519
1520
    /**
1521
     * @param int $results_disabled
1522
     */
1523
    public function updateResultsDisabled($results_disabled)
1524
    {
1525
        $this->results_disabled = (int) $results_disabled;
1526
    }
1527
1528
    /**
1529
     * updates the exercise in the data base.
1530
     *
1531
     * @author Olivier Brouckaert
1532
     */
1533
    public function save()
1534
    {
1535
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1536
1537
        $id = $this->id;
1538
        $exercise = $this->exercise;
1539
        $description = $this->description;
1540
        $sound = $this->sound;
1541
        $type = $this->type;
1542
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1543
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1544
        $random = $this->random;
1545
        $random_answers = $this->random_answers;
1546
        $active = $this->active;
1547
        $propagate_neg = (int) $this->propagate_neg;
1548
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) ? (int) $this->saveCorrectAnswers : 0;
1549
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1550
        $randomByCat = (int) $this->randomByCat;
1551
        $text_when_finished = $this->text_when_finished;
1552
        $display_category_name = (int) $this->display_category_name;
1553
        $pass_percentage = (int) $this->pass_percentage;
1554
        $session_id = $this->sessionId;
1555
1556
        // If direct we do not show results
1557
        $results_disabled = (int) $this->results_disabled;
1558
        if (in_array($feedback_type, [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
1559
            $results_disabled = 0;
1560
        }
1561
        $expired_time = (int) $this->expired_time;
1562
1563
        $repo = Container::getExerciseRepository();
1564
        // Exercise already exists
1565
        if ($id) {
1566
            // we prepare date in the database using the api_get_utc_datetime() function
1567
            $start_time = null;
1568
            if (!empty($this->start_time)) {
1569
                $start_time = $this->start_time;
1570
            }
1571
1572
            $end_time = null;
1573
            if (!empty($this->end_time)) {
1574
                $end_time = $this->end_time;
1575
            }
1576
1577
            $params = [
1578
                'title' => $exercise,
1579
                'description' => $description,
1580
            ];
1581
1582
            $paramsExtra = [
1583
                'sound' => $sound,
1584
                'type' => $type,
1585
                'random' => $random,
1586
                'random_answers' => $random_answers,
1587
                'active' => $active,
1588
                'feedback_type' => $feedback_type,
1589
                'start_time' => $start_time,
1590
                'end_time' => $end_time,
1591
                'max_attempt' => $attempts,
1592
                'expired_time' => $expired_time,
1593
                'propagate_neg' => $propagate_neg,
1594
                'save_correct_answers' => $saveCorrectAnswers,
1595
                'review_answers' => $review_answers,
1596
                'random_by_category' => $randomByCat,
1597
                'text_when_finished' => $text_when_finished,
1598
                'display_category_name' => $display_category_name,
1599
                'pass_percentage' => $pass_percentage,
1600
                'results_disabled' => $results_disabled,
1601
                'question_selection_type' => $this->getQuestionSelectionType(),
1602
                'hide_question_title' => $this->getHideQuestionTitle(),
1603
            ];
1604
1605
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1606
            if (true === $allow) {
1607
                $paramsExtra['show_previous_button'] = $this->showPreviousButton();
1608
            }
1609
1610
            if (api_get_configuration_value('quiz_prevent_backwards_move')) {
1611
                $paramsExtra['prevent_backwards'] = $this->getPreventBackwards();
1612
            }
1613
1614
            $allow = api_get_configuration_value('allow_exercise_categories');
1615
            if (true === $allow) {
1616
                if (!empty($this->getExerciseCategoryId())) {
1617
                    $paramsExtra['exercise_category_id'] = $this->getExerciseCategoryId();
1618
                }
1619
            }
1620
1621
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1622
            if (true === $allow) {
1623
                $notifications = $this->getNotifications();
1624
                $notifications = implode(',', $notifications);
1625
                $paramsExtra['notifications'] = $notifications;
1626
            }
1627
1628
            if (!empty($this->pageResultConfiguration)) {
1629
                $paramsExtra['page_result_configuration'] = $this->pageResultConfiguration;
1630
            }
1631
1632
            $params = array_merge($params, $paramsExtra);
1633
1634
            Database::update(
1635
                $TBL_EXERCISES,
1636
                $params,
1637
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1638
            );
1639
            /** @var CQuiz $exerciseEntity */
1640
            $exerciseEntity = $repo->find($id);
1641
            $repo->updateNodeForResource($exerciseEntity);
1642
1643
            if ('true' == api_get_setting('search_enabled')) {
1644
                $this->search_engine_edit();
1645
            }
1646
        } else {
1647
            // Creates a new exercise
1648
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1649
            // for date because, bellow, we call function api_set_default_visibility()
1650
            // In this function, api_set_default_visibility,
1651
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1652
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1653
            $start_time = null;
1654
            if (!empty($this->start_time)) {
1655
                $start_time = $this->start_time;
1656
            }
1657
1658
            $end_time = null;
1659
            if (!empty($this->end_time)) {
1660
                $end_time = $this->end_time;
1661
            }
1662
1663
            $params = [
1664
                'c_id' => $this->course_id,
1665
                'start_time' => $start_time,
1666
                'end_time' => $end_time,
1667
                'title' => $exercise,
1668
                'description' => $description,
1669
                'sound' => $sound,
1670
                'type' => $type,
1671
                'random' => $random,
1672
                'random_answers' => $random_answers,
1673
                'active' => $active,
1674
                'results_disabled' => $results_disabled,
1675
                'max_attempt' => $attempts,
1676
                'feedback_type' => $feedback_type,
1677
                'expired_time' => $expired_time,
1678
                'session_id' => $session_id,
1679
                'review_answers' => $review_answers,
1680
                'random_by_category' => $randomByCat,
1681
                'text_when_finished' => $text_when_finished,
1682
                'display_category_name' => $display_category_name,
1683
                'pass_percentage' => $pass_percentage,
1684
                'save_correct_answers' => $saveCorrectAnswers,
1685
                'propagate_neg' => $propagate_neg,
1686
                'hide_question_title' => $this->getHideQuestionTitle(),
1687
            ];
1688
1689
            $allow = api_get_configuration_value('allow_exercise_categories');
1690
            if (true === $allow) {
1691
                if (!empty($this->getExerciseCategoryId())) {
1692
                    $params['exercise_category_id'] = $this->getExerciseCategoryId();
1693
                }
1694
            }
1695
1696
            if (api_get_configuration_value('quiz_prevent_backwards_move')) {
1697
                $params['prevent_backwards'] = $this->getPreventBackwards();
1698
            }
1699
1700
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1701
            if (true === $allow) {
1702
                $params['show_previous_button'] = $this->showPreviousButton();
1703
            }
1704
1705
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1706
            if (true === $allow) {
1707
                $notifications = $this->getNotifications();
1708
                $params['notifications'] = '';
1709
                if (!empty($notifications)) {
1710
                    $notifications = implode(',', $notifications);
1711
                    $params['notifications'] = $notifications;
1712
                }
1713
            }
1714
1715
            if (!empty($this->pageResultConfiguration)) {
1716
                $params['page_result_configuration'] = $this->pageResultConfiguration;
1717
            }
1718
1719
            $this->id = $this->iId = Database::insert($TBL_EXERCISES, $params);
1720
1721
            if ($this->id) {
1722
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1723
                Database::query($sql);
1724
1725
                $sql = "UPDATE $TBL_EXERCISES
1726
                        SET question_selection_type= ".$this->getQuestionSelectionType().'
1727
                        WHERE id = '.$this->id.' AND c_id = '.$this->course_id;
1728
                Database::query($sql);
1729
1730
                $exercise = $repo->find($this->id);
1731
                if ($exercise) {
1732
                    $repo->addResourceToCourse(
1733
                        $exercise,
1734
                        ResourceLink::VISIBILITY_PUBLISHED,
1735
                        api_get_user_entity(api_get_user_id()),
1736
                        api_get_course_entity($this->course_id),
1737
                        api_get_session_entity(),
1738
                        api_get_group_entity()
1739
                    );
1740
                    $repo->getEntityManager()->flush();
1741
1742
                    if ('true' == api_get_setting('search_enabled') && extension_loaded('xapian')) {
1743
                        $this->search_engine_save();
1744
                    }
1745
                }
1746
            }
1747
        }
1748
1749
        $this->save_categories_in_exercise($this->categories);
1750
1751
        return $this->iId;
1752
    }
1753
1754
    /**
1755
     * Updates question position.
1756
     *
1757
     * @return bool
1758
     */
1759
    public function update_question_positions()
1760
    {
1761
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1762
        // Fixes #3483 when updating order
1763
        $questionList = $this->selectQuestionList(true);
1764
1765
        $this->id = (int) $this->id;
1766
1767
        if (empty($this->id)) {
1768
            return false;
1769
        }
1770
1771
        if (!empty($questionList)) {
1772
            foreach ($questionList as $position => $questionId) {
1773
                $position = (int) $position;
1774
                $questionId = (int) $questionId;
1775
                $sql = "UPDATE $table SET
1776
                            question_order ='".$position."'
1777
                        WHERE
1778
                            c_id = ".$this->course_id.' AND
1779
                            question_id = '.$questionId.' AND
1780
                            exercice_id='.$this->id;
1781
                Database::query($sql);
1782
            }
1783
        }
1784
1785
        return true;
1786
    }
1787
1788
    /**
1789
     * Adds a question into the question list.
1790
     *
1791
     * @author Olivier Brouckaert
1792
     *
1793
     * @param int $questionId - question ID
1794
     *
1795
     * @return bool - true if the question has been added, otherwise false
1796
     */
1797
    public function addToList($questionId)
1798
    {
1799
        // checks if the question ID is not in the list
1800
        if (!$this->isInList($questionId)) {
1801
            // selects the max position
1802
            if (!$this->selectNbrQuestions()) {
1803
                $pos = 1;
1804
            } else {
1805
                if (is_array($this->questionList)) {
1806
                    $pos = max(array_keys($this->questionList)) + 1;
1807
                }
1808
            }
1809
            $this->questionList[$pos] = $questionId;
1810
1811
            return true;
1812
        }
1813
1814
        return false;
1815
    }
1816
1817
    /**
1818
     * removes a question from the question list.
1819
     *
1820
     * @author Olivier Brouckaert
1821
     *
1822
     * @param int $questionId - question ID
1823
     *
1824
     * @return bool - true if the question has been removed, otherwise false
1825
     */
1826
    public function removeFromList($questionId)
1827
    {
1828
        // searches the position of the question ID in the list
1829
        $pos = array_search($questionId, $this->questionList);
1830
        // question not found
1831
        if (false === $pos) {
1832
            return false;
1833
        } else {
1834
            // dont reduce the number of random question if we use random by category option, or if
1835
            // random all questions
1836
            if ($this->isRandom() && 0 == $this->isRandomByCat()) {
1837
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1838
                    $this->random--;
1839
                    $this->save();
1840
                }
1841
            }
1842
            // deletes the position from the array containing the wanted question ID
1843
            unset($this->questionList[$pos]);
1844
1845
            return true;
1846
        }
1847
    }
1848
1849
    /**
1850
     * deletes the exercise from the database
1851
     * Notice : leaves the question in the data base.
1852
     *
1853
     * @author Olivier Brouckaert
1854
     */
1855
    public function delete()
1856
    {
1857
        $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
1858
1859
        if ($limitTeacherAccess && !api_is_platform_admin()) {
1860
            return false;
1861
        }
1862
1863
        $exerciseId = $this->iId;
1864
1865
        $repo = Container::getExerciseRepository();
1866
        $exercise = $repo->find($exerciseId);
1867
1868
        if (null === $exercise) {
1869
            return false;
1870
        }
1871
1872
        $locked = api_resource_is_locked_by_gradebook(
1873
            $exerciseId,
1874
            LINK_EXERCISE
1875
        );
1876
1877
        if ($locked) {
1878
            return false;
1879
        }
1880
1881
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
1882
        $sql = "UPDATE $table SET active='-1'
1883
                WHERE c_id = ".$this->course_id.' AND iid = '.$exerciseId;
1884
        Database::query($sql);
1885
1886
        $repo->softDelete($exercise);
1887
1888
        Skill::deleteSkillsFromItem($exerciseId, ITEM_TYPE_EXERCISE);
1889
1890
        if ('true' === api_get_setting('search_enabled') &&
1891
            extension_loaded('xapian')
1892
        ) {
1893
            $this->search_engine_delete();
1894
        }
1895
1896
        $linkInfo = GradebookUtils::isResourceInCourseGradebook(
1897
            $this->course['code'],
1898
            LINK_EXERCISE,
1899
            $exerciseId,
1900
            $this->sessionId
1901
        );
1902
        if (false !== $linkInfo) {
1903
            GradebookUtils::remove_resource_from_course_gradebook($linkInfo['id']);
1904
        }
1905
1906
        return true;
1907
    }
1908
1909
    /**
1910
     * Creates the form to create / edit an exercise.
1911
     *
1912
     * @param FormValidator $form
1913
     * @param string        $type
1914
     */
1915
    public function createForm($form, $type = 'full')
1916
    {
1917
        if (empty($type)) {
1918
            $type = 'full';
1919
        }
1920
1921
        // Form title
1922
        $form_title = get_lang('Create a new test');
1923
        if (!empty($_GET['id'])) {
1924
            $form_title = get_lang('Edit test name and settings');
1925
        }
1926
1927
        $form->addHeader($form_title);
1928
1929
        // Title.
1930
        if (api_get_configuration_value('save_titles_as_html')) {
1931
            $form->addHtmlEditor(
1932
                'exerciseTitle',
1933
                get_lang('Test name'),
1934
                false,
1935
                false,
1936
                ['ToolbarSet' => 'TitleAsHtml']
1937
            );
1938
        } else {
1939
            $form->addElement(
1940
                'text',
1941
                'exerciseTitle',
1942
                get_lang('Test name'),
1943
                ['id' => 'exercise_title']
1944
            );
1945
        }
1946
1947
        $form->addElement('advanced_settings', 'advanced_params', get_lang('Advanced settings'));
1948
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1949
1950
        if (api_get_configuration_value('allow_exercise_categories')) {
1951
            $categoryManager = new ExerciseCategoryManager();
1952
            $categories = $categoryManager->getCategories(api_get_course_int_id());
1953
            $options = [];
1954
            if (!empty($categories)) {
1955
                /** @var CExerciseCategory $category */
1956
                foreach ($categories as $category) {
1957
                    $options[$category->getId()] = $category->getName();
1958
                }
1959
            }
1960
1961
            $form->addSelect(
1962
                'exercise_category_id',
1963
                get_lang('Category'),
1964
                $options,
1965
                ['placeholder' => get_lang('Please select an option')]
1966
            );
1967
        }
1968
1969
        $editor_config = [
1970
            'ToolbarSet' => 'TestQuestionDescription',
1971
            'Width' => '100%',
1972
            'Height' => '150',
1973
        ];
1974
1975
        if (is_array($type)) {
1976
            $editor_config = array_merge($editor_config, $type);
1977
        }
1978
1979
        $form->addHtmlEditor(
1980
            'exerciseDescription',
1981
            get_lang('Give a context to the test'),
1982
            false,
1983
            false,
1984
            $editor_config
1985
        );
1986
1987
        $skillList = [];
1988
        if ('full' === $type) {
1989
            // Can't modify a DirectFeedback question.
1990
            if (!in_array($this->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
1991
                $this->setResultFeedbackGroup($form);
1992
1993
                // Type of results display on the final page
1994
                $this->setResultDisabledGroup($form);
1995
1996
                // Type of questions disposition on page
1997
                $radios = [];
1998
                $radios[] = $form->createElement(
1999
                    'radio',
2000
                    'exerciseType',
2001
                    null,
2002
                    get_lang('All questions on one page'),
2003
                    '1',
2004
                    [
2005
                        'onclick' => 'check_per_page_all()',
2006
                        'id' => 'option_page_all',
2007
                    ]
2008
                );
2009
                $radios[] = $form->createElement(
2010
                    'radio',
2011
                    'exerciseType',
2012
                    null,
2013
                    get_lang('One question by page'),
2014
                    '2',
2015
                    [
2016
                        'onclick' => 'check_per_page_one()',
2017
                        'id' => 'option_page_one',
2018
                    ]
2019
                );
2020
2021
                $form->addGroup($radios, null, get_lang('Questions per page'));
2022
            } else {
2023
                // if is Direct feedback but has not questions we can allow to modify the question type
2024
                if (0 === $this->getQuestionCount()) {
2025
                    $this->setResultFeedbackGroup($form);
2026
                    $this->setResultDisabledGroup($form);
2027
2028
                    // Type of questions disposition on page
2029
                    $radios = [];
2030
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('All questions on one page'), '1');
2031
                    $radios[] = $form->createElement(
2032
                        'radio',
2033
                        'exerciseType',
2034
                        null,
2035
                        get_lang('One question by page'),
2036
                        '2'
2037
                    );
2038
                    $form->addGroup($radios, null, get_lang('Sequential'));
2039
                } else {
2040
                    $this->setResultFeedbackGroup($form, true);
2041
                    $group = $this->setResultDisabledGroup($form);
2042
                    $group->freeze();
2043
2044
                    // we force the options to the DirectFeedback exercisetype
2045
                    //$form->addElement('hidden', 'exerciseFeedbackType', $this->getFeedbackType());
2046
                    //$form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
2047
2048
                    // Type of questions disposition on page
2049
                    $radios[] = $form->createElement(
2050
                        'radio',
2051
                        'exerciseType',
2052
                        null,
2053
                        get_lang('All questions on one page'),
2054
                        '1',
2055
                        [
2056
                            'onclick' => 'check_per_page_all()',
2057
                            'id' => 'option_page_all',
2058
                        ]
2059
                    );
2060
                    $radios[] = $form->createElement(
2061
                        'radio',
2062
                        'exerciseType',
2063
                        null,
2064
                        get_lang('One question by page'),
2065
                        '2',
2066
                        [
2067
                            'onclick' => 'check_per_page_one()',
2068
                            'id' => 'option_page_one',
2069
                        ]
2070
                    );
2071
2072
                    $type_group = $form->addGroup($radios, null, get_lang('Questions per page'));
2073
                    $type_group->freeze();
2074
                }
2075
            }
2076
2077
            $option = [
2078
                EX_Q_SELECTION_ORDERED => get_lang('Ordered by user'),
2079
                //  Defined by user
2080
                EX_Q_SELECTION_RANDOM => get_lang('Random'),
2081
                // 1-10, All
2082
                'per_categories' => '--------'.get_lang('Using categories').'----------',
2083
                // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2084
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang('Ordered categories alphabetically with questions ordered'),
2085
                // A 123 B 456 C 78 (0, 1, all)
2086
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang('Random categories with questions ordered'),
2087
                // C 78 B 456 A 123
2088
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang('Ordered categories alphabetically with random questions'),
2089
                // A 321 B 654 C 87
2090
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang('Random categories with random questions'),
2091
                // C 87 B 654 A 321
2092
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2093
                /*    B 456 C 78 A 123
2094
                        456 78 123
2095
                        123 456 78
2096
                */
2097
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2098
                /*
2099
                    A 123 B 456 C 78
2100
                    B 456 C 78 A 123
2101
                    B 654 C 87 A 321
2102
                    654 87 321
2103
                    165 842 73
2104
                */
2105
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2106
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2107
            ];
2108
2109
            $form->addElement(
2110
                'select',
2111
                'question_selection_type',
2112
                [get_lang('Question selection type')],
2113
                $option,
2114
                [
2115
                    'id' => 'questionSelection',
2116
                    'onchange' => 'checkQuestionSelection()',
2117
                ]
2118
            );
2119
2120
            $group = [
2121
                $form->createElement(
2122
                    'checkbox',
2123
                    'hide_expected_answer',
2124
                    null,
2125
                    get_lang('Hide expected answers column')
2126
                ),
2127
                $form->createElement(
2128
                    'checkbox',
2129
                    'hide_total_score',
2130
                    null,
2131
                    get_lang('Hide total score')
2132
                ),
2133
                $form->createElement(
2134
                    'checkbox',
2135
                    'hide_question_score',
2136
                    null,
2137
                    get_lang('Hide question score')
2138
                ),
2139
            ];
2140
            $form->addGroup($group, null, get_lang('Results and feedback and feedback and feedback and feedback and feedback and feedback page configuration'));
2141
2142
            $displayMatrix = 'none';
2143
            $displayRandom = 'none';
2144
            $selectionType = $this->getQuestionSelectionType();
2145
            switch ($selectionType) {
2146
                case EX_Q_SELECTION_RANDOM:
2147
                    $displayRandom = 'block';
2148
2149
                    break;
2150
                case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2151
                    $displayMatrix = 'block';
2152
2153
                    break;
2154
            }
2155
2156
            $form->addElement(
2157
                'html',
2158
                '<div id="hidden_random" style="display:'.$displayRandom.'">'
2159
            );
2160
            // Number of random question.
2161
            $max = $this->id > 0 ? $this->getQuestionCount() : 10;
2162
            $option = range(0, $max);
2163
            $option[0] = get_lang('No');
2164
            $option[-1] = get_lang('All');
2165
            $form->addElement(
2166
                'select',
2167
                'randomQuestions',
2168
                [
2169
                    get_lang('Random questions'),
2170
                    get_lang('Random questionsHelp'),
2171
                ],
2172
                $option,
2173
                ['id' => 'randomQuestions']
2174
            );
2175
            $form->addElement('html', '</div>');
2176
2177
            $form->addElement(
2178
                'html',
2179
                '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2180
            );
2181
2182
            // Category selection.
2183
            $cat = new TestCategory();
2184
            $cat_form = $cat->returnCategoryForm($this);
2185
            if (empty($cat_form)) {
2186
                $cat_form = '<span class="label label-warning">'.get_lang('No categories defined').'</span>';
2187
            }
2188
            $form->addElement('label', null, $cat_form);
2189
            $form->addElement('html', '</div>');
2190
2191
            // Random answers.
2192
            $radios_random_answers = [
2193
                $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2194
                $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0'),
2195
            ];
2196
            $form->addGroup($radios_random_answers, null, get_lang('Shuffle answers'));
2197
2198
            // Category name.
2199
            $radio_display_cat_name = [
2200
                $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2201
                $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0'),
2202
            ];
2203
            $form->addGroup($radio_display_cat_name, null, get_lang('Display questions category'));
2204
2205
            // Hide question title.
2206
            $group = [
2207
                $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2208
                $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0'),
2209
            ];
2210
            $form->addGroup($group, null, get_lang('Hide question title'));
2211
2212
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
2213
2214
            if (true === $allow) {
2215
                // Hide question title.
2216
                $group = [
2217
                    $form->createElement(
2218
                        'radio',
2219
                        'show_previous_button',
2220
                        null,
2221
                        get_lang('Yes'),
2222
                        '1'
2223
                    ),
2224
                    $form->createElement(
2225
                        'radio',
2226
                        'show_previous_button',
2227
                        null,
2228
                        get_lang('No'),
2229
                        '0'
2230
                    ),
2231
                ];
2232
                $form->addGroup($group, null, get_lang('Show previous button'));
2233
            }
2234
2235
            $form->addElement(
2236
                'number',
2237
                'exerciseAttempts',
2238
                get_lang('max. 20 characters, e.g. <i>INNOV21</i> number of attempts'),
2239
                null,
2240
                ['id' => 'exerciseAttempts']
2241
            );
2242
2243
            // Exercise time limit
2244
            $form->addElement(
2245
                'checkbox',
2246
                'activate_start_date_check',
2247
                null,
2248
                get_lang('Enable start time'),
2249
                ['onclick' => 'activate_start_date()']
2250
            );
2251
2252
            if (!empty($this->start_time)) {
2253
                $form->addElement('html', '<div id="start_date_div" style="display:block;">');
2254
            } else {
2255
                $form->addElement('html', '<div id="start_date_div" style="display:none;">');
2256
            }
2257
2258
            $form->addElement('date_time_picker', 'start_time');
2259
            $form->addElement('html', '</div>');
2260
            $form->addElement(
2261
                'checkbox',
2262
                'activate_end_date_check',
2263
                null,
2264
                get_lang('Enable end time'),
2265
                ['onclick' => 'activate_end_date()']
2266
            );
2267
2268
            if (!empty($this->end_time)) {
2269
                $form->addElement('html', '<div id="end_date_div" style="display:block;">');
2270
            } else {
2271
                $form->addElement('html', '<div id="end_date_div" style="display:none;">');
2272
            }
2273
2274
            $form->addElement('date_time_picker', 'end_time');
2275
            $form->addElement('html', '</div>');
2276
2277
            $display = 'block';
2278
            $form->addElement(
2279
                'checkbox',
2280
                'propagate_neg',
2281
                null,
2282
                get_lang('Propagate negative results between questions')
2283
            );
2284
2285
            $options = [
2286
                '' => get_lang('Please select an option'),
2287
                1 => get_lang('Save the correct answer for the next attempt'),
2288
                2 => get_lang('Pre-fill with answers from previous attempt'),
2289
            ];
2290
            $form->addSelect(
2291
                'save_correct_answers',
2292
                get_lang('Save answers'),
2293
                $options
2294
            );
2295
2296
            $form->addElement('html', '<div class="clear">&nbsp;</div>');
2297
            $form->addElement('checkbox', 'review_answers', null, get_lang('Review my answers'));
2298
            $form->addElement('html', '<div id="divtimecontrol"  style="display:'.$display.';">');
2299
2300
            // Timer control
2301
            $form->addElement(
2302
                'checkbox',
2303
                'enabletimercontrol',
2304
                null,
2305
                get_lang('Enable time control'),
2306
                [
2307
                    'onclick' => 'option_time_expired()',
2308
                    'id' => 'enabletimercontrol',
2309
                    'onload' => 'check_load_time()',
2310
                ]
2311
            );
2312
2313
            $expired_date = (int) $this->selectExpiredTime();
2314
2315
            if (('0' != $expired_date)) {
2316
                $form->addElement('html', '<div id="timercontrol" style="display:block;">');
2317
            } else {
2318
                $form->addElement('html', '<div id="timercontrol" style="display:none;">');
2319
            }
2320
            $form->addText(
2321
                'enabletimercontroltotalminutes',
2322
                get_lang('Total duration in minutes of the test'),
2323
                false,
2324
                [
2325
                    'id' => 'enabletimercontroltotalminutes',
2326
                    'cols-size' => [2, 2, 8],
2327
                ]
2328
            );
2329
            $form->addElement('html', '</div>');
2330
2331
            if (api_get_configuration_value('quiz_prevent_backwards_move')) {
2332
                $form->addCheckBox(
2333
                    'prevent_backwards',
2334
                    null,
2335
                    get_lang('QuizPreventBackwards')
2336
                );
2337
            }
2338
2339
            $form->addElement(
2340
                'text',
2341
                'pass_percentage',
2342
                [get_lang('Pass percentage'), null, '%'],
2343
                ['id' => 'pass_percentage']
2344
            );
2345
2346
            $form->addRule('pass_percentage', get_lang('Numericalal'), 'numeric');
2347
            $form->addRule('pass_percentage', get_lang('Value is too small.'), 'min_numeric_length', 0);
2348
            $form->addRule('pass_percentage', get_lang('Value is too big.'), 'max_numeric_length', 100);
2349
2350
            // add the text_when_finished textbox
2351
            $form->addHtmlEditor(
2352
                'text_when_finished',
2353
                get_lang('Text appearing at the end of the test'),
2354
                false,
2355
                false,
2356
                $editor_config
2357
            );
2358
2359
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
2360
            if (true === $allow) {
2361
                $settings = ExerciseLib::getNotificationSettings();
2362
                $group = [];
2363
                foreach ($settings as $itemId => $label) {
2364
                    $group[] = $form->createElement(
2365
                        'checkbox',
2366
                        'notifications[]',
2367
                        null,
2368
                        $label,
2369
                        ['value' => $itemId]
2370
                    );
2371
                }
2372
                $form->addGroup($group, '', [get_lang('E-mail notifications')]);
2373
            }
2374
2375
            $form->addCheckBox(
2376
                'update_title_in_lps',
2377
                null,
2378
                get_lang('Update this title in learning paths')
2379
            );
2380
2381
            $defaults = [];
2382
            if ('true' === api_get_setting('search_enabled')) {
2383
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2384
                $form->addElement('checkbox', 'index_document', '', get_lang('Index document text?'));
2385
                $form->addSelectLanguage('language', get_lang('Document language for indexation'));
2386
                $specific_fields = get_specific_field_list();
2387
2388
                foreach ($specific_fields as $specific_field) {
2389
                    $form->addElement('text', $specific_field['code'], $specific_field['name']);
2390
                    $filter = [
2391
                        'c_id' => api_get_course_int_id(),
2392
                        'field_id' => $specific_field['id'],
2393
                        'ref_id' => $this->id,
2394
                        'tool_id' => "'".TOOL_QUIZ."'",
2395
                    ];
2396
                    $values = get_specific_field_values_list($filter, ['value']);
2397
                    if (!empty($values)) {
2398
                        $arr_str_values = [];
2399
                        foreach ($values as $value) {
2400
                            $arr_str_values[] = $value['value'];
2401
                        }
2402
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2403
                    }
2404
                }
2405
            }
2406
2407
            $skillList = Skill::addSkillsToForm($form, ITEM_TYPE_EXERCISE, $this->iId);
2408
2409
            $extraField = new ExtraField('exercise');
2410
            $extraField->addElements(
2411
                $form,
2412
                $this->iId,
2413
                [], //exclude
2414
                false, // filter
2415
                false, // tag as select
2416
                [], //show only fields
2417
                [], // order fields
2418
                [] // extra data
2419
            );
2420
            $form->addElement('html', '</div>'); //End advanced setting
2421
            $form->addElement('html', '</div>');
2422
        }
2423
2424
        // submit
2425
        if (isset($_GET['id'])) {
2426
            $form->addButtonSave(get_lang('Edit test name and settings'), 'submitExercise');
2427
        } else {
2428
            $form->addButtonUpdate(get_lang('Proceed to questions'), 'submitExercise');
2429
        }
2430
2431
        $form->addRule('exerciseTitle', get_lang('Name'), 'required');
2432
2433
        // defaults
2434
        if ('full' == $type) {
2435
            // rules
2436
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2437
            $form->addRule('start_time', get_lang('Invalid date'), 'datetime');
2438
            $form->addRule('end_time', get_lang('Invalid date'), 'datetime');
2439
2440
            if ($this->id > 0) {
2441
                $defaults['randomQuestions'] = $this->random;
2442
                $defaults['randomAnswers'] = $this->getRandomAnswers();
2443
                $defaults['exerciseType'] = $this->selectType();
2444
                $defaults['exerciseTitle'] = $this->get_formated_title();
2445
                $defaults['exerciseDescription'] = $this->selectDescription();
2446
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2447
                $defaults['exerciseFeedbackType'] = $this->getFeedbackType();
2448
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2449
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2450
                $defaults['save_correct_answers'] = $this->getSaveCorrectAnswers();
2451
                $defaults['review_answers'] = $this->review_answers;
2452
                $defaults['randomByCat'] = $this->getRandomByCategory();
2453
                $defaults['text_when_finished'] = $this->getTextWhenFinished();
2454
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2455
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2456
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2457
                $defaults['hide_question_title'] = $this->getHideQuestionTitle();
2458
                $defaults['show_previous_button'] = $this->showPreviousButton();
2459
                $defaults['exercise_category_id'] = $this->getExerciseCategoryId();
2460
                $defaults['prevent_backwards'] = $this->getPreventBackwards();
2461
2462
                if (!empty($this->start_time)) {
2463
                    $defaults['activate_start_date_check'] = 1;
2464
                }
2465
                if (!empty($this->end_time)) {
2466
                    $defaults['activate_end_date_check'] = 1;
2467
                }
2468
2469
                $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2470
                $defaults['end_time'] = !empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time() + 84600);
2471
2472
                // Get expired time
2473
                if ('0' != $this->expired_time) {
2474
                    $defaults['enabletimercontrol'] = 1;
2475
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2476
                } else {
2477
                    $defaults['enabletimercontroltotalminutes'] = 0;
2478
                }
2479
                $defaults['skills'] = array_keys($skillList);
2480
                $defaults['notifications'] = $this->getNotifications();
2481
            } else {
2482
                $defaults['exerciseType'] = 2;
2483
                $defaults['exerciseAttempts'] = 0;
2484
                $defaults['randomQuestions'] = 0;
2485
                $defaults['randomAnswers'] = 0;
2486
                $defaults['exerciseDescription'] = '';
2487
                $defaults['exerciseFeedbackType'] = 0;
2488
                $defaults['results_disabled'] = 0;
2489
                $defaults['randomByCat'] = 0;
2490
                $defaults['text_when_finished'] = '';
2491
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2492
                $defaults['display_category_name'] = 1;
2493
                $defaults['end_time'] = date('Y-m-d 12:00:00', time() + 84600);
2494
                $defaults['pass_percentage'] = '';
2495
                $defaults['end_button'] = $this->selectEndButton();
2496
                $defaults['question_selection_type'] = 1;
2497
                $defaults['hide_question_title'] = 0;
2498
                $defaults['show_previous_button'] = 1;
2499
                $defaults['on_success_message'] = null;
2500
                $defaults['on_failed_message'] = null;
2501
            }
2502
        } else {
2503
            $defaults['exerciseTitle'] = $this->selectTitle();
2504
            $defaults['exerciseDescription'] = $this->selectDescription();
2505
        }
2506
2507
        if ('true' === api_get_setting('search_enabled')) {
2508
            $defaults['index_document'] = 'checked="checked"';
2509
        }
2510
2511
        $this->setPageResultConfigurationDefaults($defaults);
2512
        $form->setDefaults($defaults);
2513
2514
        // Freeze some elements.
2515
        if (0 != $this->id && false == $this->edit_exercise_in_lp) {
2516
            $elementsToFreeze = [
2517
                'randomQuestions',
2518
                //'randomByCat',
2519
                'exerciseAttempts',
2520
                'propagate_neg',
2521
                'enabletimercontrol',
2522
                'review_answers',
2523
            ];
2524
2525
            foreach ($elementsToFreeze as $elementName) {
2526
                /** @var HTML_QuickForm_element $element */
2527
                $element = $form->getElement($elementName);
2528
                $element->freeze();
2529
            }
2530
        }
2531
    }
2532
2533
    public function setResultFeedbackGroup(FormValidator $form, $checkFreeze = true)
2534
    {
2535
        // Feedback type.
2536
        $feedback = [];
2537
        $feedback[] = $form->createElement(
2538
            'radio',
2539
            'exerciseFeedbackType',
2540
            null,
2541
            get_lang('At end of test'),
2542
            EXERCISE_FEEDBACK_TYPE_END,
2543
            [
2544
                'id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_END,
2545
                'onclick' => 'check_feedback()',
2546
            ]
2547
        );
2548
2549
        $noFeedBack = $form->createElement(
2550
            'radio',
2551
            'exerciseFeedbackType',
2552
            null,
2553
            get_lang('NoFeedback'),
2554
            EXERCISE_FEEDBACK_TYPE_EXAM,
2555
            [
2556
                'id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_EXAM,
2557
            ]
2558
        );
2559
2560
        $freeze = true;
2561
        if ('true' === api_get_setting('enable_quiz_scenario')) {
2562
            if (0 === $this->getQuestionCount()) {
2563
                $freeze = false;
2564
            } else {
2565
                $hasDifferentQuestion = $this->hasQuestionWithTypeNotInList([UNIQUE_ANSWER, HOT_SPOT_DELINEATION]);
2566
                if (false === $hasDifferentQuestion) {
2567
                    $freeze = false;
2568
                }
2569
            }
2570
        }
2571
            // Can't convert a question from one feedback to another
2572
        $direct = $form->createElement(
2573
                    'radio',
2574
                    'exerciseFeedbackType',
2575
                    null,
2576
            get_lang('DirectFeedback'),
2577
                    EXERCISE_FEEDBACK_TYPE_DIRECT,
2578
                    [
2579
                        'id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_DIRECT,
2580
                        'onclick' => 'check_direct_feedback()',
2581
                    ]
2582
                );
2583
2584
        if ($freeze) {
2585
            $direct->freeze();
2586
            $noFeedBack->freeze();
2587
            }
2588
2589
        $feedback[] = $noFeedBack;
2590
        $feedback[] = $direct;
2591
2592
        $feedback[] = $form->createElement(
2593
            'radio',
2594
            'exerciseFeedbackType',
2595
            null,
2596
            get_lang('ExerciseDirectPopUp'),
2597
            EXERCISE_FEEDBACK_TYPE_POPUP,
2598
            ['id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_POPUP, 'onclick' => 'check_direct_feedback()']
2599
        );
2600
2601
        $group = $form->addGroup(
2602
            $feedback,
2603
            null,
2604
            [
2605
                get_lang('FeedbackType'),
2606
                get_lang('FeedbackDisplayOptions'),
2607
            ]
2608
        );
2609
2610
        if ($freeze) {
2611
            //$group->freeze();
2612
        }
2613
    }
2614
2615
    /**
2616
     * function which process the creation of exercises.
2617
     *
2618
     * @param FormValidator $form
2619
     *
2620
     * @return int c_quiz.iid
2621
     */
2622
    public function processCreation($form)
2623
    {
2624
        $this->updateTitle(self::format_title_variable($form->getSubmitValue('exerciseTitle')));
2625
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2626
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2627
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2628
        $this->updateType($form->getSubmitValue('exerciseType'));
2629
2630
        // If direct feedback then force to One per page
2631
        if (EXERCISE_FEEDBACK_TYPE_DIRECT == $form->getSubmitValue('exerciseFeedbackType')) {
2632
            $this->updateType(ONE_PER_PAGE);
2633
        }
2634
2635
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2636
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2637
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2638
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2639
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2640
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2641
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2642
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2643
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2644
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2645
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2646
        $this->updateCategories($form->getSubmitValue('category'));
2647
        $this->updateEndButton($form->getSubmitValue('end_button'));
2648
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2649
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2650
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2651
        $this->setEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2652
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2653
        $this->setModelType($form->getSubmitValue('model_type'));
2654
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2655
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2656
        $this->sessionId = api_get_session_id();
2657
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2658
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2659
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2660
        $this->setShowPreviousButton($form->getSubmitValue('show_previous_button'));
2661
        $this->setNotifications($form->getSubmitValue('notifications'));
2662
        $this->setExerciseCategoryId($form->getSubmitValue('exercise_category_id'));
2663
        $this->setPageResultConfiguration($form->getSubmitValues());
2664
        $this->preventBackwards = (int) $form->getSubmitValue('prevent_backwards');
2665
2666
        $this->start_time = null;
2667
        if (1 == $form->getSubmitValue('activate_start_date_check')) {
2668
            $start_time = $form->getSubmitValue('start_time');
2669
            $this->start_time = api_get_utc_datetime($start_time);
2670
        }
2671
2672
        $this->end_time = null;
2673
        if (1 == $form->getSubmitValue('activate_end_date_check')) {
2674
            $end_time = $form->getSubmitValue('end_time');
2675
            $this->end_time = api_get_utc_datetime($end_time);
2676
        }
2677
2678
        $this->expired_time = 0;
2679
        if (1 == $form->getSubmitValue('enabletimercontrol')) {
2680
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2681
            if (0 == $this->expired_time) {
2682
                $this->expired_time = $expired_total_time;
2683
            }
2684
        }
2685
2686
        $this->random_answers = 0;
2687
        if (1 == $form->getSubmitValue('randomAnswers')) {
2688
            $this->random_answers = 1;
2689
        }
2690
2691
        // Update title in all LPs that have this quiz added
2692
        if (1 == $form->getSubmitValue('update_title_in_lps')) {
2693
            $courseId = api_get_course_int_id();
2694
            $table = Database::get_course_table(TABLE_LP_ITEM);
2695
            $sql = "SELECT * FROM $table
2696
                    WHERE
2697
                        c_id = $courseId AND
2698
                        item_type = 'quiz' AND
2699
                        path = '".$this->id."'
2700
                    ";
2701
            $result = Database::query($sql);
2702
            $items = Database::store_result($result);
2703
            if (!empty($items)) {
2704
                foreach ($items as $item) {
2705
                    $itemId = $item['iid'];
2706
                    $sql = "UPDATE $table SET title = '".$this->title."'
2707
                            WHERE iid = $itemId AND c_id = $courseId ";
2708
                    Database::query($sql);
2709
                }
2710
            }
2711
        }
2712
2713
        $iId = $this->save();
2714
        if (!empty($iId)) {
2715
            $values = $form->getSubmitValues();
2716
            $values['item_id'] = $iId;
2717
            $extraFieldValue = new ExtraFieldValue('exercise');
2718
            $extraFieldValue->saveFieldValues($values);
2719
2720
            Skill::saveSkills($form, ITEM_TYPE_EXERCISE, $iId);
2721
        }
2722
    }
2723
2724
    public function search_engine_save()
2725
    {
2726
        if (1 != $_POST['index_document']) {
2727
            return;
2728
        }
2729
        $course_id = api_get_course_id();
2730
2731
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2732
2733
        $specific_fields = get_specific_field_list();
2734
        $ic_slide = new IndexableChunk();
2735
2736
        $all_specific_terms = '';
2737
        foreach ($specific_fields as $specific_field) {
2738
            if (isset($_REQUEST[$specific_field['code']])) {
2739
                $sterms = trim($_REQUEST[$specific_field['code']]);
2740
                if (!empty($sterms)) {
2741
                    $all_specific_terms .= ' '.$sterms;
2742
                    $sterms = explode(',', $sterms);
2743
                    foreach ($sterms as $sterm) {
2744
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2745
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2746
                    }
2747
                }
2748
            }
2749
        }
2750
2751
        // build the chunk to index
2752
        $ic_slide->addValue('title', $this->exercise);
2753
        $ic_slide->addCourseId($course_id);
2754
        $ic_slide->addToolId(TOOL_QUIZ);
2755
        $xapian_data = [
2756
            SE_COURSE_ID => $course_id,
2757
            SE_TOOL_ID => TOOL_QUIZ,
2758
            SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2759
            SE_USER => (int) api_get_user_id(),
2760
        ];
2761
        $ic_slide->xapian_data = serialize($xapian_data);
2762
        $exercise_description = $all_specific_terms.' '.$this->description;
2763
        $ic_slide->addValue('content', $exercise_description);
2764
2765
        $di = new ChamiloIndexer();
2766
        isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2767
        $di->connectDb(null, null, $lang);
2768
        $di->addChunk($ic_slide);
2769
2770
        //index and return search engine document id
2771
        $did = $di->index();
2772
        if ($did) {
2773
            // save it to db
2774
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2775
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2776
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2777
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2778
            Database::query($sql);
2779
        }
2780
    }
2781
2782
    public function search_engine_edit()
2783
    {
2784
        // update search enchine and its values table if enabled
2785
        if ('true' == api_get_setting('search_enabled') && extension_loaded('xapian')) {
2786
            $course_id = api_get_course_id();
2787
2788
            // actually, it consists on delete terms from db,
2789
            // insert new ones, create a new search engine document, and remove the old one
2790
            // get search_did
2791
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2792
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2793
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2794
            $res = Database::query($sql);
2795
2796
            if (Database::num_rows($res) > 0) {
2797
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2798
2799
                $se_ref = Database::fetch_array($res);
2800
                $specific_fields = get_specific_field_list();
2801
                $ic_slide = new IndexableChunk();
2802
2803
                $all_specific_terms = '';
2804
                foreach ($specific_fields as $specific_field) {
2805
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2806
                    if (isset($_REQUEST[$specific_field['code']])) {
2807
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2808
                        $all_specific_terms .= ' '.$sterms;
2809
                        $sterms = explode(',', $sterms);
2810
                        foreach ($sterms as $sterm) {
2811
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2812
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2813
                        }
2814
                    }
2815
                }
2816
2817
                // build the chunk to index
2818
                $ic_slide->addValue('title', $this->exercise);
2819
                $ic_slide->addCourseId($course_id);
2820
                $ic_slide->addToolId(TOOL_QUIZ);
2821
                $xapian_data = [
2822
                    SE_COURSE_ID => $course_id,
2823
                    SE_TOOL_ID => TOOL_QUIZ,
2824
                    SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2825
                    SE_USER => (int) api_get_user_id(),
2826
                ];
2827
                $ic_slide->xapian_data = serialize($xapian_data);
2828
                $exercise_description = $all_specific_terms.' '.$this->description;
2829
                $ic_slide->addValue('content', $exercise_description);
2830
2831
                $di = new ChamiloIndexer();
2832
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2833
                $di->connectDb(null, null, $lang);
2834
                $di->remove_document($se_ref['search_did']);
2835
                $di->addChunk($ic_slide);
2836
2837
                //index and return search engine document id
2838
                $did = $di->index();
2839
                if ($did) {
2840
                    // save it to db
2841
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2842
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2843
                    Database::query($sql);
2844
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2845
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2846
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2847
                    Database::query($sql);
2848
                }
2849
            } else {
2850
                $this->search_engine_save();
2851
            }
2852
        }
2853
    }
2854
2855
    public function search_engine_delete()
2856
    {
2857
        // remove from search engine if enabled
2858
        if ('true' == api_get_setting('search_enabled') && extension_loaded('xapian')) {
2859
            $course_id = api_get_course_id();
2860
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2861
            $sql = 'SELECT * FROM %s
2862
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL
2863
                    LIMIT 1';
2864
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2865
            $res = Database::query($sql);
2866
            if (Database::num_rows($res) > 0) {
2867
                $row = Database::fetch_array($res);
2868
                $di = new ChamiloIndexer();
2869
                $di->remove_document($row['search_did']);
2870
                unset($di);
2871
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2872
                foreach ($this->questionList as $question_i) {
2873
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2874
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2875
                    $qres = Database::query($sql);
2876
                    if (Database::num_rows($qres) > 0) {
2877
                        $qrow = Database::fetch_array($qres);
2878
                        $objQuestion = Question::getInstance($qrow['type']);
2879
                        $objQuestion = Question::read((int) $question_i);
2880
                        $objQuestion->search_engine_edit($this->id, false, true);
2881
                        unset($objQuestion);
2882
                    }
2883
                }
2884
            }
2885
            $sql = 'DELETE FROM %s
2886
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL
2887
                    LIMIT 1';
2888
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2889
            Database::query($sql);
2890
2891
            // remove terms from db
2892
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2893
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2894
        }
2895
    }
2896
2897
    public function selectExpiredTime()
2898
    {
2899
        return $this->expired_time;
2900
    }
2901
2902
    /**
2903
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2904
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2905
     * Works with exercises in sessions.
2906
     *
2907
     * @param bool   $cleanLpTests
2908
     * @param string $cleanResultBeforeDate
2909
     *
2910
     * @return int quantity of user's exercises deleted
2911
     */
2912
    public function cleanResults($cleanLpTests = false, $cleanResultBeforeDate = null)
2913
    {
2914
        $sessionId = api_get_session_id();
2915
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2916
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2917
2918
        $sql_where = '  AND
2919
                        orig_lp_id = 0 AND
2920
                        orig_lp_item_id = 0';
2921
2922
        // if we want to delete results from LP too
2923
        if ($cleanLpTests) {
2924
            $sql_where = '';
2925
        }
2926
2927
        // if we want to delete attempts before date $cleanResultBeforeDate
2928
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2929
2930
        if (!empty($cleanResultBeforeDate)) {
2931
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2932
            if (api_is_valid_date($cleanResultBeforeDate)) {
2933
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2934
            } else {
2935
                return 0;
2936
            }
2937
        }
2938
2939
        $sql = "SELECT exe_id
2940
            FROM $table_track_e_exercises
2941
            WHERE
2942
                c_id = ".api_get_course_int_id().' AND
2943
                exe_exo_id = '.$this->id.' AND
2944
                session_id = '.$sessionId.' '.
2945
                $sql_where;
2946
2947
        $result = Database::query($sql);
2948
        $exe_list = Database::store_result($result);
2949
2950
        // deleting TRACK_E_ATTEMPT table
2951
        // check if exe in learning path or not
2952
        $i = 0;
2953
        if (is_array($exe_list) && count($exe_list) > 0) {
2954
            foreach ($exe_list as $item) {
2955
                $sql = "DELETE FROM $table_track_e_attempt
2956
                        WHERE exe_id = '".$item['exe_id']."'";
2957
                Database::query($sql);
2958
                $i++;
2959
            }
2960
        }
2961
2962
        // delete TRACK_E_EXERCISES table
2963
        $sql = "DELETE FROM $table_track_e_exercises
2964
                WHERE
2965
                  c_id = ".api_get_course_int_id().' AND
2966
                  exe_exo_id = '.$this->id." $sql_where AND
2967
                  session_id = ".$sessionId;
2968
        Database::query($sql);
2969
2970
        $this->generateStats($this->id, api_get_course_info(), $sessionId);
2971
2972
        Event::addEvent(
2973
            LOG_EXERCISE_RESULT_DELETE,
2974
            LOG_EXERCISE_ID,
2975
            $this->id,
2976
            null,
2977
            null,
2978
            api_get_course_int_id(),
2979
            $sessionId
2980
        );
2981
2982
        return $i;
2983
    }
2984
2985
    /**
2986
     * Copies an exercise (duplicate all questions and answers).
2987
     */
2988
    public function copyExercise()
2989
    {
2990
        $exerciseObject = $this;
2991
        $categories = $exerciseObject->getCategoriesInExercise();
2992
        // Get all questions no matter the order/category settings
2993
        $questionList = $exerciseObject->getQuestionOrderedList();
2994
        // Force the creation of a new exercise
2995
        $exerciseObject->updateTitle($exerciseObject->selectTitle().' - '.get_lang('Copy'));
2996
        // Hides the new exercise
2997
        $exerciseObject->updateStatus(false);
2998
        $exerciseObject->updateId(0);
2999
        $exerciseObject->sessionId = api_get_session_id();
3000
        $exerciseObject->save();
3001
        $newId = $exerciseObject->selectId();
3002
        if ($newId && !empty($questionList)) {
3003
            // Question creation
3004
            foreach ($questionList as $oldQuestionId) {
3005
                $oldQuestionObj = Question::read($oldQuestionId);
3006
                $newQuestionId = $oldQuestionObj->duplicate();
3007
                if ($newQuestionId) {
3008
                    $newQuestionObj = Question::read($newQuestionId);
3009
                    if (isset($newQuestionObj) && $newQuestionObj) {
3010
                        $newQuestionObj->addToList($newId);
3011
                        if (!empty($oldQuestionObj->category)) {
3012
                            $newQuestionObj->saveCategory($oldQuestionObj->category);
3013
                        }
3014
3015
                        // This should be moved to the duplicate function
3016
                        $newAnswerObj = new Answer($oldQuestionId);
3017
                        $newAnswerObj->read();
3018
                        $newAnswerObj->duplicate($newQuestionObj);
3019
                    }
3020
                }
3021
            }
3022
            if (!empty($categories)) {
3023
                $newCategoryList = [];
3024
                foreach ($categories as $category) {
3025
                    $newCategoryList[$category['category_id']] = $category['count_questions'];
3026
                }
3027
                $exerciseObject->save_categories_in_exercise($newCategoryList);
3028
            }
3029
        }
3030
    }
3031
3032
    /**
3033
     * Changes the exercise status.
3034
     *
3035
     * @param string $status - exercise status
3036
     */
3037
    public function updateStatus($status)
3038
    {
3039
        $this->active = $status;
3040
    }
3041
3042
    /**
3043
     * @param int    $lp_id
3044
     * @param int    $lp_item_id
3045
     * @param int    $lp_item_view_id
3046
     * @param string $status
3047
     *
3048
     * @return array
3049
     */
3050
    public function get_stat_track_exercise_info(
3051
        $lp_id = 0,
3052
        $lp_item_id = 0,
3053
        $lp_item_view_id = 0,
3054
        $status = 'incomplete'
3055
    ) {
3056
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3057
        if (empty($lp_id)) {
3058
            $lp_id = 0;
3059
        }
3060
        if (empty($lp_item_id)) {
3061
            $lp_item_id = 0;
3062
        }
3063
        if (empty($lp_item_view_id)) {
3064
            $lp_item_view_id = 0;
3065
        }
3066
        $condition = ' WHERE exe_exo_id 	= '."'".$this->id."'".' AND
3067
					   exe_user_id 			= '."'".api_get_user_id()."'".' AND
3068
					   c_id                 = '.api_get_course_int_id().' AND
3069
					   status 				= '."'".Database::escape_string($status)."'".' AND
3070
					   orig_lp_id 			= '."'".$lp_id."'".' AND
3071
					   orig_lp_item_id 		= '."'".$lp_item_id."'".' AND
3072
                       orig_lp_item_view_id = '."'".$lp_item_view_id."'".' AND
3073
					   session_id 			= '."'".api_get_session_id()."' LIMIT 1"; //Adding limit 1 just in case
3074
3075
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
3076
3077
        $result = Database::query($sql_track);
3078
        $new_array = [];
3079
        if (Database::num_rows($result) > 0) {
3080
            $new_array = Database::fetch_array($result, 'ASSOC');
3081
            $new_array['num_exe'] = Database::num_rows($result);
3082
        }
3083
3084
        return $new_array;
3085
    }
3086
3087
    /**
3088
     * Saves a test attempt.
3089
     *
3090
     * @param int   $clock_expired_time   clock_expired_time
3091
     * @param int   $safe_lp_id           int lp id
3092
     * @param int   $safe_lp_item_id      int lp item id
3093
     * @param int   $safe_lp_item_view_id int lp item_view id
3094
     * @param array $questionList
3095
     * @param float $weight
3096
     *
3097
     * @return int
3098
     */
3099
    public function save_stat_track_exercise_info(
3100
        $clock_expired_time = 0,
3101
        $safe_lp_id = 0,
3102
        $safe_lp_item_id = 0,
3103
        $safe_lp_item_view_id = 0,
3104
        $questionList = [],
3105
        $weight = 0
3106
    ) {
3107
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3108
        $safe_lp_id = (int) $safe_lp_id;
3109
        $safe_lp_item_id = (int) $safe_lp_item_id;
3110
        $safe_lp_item_view_id = (int) $safe_lp_item_view_id;
3111
3112
        if (empty($clock_expired_time)) {
3113
            $clock_expired_time = null;
3114
        }
3115
3116
        $questionList = array_map('intval', $questionList);
3117
3118
        $params = [
3119
            'exe_exo_id' => $this->id,
3120
            'exe_user_id' => api_get_user_id(),
3121
            'c_id' => api_get_course_int_id(),
3122
            'status' => 'incomplete',
3123
            'session_id' => api_get_session_id(),
3124
            'data_tracking' => implode(',', $questionList),
3125
            'start_date' => api_get_utc_datetime(),
3126
            'orig_lp_id' => $safe_lp_id,
3127
            'orig_lp_item_id' => $safe_lp_item_id,
3128
            'orig_lp_item_view_id' => $safe_lp_item_view_id,
3129
            'max_score' => $weight,
3130
            'user_ip' => Database::escape_string(api_get_real_ip()),
3131
            'exe_date' => api_get_utc_datetime(),
3132
            'score' => 0,
3133
            'steps_counter' => 0,
3134
            'exe_duration' => 0,
3135
            'expired_time_control' => $clock_expired_time,
3136
            'questions_to_check' => '',
3137
        ];
3138
3139
        return Database::insert($track_exercises, $params);
3140
    }
3141
3142
    /**
3143
     * @param int    $question_id
3144
     * @param int    $questionNum
3145
     * @param array  $questions_in_media
3146
     * @param string $currentAnswer
3147
     * @param array  $myRemindList
3148
     *
3149
     * @return string
3150
     */
3151
    public function show_button(
3152
        $question_id,
3153
        $questionNum,
3154
        $questions_in_media = [],
3155
        $currentAnswer = '',
3156
        $myRemindList = []
3157
    ) {
3158
        global $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
3159
        $nbrQuestions = $this->getQuestionCount();
3160
        $buttonList = [];
3161
        $html = $label = '';
3162
        $hotspotGet = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']) : null;
3163
3164
        if (in_array($this->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]) &&
3165
            ONE_PER_PAGE == $this->type
3166
        ) {
3167
            $urlTitle = get_lang('Proceed with the test');
3168
            if ($questionNum == count($this->questionList)) {
3169
                $urlTitle = get_lang('End test');
3170
            }
3171
3172
            $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit_modal.php?'.api_get_cidreq();
3173
            $url .= '&'.http_build_query([
3174
                'learnpath_id' => $safe_lp_id,
3175
                'learnpath_item_id' => $safe_lp_item_id,
3176
                'learnpath_item_view_id' => $safe_lp_item_view_id,
3177
                'hotspot' => $hotspotGet,
3178
                'nbrQuestions' => $nbrQuestions,
3179
                'num' => $questionNum,
3180
                'exerciseType' => $this->type,
3181
                'exerciseId' => $this->id,
3182
                'reminder' => empty($myRemindList) ? null : 2,
3183
            ]);
3184
3185
            $params = [
3186
                'class' => 'ajax btn btn-default no-close-button',
3187
                'data-title' => Security::remove_XSS(get_lang('Comment')),
3188
                'data-size' => 'md',
3189
                'id' => "button_$question_id",
3190
            ];
3191
3192
            if (EXERCISE_FEEDBACK_TYPE_POPUP === $this->getFeedbackType()) {
3193
                //$params['data-block-div-after-closing'] = "question_div_$question_id";
3194
                $params['data-block-closing'] = 'true';
3195
                $params['class'] .= ' no-header ';
3196
            }
3197
3198
            $html .= Display::url($urlTitle, $url, $params);
3199
            $html .= '<br />';
3200
            // User
3201
            return $html;
3202
        }
3203
3204
        if (!api_is_allowed_to_session_edit()) {
3205
            return '';
3206
        }
3207
3208
        $isReviewingAnswers = isset($_REQUEST['reminder']) && 2 == $_REQUEST['reminder'];
3209
3210
        // User
3211
                $endReminderValue = false;
3212
        if (!empty($myRemindList) && $isReviewingAnswers) {
3213
                    $endValue = end($myRemindList);
3214
                    if ($endValue == $question_id) {
3215
                        $endReminderValue = true;
3216
                    }
3217
                }
3218
        if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum || $endReminderValue) {
3219
                    if ($this->review_answers) {
3220
                $label = get_lang('ReviewQuestions');
3221
                        $class = 'btn btn-success';
3222
                    } else {
3223
                $label = get_lang('EndTest');
3224
                        $class = 'btn btn-warning';
3225
                    }
3226
                } else {
3227
            $label = get_lang('NextQuestion');
3228
                    $class = 'btn btn-primary';
3229
                }
3230
                // used to select it with jquery
3231
                $class .= ' question-validate-btn';
3232
        if ($this->type == ONE_PER_PAGE) {
3233
            if ($questionNum != 1 && $this->showPreviousButton()) {
3234
                            $prev_question = $questionNum - 2;
3235
                            $showPreview = true;
3236
                if (!empty($myRemindList) && $isReviewingAnswers) {
3237
                                $beforeId = null;
3238
                                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...
3239
                                    if (isset($myRemindList[$i]) && $myRemindList[$i] == $question_id) {
3240
                                        $beforeId = isset($myRemindList[$i - 1]) ? $myRemindList[$i - 1] : null;
3241
3242
                                        break;
3243
                                    }
3244
                                }
3245
3246
                                if (empty($beforeId)) {
3247
                                    $showPreview = false;
3248
                                } else {
3249
                                    $num = 0;
3250
                                    foreach ($this->questionList as $originalQuestionId) {
3251
                                        if ($originalQuestionId == $beforeId) {
3252
                                            break;
3253
                                        }
3254
                                        $num++;
3255
                                    }
3256
                                    $prev_question = $num;
3257
                                }
3258
                            }
3259
3260
                            if ($showPreview && 0 === $this->getPreventBackwards()) {
3261
                                $buttonList[] = Display::button(
3262
                                    'previous_question_and_save',
3263
                                    get_lang('Previous question'),
3264
                                    [
3265
                                        'type' => 'button',
3266
                                        'class' => 'btn btn-default',
3267
                                        'data-prev' => $prev_question,
3268
                                        'data-question' => $question_id,
3269
                                    ]
3270
                                );
3271
                            }
3272
                        }
3273
3274
                    // Next question
3275
                    if (!empty($questions_in_media)) {
3276
                        $buttonList[] = Display::button(
3277
                            'save_question_list',
3278
                            $label,
3279
                            [
3280
                                'type' => 'button',
3281
                                'class' => $class,
3282
                                'data-list' => implode(',', $questions_in_media),
3283
                            ]
3284
                        );
3285
                    } else {
3286
                        $buttonList[] = Display::button(
3287
                            'save_now',
3288
                            $label,
3289
                            ['type' => 'button', 'class' => $class, 'data-question' => $question_id]
3290
                        );
3291
                    }
3292
            $buttonList[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>';
3293
3294
            $html .= implode(PHP_EOL, $buttonList).PHP_EOL;
3295
3296
            return $html;
3297
        }
3298
3299
        if ($this->review_answers) {
3300
            $all_label = get_lang('Review selected questions');
3301
            $class = 'btn btn-success';
3302
        } else {
3303
            $all_label = get_lang('End test');
3304
            $class = 'btn btn-warning';
3305
        }
3306
        // used to select it with jquery
3307
        $class .= ' question-validate-btn';
3308
        $buttonList[] = Display::button(
3309
            'validate_all',
3310
            $all_label,
3311
            ['type' => 'button', 'class' => $class]
3312
        );
3313
        $buttonList[] = Display::span(null, ['id' => 'save_all_response']);
3314
        $html .= implode(PHP_EOL, $buttonList).PHP_EOL;
3315
3316
        return $html;
3317
    }
3318
3319
    /**
3320
     * @param int    $timeLeft in seconds
3321
     * @param string $url
3322
     *
3323
     * @return string
3324
     */
3325
    public function showSimpleTimeControl($timeLeft, $url = '')
3326
    {
3327
        $timeLeft = (int) $timeLeft;
3328
3329
        return "<script>
3330
            function openClockWarning() {
3331
                $('#clock_warning').dialog({
3332
                    modal:true,
3333
                    height:320,
3334
                    width:550,
3335
                    closeOnEscape: false,
3336
                    resizable: false,
3337
                    buttons: {
3338
                        '".addslashes(get_lang('Close'))."': function() {
3339
                            $('#clock_warning').dialog('close');
3340
                        }
3341
                    },
3342
                    close: function() {
3343
                        window.location.href = '$url';
3344
                    }
3345
                });
3346
                $('#clock_warning').dialog('open');
3347
                $('#counter_to_redirect').epiclock({
3348
                    mode: $.epiclock.modes.countdown,
3349
                    offset: {seconds: 5},
3350
                    format: 's'
3351
                }).bind('timer', function () {
3352
                    window.location.href = '$url';
3353
                });
3354
            }
3355
3356
            function onExpiredTimeExercise() {
3357
                $('#wrapper-clock').hide();
3358
                $('#expired-message-id').show();
3359
                // Fixes bug #5263
3360
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3361
                openClockWarning();
3362
            }
3363
3364
			$(function() {
3365
				// time in seconds when using minutes there are some seconds lost
3366
                var time_left = parseInt(".$timeLeft.");
3367
                $('#exercise_clock_warning').epiclock({
3368
                    mode: $.epiclock.modes.countdown,
3369
                    offset: {seconds: time_left},
3370
                    format: 'x:i:s',
3371
                    renderer: 'minute'
3372
                }).bind('timer', function () {
3373
                    onExpiredTimeExercise();
3374
                });
3375
	       		$('#submit_save').click(function () {});
3376
	        });
3377
	    </script>";
3378
    }
3379
3380
    /**
3381
     * So the time control will work.
3382
     *
3383
     * @param int $timeLeft
3384
     *
3385
     * @return string
3386
     */
3387
    public function showTimeControlJS($timeLeft)
3388
    {
3389
        $timeLeft = (int) $timeLeft;
3390
        $script = 'redirectExerciseToResult();';
3391
        if (ALL_ON_ONE_PAGE == $this->type) {
3392
            $script = "save_now_all('validate');";
3393
        } elseif (ONE_PER_PAGE == $this->type) {
3394
            $script = 'window.quizTimeEnding = true;
3395
                $(\'[name="save_now"]\').trigger(\'click\');';
3396
        }
3397
3398
        return "<script>
3399
            function openClockWarning() {
3400
                $('#clock_warning').dialog({
3401
                    modal:true,
3402
                    height:320,
3403
                    width:550,
3404
                    closeOnEscape: false,
3405
                    resizable: false,
3406
                    buttons: {
3407
                        '".addslashes(get_lang('End test'))."': function() {
3408
                            $('#clock_warning').dialog('close');
3409
                        }
3410
                    },
3411
                    close: function() {
3412
                        send_form();
3413
                    }
3414
                });
3415
3416
                $('#clock_warning').dialog('open');
3417
                $('#counter_to_redirect').epiclock({
3418
                    mode: $.epiclock.modes.countdown,
3419
                    offset: {seconds: 5},
3420
                    format: 's'
3421
                }).bind('timer', function () {
3422
                    send_form();
3423
                });
3424
            }
3425
3426
            function send_form() {
3427
                if ($('#exercise_form').length) {
3428
                    $script
3429
                } else {
3430
                    // In exercise_reminder.php
3431
                    final_submit();
3432
                }
3433
            }
3434
3435
            function onExpiredTimeExercise() {
3436
                $('#wrapper-clock').hide();
3437
                $('#expired-message-id').show();
3438
                // Fixes bug #5263
3439
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3440
                openClockWarning();
3441
            }
3442
3443
			$(function() {
3444
				// time in seconds when using minutes there are some seconds lost
3445
                var time_left = parseInt(".$timeLeft.");
3446
                $('#exercise_clock_warning').epiclock({
3447
                    mode: $.epiclock.modes.countdown,
3448
                    offset: {seconds: time_left},
3449
                    format: 'x:C:s',
3450
                    renderer: 'minute'
3451
                }).bind('timer', function () {
3452
                    onExpiredTimeExercise();
3453
                });
3454
	       		$('#submit_save').click(function () {});
3455
	        });
3456
	    </script>";
3457
    }
3458
3459
    /**
3460
     * This function was originally found in the exercise_show.php.
3461
     *
3462
     * @param int    $exeId
3463
     * @param int    $questionId
3464
     * @param mixed  $choice                                    the user-selected option
3465
     * @param string $from                                      function is called from 'exercise_show' or
3466
     *                                                          'exercise_result'
3467
     * @param array  $exerciseResultCoordinates                 the hotspot coordinates $hotspot[$question_id] =
3468
     *                                                          coordinates
3469
     * @param bool   $saved_results                             save results in the DB or just show the reponse
3470
     * @param bool   $from_database                             gets information from DB or from the current selection
3471
     * @param bool   $show_result                               show results or not
3472
     * @param int    $propagate_neg
3473
     * @param array  $hotspot_delineation_result
3474
     * @param bool   $showTotalScoreAndUserChoicesInLastAttempt
3475
     * @param bool   $updateResults
3476
     * @param bool   $showHotSpotDelineationTable
3477
     *
3478
     * @todo    reduce parameters of this function
3479
     *
3480
     * @return string html code
3481
     */
3482
    public function manage_answer(
3483
        $exeId,
3484
        $questionId,
3485
        $choice,
3486
        $from = 'exercise_show',
3487
        $exerciseResultCoordinates = [],
3488
        $save_results = true,
3489
        $from_database = false,
3490
        $show_result = true,
3491
        $propagate_neg = 0,
3492
        $hotspot_delineation_result = [],
3493
        $showTotalScoreAndUserChoicesInLastAttempt = true,
3494
        $updateResults = false,
3495
        $showHotSpotDelineationTable = false
3496
    ) {
3497
        $debug = false;
3498
        //needed in order to use in the exercise_attempt() for the time
3499
        global $learnpath_id, $learnpath_item_id;
3500
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3501
        $em = Database::getManager();
3502
        $feedback_type = $this->getFeedbackType();
3503
        $results_disabled = $this->selectResultsDisabled();
3504
3505
        if ($debug) {
3506
            error_log('<------ manage_answer ------> ');
3507
            error_log('exe_id: '.$exeId);
3508
            error_log('$from:  '.$from);
3509
            error_log('$saved_results: '.(int) $saved_results);
3510
            error_log('$from_database: '.(int) $from_database);
3511
            error_log('$show_result: '.(int) $show_result);
3512
            error_log('$propagate_neg: '.$propagate_neg);
3513
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3514
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3515
            error_log('$learnpath_id: '.$learnpath_id);
3516
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3517
            error_log('$choice: '.print_r($choice, 1));
3518
        }
3519
3520
        $final_overlap = 0;
3521
        $final_missing = 0;
3522
        $final_excess = 0;
3523
        $overlap_color = 0;
3524
        $missing_color = 0;
3525
        $excess_color = 0;
3526
        $threadhold1 = 0;
3527
        $threadhold2 = 0;
3528
        $threadhold3 = 0;
3529
        $arrques = null;
3530
        $arrans = null;
3531
        $studentChoice = null;
3532
        $expectedAnswer = '';
3533
        $calculatedChoice = '';
3534
        $calculatedStatus = '';
3535
        $questionId = (int) $questionId;
3536
        $exeId = (int) $exeId;
3537
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3538
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3539
        $studentChoiceDegree = null;
3540
3541
        // Creates a temporary Question object
3542
        $course_id = $this->course_id;
3543
        $objQuestionTmp = Question::read($questionId, $this->course);
3544
3545
        if (false === $objQuestionTmp) {
3546
            return false;
3547
        }
3548
3549
        $questionName = $objQuestionTmp->selectTitle();
3550
        $questionWeighting = $objQuestionTmp->selectWeighting();
3551
        $answerType = $objQuestionTmp->selectType();
3552
        $quesId = $objQuestionTmp->selectId();
3553
        $extra = $objQuestionTmp->extra;
3554
        $next = 1; //not for now
3555
        $totalWeighting = 0;
3556
        $totalScore = 0;
3557
3558
        // Extra information of the question
3559
        if ((
3560
            MULTIPLE_ANSWER_TRUE_FALSE == $answerType ||
3561
            MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $answerType
3562
            )
3563
            && !empty($extra)
3564
        ) {
3565
            $extra = explode(':', $extra);
3566
            if ($debug) {
3567
                error_log(print_r($extra, 1));
3568
            }
3569
            // Fixes problems with negatives values using intval
3570
            $true_score = (float) (trim($extra[0]));
3571
            $false_score = (float) (trim($extra[1]));
3572
            $doubt_score = (float) (trim($extra[2]));
3573
        }
3574
3575
        // Construction of the Answer object
3576
        $objAnswerTmp = new Answer($questionId, $course_id);
3577
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3578
3579
        if ($debug) {
3580
            error_log('Count of answers: '.$nbrAnswers);
3581
            error_log('$answerType: '.$answerType);
3582
        }
3583
3584
        if (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $answerType) {
3585
            $choiceTmp = $choice;
3586
            $choice = isset($choiceTmp['choice']) ? $choiceTmp['choice'] : '';
3587
            $choiceDegreeCertainty = isset($choiceTmp['choiceDegreeCertainty']) ? $choiceTmp['choiceDegreeCertainty'] : '';
3588
        }
3589
3590
        if (FREE_ANSWER == $answerType ||
3591
            ORAL_EXPRESSION == $answerType ||
3592
            CALCULATED_ANSWER == $answerType ||
3593
            ANNOTATION == $answerType
3594
        ) {
3595
            $nbrAnswers = 1;
3596
        }
3597
3598
        $generatedFile = '';
3599
        if (ORAL_EXPRESSION == $answerType) {
3600
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3601
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3602
3603
            $objQuestionTmp->initFile(
3604
                api_get_session_id(),
3605
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3606
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3607
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3608
            );
3609
3610
            // Probably this attempt came in an exercise all question by page
3611
            if (0 == $feedback_type) {
3612
                $objQuestionTmp->replaceWithRealExe($exeId);
3613
            }
3614
            $generatedFile = $objQuestionTmp->getFileUrl();
3615
        }
3616
3617
        $user_answer = '';
3618
        // Get answer list for matching
3619
        $sql = "SELECT id_auto, id, answer
3620
                FROM $table_ans
3621
                WHERE c_id = $course_id AND question_id = $questionId";
3622
        $res_answer = Database::query($sql);
3623
3624
        $answerMatching = [];
3625
        while ($real_answer = Database::fetch_array($res_answer)) {
3626
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3627
        }
3628
3629
        $real_answers = [];
3630
        $quiz_question_options = Question::readQuestionOption(
3631
            $questionId,
3632
            $course_id
3633
        );
3634
3635
        $organs_at_risk_hit = 0;
3636
        $questionScore = 0;
3637
        $orderedHotSpots = [];
3638
        if (HOT_SPOT == $answerType || ANNOTATION == $answerType) {
3639
            $orderedHotSpots = $em->getRepository('ChamiloCoreBundle:TrackEHotspot')->findBy(
3640
                [
3641
                    'hotspotQuestionId' => $questionId,
3642
                    'course' => $course_id,
3643
                    'hotspotExeId' => $exeId,
3644
                ],
3645
                ['hotspotAnswerId' => 'ASC']
3646
            );
3647
        }
3648
3649
        if ($debug) {
3650
            error_log('-- Start answer loop --');
3651
        }
3652
3653
        $answerDestination = null;
3654
        $userAnsweredQuestion = false;
3655
        $correctAnswerId = [];
3656
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3657
            $answer = $objAnswerTmp->selectAnswer($answerId);
3658
            $answerComment = $objAnswerTmp->selectComment($answerId);
3659
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3660
            $answerWeighting = (float) $objAnswerTmp->selectWeighting($answerId);
3661
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3662
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? (int) $objAnswerTmp->iid[$answerId] : 0;
3663
3664
            if ($debug) {
3665
                error_log("c_quiz_answer.id_auto: $answerAutoId ");
3666
                error_log("Answer marked as correct in db (0/1)?: $answerCorrect ");
3667
            }
3668
3669
            // Delineation
3670
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3671
            $answer_delineation_destination = $objAnswerTmp->selectDestination(1);
3672
3673
            switch ($answerType) {
3674
                case UNIQUE_ANSWER:
3675
                case UNIQUE_ANSWER_IMAGE:
3676
                case UNIQUE_ANSWER_NO_OPTION:
3677
                case READING_COMPREHENSION:
3678
                    if ($from_database) {
3679
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3680
                                WHERE
3681
                                    exe_id = $exeId AND
3682
                                    question_id = $questionId";
3683
                        $result = Database::query($sql);
3684
                        $choice = Database::result($result, 0, 'answer');
3685
3686
                        if (false === $userAnsweredQuestion) {
3687
                            $userAnsweredQuestion = !empty($choice);
3688
                        }
3689
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3690
                        if ($studentChoice) {
3691
                            $questionScore += $answerWeighting;
3692
                            $answerDestination = $objAnswerTmp->selectDestination($answerId);
3693
                            $correctAnswerId[] = $answerId;
3694
                        }
3695
                    } else {
3696
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3697
                        if ($studentChoice) {
3698
                            $questionScore += $answerWeighting;
3699
                            $answerDestination = $objAnswerTmp->selectDestination($answerId);
3700
                            $correctAnswerId[] = $answerId;
3701
                        }
3702
                    }
3703
3704
                    break;
3705
                case MULTIPLE_ANSWER_TRUE_FALSE:
3706
                    if ($from_database) {
3707
                        $choice = [];
3708
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3709
                                WHERE
3710
                                    exe_id = $exeId AND
3711
                                    question_id = ".$questionId;
3712
3713
                        $result = Database::query($sql);
3714
                        while ($row = Database::fetch_array($result)) {
3715
                            $values = explode(':', $row['answer']);
3716
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3717
                            $option = isset($values[1]) ? $values[1] : '';
3718
                            $choice[$my_answer_id] = $option;
3719
                        }
3720
                    }
3721
3722
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3723
                    if (!empty($studentChoice)) {
3724
                        $correctAnswerId[] = $answerAutoId;
3725
                        if ($studentChoice == $answerCorrect) {
3726
                            $questionScore += $true_score;
3727
                        } else {
3728
                            if ("Don't know" == $quiz_question_options[$studentChoice]['name'] ||
3729
                                'DoubtScore' == $quiz_question_options[$studentChoice]['name']
3730
                            ) {
3731
                                $questionScore += $doubt_score;
3732
                            } else {
3733
                                $questionScore += $false_score;
3734
                            }
3735
                        }
3736
                    } else {
3737
                        // If no result then the user just hit don't know
3738
                        $studentChoice = 3;
3739
                        $questionScore += $doubt_score;
3740
                    }
3741
                    $totalScore = $questionScore;
3742
3743
                    break;
3744
                case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
3745
                    if ($from_database) {
3746
                        $choice = [];
3747
                        $choiceDegreeCertainty = [];
3748
                        $sql = "SELECT answer
3749
                            FROM $TBL_TRACK_ATTEMPT
3750
                            WHERE
3751
                            exe_id = $exeId AND question_id = $questionId";
3752
3753
                        $result = Database::query($sql);
3754
                        while ($row = Database::fetch_array($result)) {
3755
                            $ind = $row['answer'];
3756
                            $values = explode(':', $ind);
3757
                            $myAnswerId = $values[0];
3758
                            $option = $values[1];
3759
                            $percent = $values[2];
3760
                            $choice[$myAnswerId] = $option;
3761
                            $choiceDegreeCertainty[$myAnswerId] = $percent;
3762
                        }
3763
                    }
3764
3765
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3766
                    $studentChoiceDegree = isset($choiceDegreeCertainty[$answerAutoId]) ? $choiceDegreeCertainty[$answerAutoId] : null;
3767
3768
                    // student score update
3769
                    if (!empty($studentChoice)) {
3770
                        if ($studentChoice == $answerCorrect) {
3771
                            // correct answer and student is Unsure or PrettySur
3772
                            if (isset($quiz_question_options[$studentChoiceDegree]) &&
3773
                                $quiz_question_options[$studentChoiceDegree]['position'] >= 3 &&
3774
                                $quiz_question_options[$studentChoiceDegree]['position'] < 9
3775
                            ) {
3776
                                $questionScore += $true_score;
3777
                            } else {
3778
                                // student ignore correct answer
3779
                                $questionScore += $doubt_score;
3780
                            }
3781
                        } else {
3782
                            // false answer and student is Unsure or PrettySur
3783
                            if ($quiz_question_options[$studentChoiceDegree]['position'] >= 3
3784
                                && $quiz_question_options[$studentChoiceDegree]['position'] < 9) {
3785
                                $questionScore += $false_score;
3786
                            } else {
3787
                                // student ignore correct answer
3788
                                $questionScore += $doubt_score;
3789
                            }
3790
                        }
3791
                    }
3792
                    $totalScore = $questionScore;
3793
3794
                    break;
3795
                case MULTIPLE_ANSWER:
3796
                    if ($from_database) {
3797
                        $choice = [];
3798
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3799
                                WHERE exe_id = $exeId AND question_id = $questionId ";
3800
                        $resultans = Database::query($sql);
3801
                        while ($row = Database::fetch_array($resultans)) {
3802
                            $choice[$row['answer']] = 1;
3803
                        }
3804
3805
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3806
                        $real_answers[$answerId] = (bool) $studentChoice;
3807
3808
                        if ($studentChoice) {
3809
                            $questionScore += $answerWeighting;
3810
                        }
3811
                    } else {
3812
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3813
                        $real_answers[$answerId] = (bool) $studentChoice;
3814
3815
                        if (isset($studentChoice)) {
3816
                            $correctAnswerId[] = $answerAutoId;
3817
                            $questionScore += $answerWeighting;
3818
                        }
3819
                    }
3820
                    $totalScore += $answerWeighting;
3821
3822
                    break;
3823
                case GLOBAL_MULTIPLE_ANSWER:
3824
                    if ($from_database) {
3825
                        $choice = [];
3826
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3827
                                WHERE exe_id = $exeId AND question_id = $questionId ";
3828
                        $resultans = Database::query($sql);
3829
                        while ($row = Database::fetch_array($resultans)) {
3830
                            $choice[$row['answer']] = 1;
3831
                        }
3832
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3833
                        $real_answers[$answerId] = (bool) $studentChoice;
3834
                        if ($studentChoice) {
3835
                            $questionScore += $answerWeighting;
3836
                        }
3837
                    } else {
3838
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3839
                        if (isset($studentChoice)) {
3840
                            $questionScore += $answerWeighting;
3841
                        }
3842
                        $real_answers[$answerId] = (bool) $studentChoice;
3843
                    }
3844
                    $totalScore += $answerWeighting;
3845
                    if ($debug) {
3846
                        error_log("studentChoice: $studentChoice");
3847
                    }
3848
3849
                    break;
3850
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3851
                    if ($from_database) {
3852
                        $choice = [];
3853
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3854
                                WHERE exe_id = $exeId AND question_id = $questionId";
3855
                        $resultans = Database::query($sql);
3856
                        while ($row = Database::fetch_array($resultans)) {
3857
                            $result = explode(':', $row['answer']);
3858
                            if (isset($result[0])) {
3859
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3860
                                $option = isset($result[1]) ? $result[1] : '';
3861
                                $choice[$my_answer_id] = $option;
3862
                            }
3863
                        }
3864
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3865
3866
                        $real_answers[$answerId] = false;
3867
                        if ($answerCorrect == $studentChoice) {
3868
                            $real_answers[$answerId] = true;
3869
                        }
3870
                    } else {
3871
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3872
                        $real_answers[$answerId] = false;
3873
                        if ($answerCorrect == $studentChoice) {
3874
                            $real_answers[$answerId] = true;
3875
                        }
3876
                    }
3877
3878
                    break;
3879
                case MULTIPLE_ANSWER_COMBINATION:
3880
                    if ($from_database) {
3881
                        $choice = [];
3882
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3883
                                WHERE exe_id = $exeId AND question_id = $questionId";
3884
                        $resultans = Database::query($sql);
3885
                        while ($row = Database::fetch_array($resultans)) {
3886
                            $choice[$row['answer']] = 1;
3887
                        }
3888
3889
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3890
                        if (1 == $answerCorrect) {
3891
                            $real_answers[$answerId] = false;
3892
                            if ($studentChoice) {
3893
                                $real_answers[$answerId] = true;
3894
                            }
3895
                        } else {
3896
                            $real_answers[$answerId] = true;
3897
                            if ($studentChoice) {
3898
                                $real_answers[$answerId] = false;
3899
                            }
3900
                        }
3901
                    } else {
3902
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3903
                        if (1 == $answerCorrect) {
3904
                            $real_answers[$answerId] = false;
3905
                            if ($studentChoice) {
3906
                                $real_answers[$answerId] = true;
3907
                            }
3908
                        } else {
3909
                            $real_answers[$answerId] = true;
3910
                            if ($studentChoice) {
3911
                                $real_answers[$answerId] = false;
3912
                            }
3913
                        }
3914
                    }
3915
3916
                    break;
3917
                case FILL_IN_BLANKS:
3918
                    $str = '';
3919
                    $answerFromDatabase = '';
3920
                    if ($from_database) {
3921
                        $sql = "SELECT answer
3922
                                FROM $TBL_TRACK_ATTEMPT
3923
                                WHERE
3924
                                    exe_id = $exeId AND
3925
                                    question_id= $questionId ";
3926
                        $result = Database::query($sql);
3927
                        $str = $answerFromDatabase = Database::result($result, 0, 'answer');
3928
                    }
3929
3930
                    // if ($saved_results == false && strpos($answerFromDatabase, 'font color') !== false) {
3931
                    if (false) {
3932
                        // the question is encoded like this
3933
                        // [A] B [C] D [E] F::10,10,10@1
3934
                        // number 1 before the "@" means that is a switchable fill in blank question
3935
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3936
                        // means that is a normal fill blank question
3937
                        // first we explode the "::"
3938
                        $pre_array = explode('::', $answer);
3939
3940
                        // is switchable fill blank or not
3941
                        $last = count($pre_array) - 1;
3942
                        $is_set_switchable = explode('@', $pre_array[$last]);
3943
                        $switchable_answer_set = false;
3944
                        if (isset($is_set_switchable[1]) && 1 == $is_set_switchable[1]) {
3945
                            $switchable_answer_set = true;
3946
                        }
3947
                        $answer = '';
3948
                        for ($k = 0; $k < $last; $k++) {
3949
                            $answer .= $pre_array[$k];
3950
                        }
3951
                        // splits weightings that are joined with a comma
3952
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3953
                        // we save the answer because it will be modified
3954
                        $temp = $answer;
3955
                        $answer = '';
3956
                        $j = 0;
3957
                        //initialise answer tags
3958
                        $user_tags = $correct_tags = $real_text = [];
3959
                        // the loop will stop at the end of the text
3960
                        while (1) {
3961
                            // quits the loop if there are no more blanks (detect '[')
3962
                            if (false == $temp || false === ($pos = api_strpos($temp, '['))) {
3963
                                // adds the end of the text
3964
                                $answer = $temp;
3965
                                $real_text[] = $answer;
3966
3967
                                break; //no more "blanks", quit the loop
3968
                            }
3969
                            // adds the piece of text that is before the blank
3970
                            //and ends with '[' into a general storage array
3971
                            $real_text[] = api_substr($temp, 0, $pos + 1);
3972
                            $answer .= api_substr($temp, 0, $pos + 1);
3973
                            //take the string remaining (after the last "[" we found)
3974
                            $temp = api_substr($temp, $pos + 1);
3975
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3976
                            if (false === ($pos = api_strpos($temp, ']'))) {
3977
                                // adds the end of the text
3978
                                $answer .= $temp;
3979
3980
                                break;
3981
                            }
3982
                            if ($from_database) {
3983
                                $str = $answerFromDatabase;
3984
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3985
                                $str = str_replace('\r\n', '', $str);
3986
3987
                                $choice = $arr[1];
3988
                                if (isset($choice[$j])) {
3989
                                    $tmp = api_strrpos($choice[$j], ' / ');
3990
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3991
                                    $choice[$j] = trim($choice[$j]);
3992
                                    // Needed to let characters ' and " to work as part of an answer
3993
                                    $choice[$j] = stripslashes($choice[$j]);
3994
                                } else {
3995
                                    $choice[$j] = null;
3996
                                }
3997
                            } else {
3998
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
3999
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
4000
                            }
4001
4002
                            $user_tags[] = $choice[$j];
4003
                            // Put the contents of the [] answer tag into correct_tags[]
4004
                            $correct_tags[] = api_substr($temp, 0, $pos);
4005
                            $j++;
4006
                            $temp = api_substr($temp, $pos + 1);
4007
                        }
4008
                        $answer = '';
4009
                        $real_correct_tags = $correct_tags;
4010
                        $chosen_list = [];
4011
4012
                        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...
4013
                            if (0 == $i) {
4014
                                $answer .= $real_text[0];
4015
                            }
4016
                            if (!$switchable_answer_set) {
4017
                                // Needed to parse ' and " characters
4018
                                $user_tags[$i] = stripslashes($user_tags[$i]);
4019
                                if ($correct_tags[$i] == $user_tags[$i]) {
4020
                                    // gives the related weighting to the student
4021
                                    $questionScore += $answerWeighting[$i];
4022
                                    // increments total score
4023
                                    $totalScore += $answerWeighting[$i];
4024
                                    // adds the word in green at the end of the string
4025
                                    $answer .= $correct_tags[$i];
4026
                                } elseif (!empty($user_tags[$i])) {
4027
                                    // else if the word entered by the student IS NOT the same as
4028
                                    // the one defined by the professor
4029
                                    // adds the word in red at the end of the string, and strikes it
4030
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
4031
                                } else {
4032
                                    // adds a tabulation if no word has been typed by the student
4033
                                    $answer .= ''; // remove &nbsp; that causes issue
4034
                                }
4035
                            } else {
4036
                                // switchable fill in the blanks
4037
                                if (in_array($user_tags[$i], $correct_tags)) {
4038
                                    $chosen_list[] = $user_tags[$i];
4039
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
4040
                                    // gives the related weighting to the student
4041
                                    $questionScore += $answerWeighting[$i];
4042
                                    // increments total score
4043
                                    $totalScore += $answerWeighting[$i];
4044
                                    // adds the word in green at the end of the string
4045
                                    $answer .= $user_tags[$i];
4046
                                } elseif (!empty($user_tags[$i])) {
4047
                                    // else if the word entered by the student IS NOT the same
4048
                                    // as the one defined by the professor
4049
                                    // adds the word in red at the end of the string, and strikes it
4050
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
4051
                                } else {
4052
                                    // adds a tabulation if no word has been typed by the student
4053
                                    $answer .= ''; // remove &nbsp; that causes issue
4054
                                }
4055
                            }
4056
4057
                            // adds the correct word, followed by ] to close the blank
4058
                            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
4059
                            if (isset($real_text[$i + 1])) {
4060
                                $answer .= $real_text[$i + 1];
4061
                            }
4062
                        }
4063
                    } else {
4064
                        // insert the student result in the track_e_attempt table, field answer
4065
                        // $answer is the answer like in the c_quiz_answer table for the question
4066
                        // student data are choice[]
4067
                        $listCorrectAnswers = FillBlanks::getAnswerInfo($answer);
4068
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
4069
                        $answerWeighting = $listCorrectAnswers['weighting'];
4070
                        // user choices is an array $choice
4071
4072
                        // get existing user data in n the BDD
4073
                        if ($from_database) {
4074
                            $listStudentResults = FillBlanks::getAnswerInfo(
4075
                                $answerFromDatabase,
4076
                                true
4077
                            );
4078
                            $choice = $listStudentResults['student_answer'];
4079
                        }
4080
4081
                        // loop other all blanks words
4082
                        if (!$switchableAnswerSet) {
4083
                            // not switchable answer, must be in the same place than teacher order
4084
                            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...
4085
                                $studentAnswer = isset($choice[$i]) ? $choice[$i] : '';
4086
                                $correctAnswer = $listCorrectAnswers['words'][$i];
4087
4088
                                if ($debug) {
4089
                                    error_log("Student answer: $i");
4090
                                    error_log($studentAnswer);
4091
                                }
4092
4093
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
4094
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
4095
                                // ENT_QUOTES is used in order to transform ' to &#039;
4096
                                if (!$from_database) {
4097
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
4098
                                    if ($debug) {
4099
                                        error_log('Student answer cleaned:');
4100
                                        error_log($studentAnswer);
4101
                                    }
4102
                                }
4103
4104
                                $isAnswerCorrect = 0;
4105
                                if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database)) {
4106
                                    // gives the related weighting to the student
4107
                                    $questionScore += $answerWeighting[$i];
4108
                                    // increments total score
4109
                                    $totalScore += $answerWeighting[$i];
4110
                                    $isAnswerCorrect = 1;
4111
                                }
4112
                                if ($debug) {
4113
                                    error_log("isAnswerCorrect $i: $isAnswerCorrect");
4114
                                }
4115
4116
                                $studentAnswerToShow = $studentAnswer;
4117
                                $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
4118
                                if ($debug) {
4119
                                    error_log("Fill in blank type: $type");
4120
                                }
4121
                                if (FillBlanks::FILL_THE_BLANK_MENU == $type) {
4122
                                    $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
4123
                                    if ('' != $studentAnswer) {
4124
                                        foreach ($listMenu as $item) {
4125
                                            if (sha1($item) == $studentAnswer) {
4126
                                                $studentAnswerToShow = $item;
4127
                                            }
4128
                                        }
4129
                                    }
4130
                                }
4131
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
4132
                                $listCorrectAnswers['student_score'][$i] = $isAnswerCorrect;
4133
                            }
4134
                        } else {
4135
                            // switchable answer
4136
                            $listStudentAnswerTemp = $choice;
4137
                            $listTeacherAnswerTemp = $listCorrectAnswers['words'];
4138
4139
                            // for every teacher answer, check if there is a student answer
4140
                            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...
4141
                                $studentAnswer = trim($listStudentAnswerTemp[$i]);
4142
                                $studentAnswerToShow = $studentAnswer;
4143
4144
                                if (empty($studentAnswer)) {
4145
                                    continue;
4146
                                }
4147
4148
                                if ($debug) {
4149
                                    error_log("Student answer: $i");
4150
                                    error_log($studentAnswer);
4151
                                }
4152
4153
                                if (!$from_database) {
4154
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
4155
                                    if ($debug) {
4156
                                        error_log("Student answer cleaned:");
4157
                                        error_log($studentAnswer);
4158
                                    }
4159
                                }
4160
4161
                                $found = false;
4162
                                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...
4163
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
4164
4165
                                    if (!$found) {
4166
                                        if (FillBlanks::isStudentAnswerGood(
4167
                                            $studentAnswer,
4168
                                            $correctAnswer,
4169
                                            $from_database
4170
                                        )) {
4171
                                            $questionScore += $answerWeighting[$i];
4172
                                            $totalScore += $answerWeighting[$i];
4173
                                            $listTeacherAnswerTemp[$j] = '';
4174
                                            $found = true;
4175
                                        }
4176
                                    }
4177
4178
                                    $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
4179
                                    if (FillBlanks::FILL_THE_BLANK_MENU == $type) {
4180
                                        $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
4181
                                        if (!empty($studentAnswer)) {
4182
                                            foreach ($listMenu as $key => $item) {
4183
                                                if ($key == $correctAnswer) {
4184
                                                    $studentAnswerToShow = $item;
4185
                                                    break;
4186
                                                }
4187
                                            }
4188
                                        }
4189
                                    }
4190
                                }
4191
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
4192
                                $listCorrectAnswers['student_score'][$i] = $found ? 1 : 0;
4193
                            }
4194
                        }
4195
                        $answer = FillBlanks::getAnswerInStudentAttempt($listCorrectAnswers);
4196
                    }
4197
4198
                    break;
4199
                case CALCULATED_ANSWER:
4200
                    $calculatedAnswerList = Session::read('calculatedAnswerId');
4201
                    if (!empty($calculatedAnswerList)) {
4202
                        $answer = $objAnswerTmp->selectAnswer($calculatedAnswerList[$questionId]);
4203
                        $preArray = explode('@@', $answer);
4204
                        $last = count($preArray) - 1;
4205
                        $answer = '';
4206
                        for ($k = 0; $k < $last; $k++) {
4207
                            $answer .= $preArray[$k];
4208
                        }
4209
                        $answerWeighting = [$answerWeighting];
4210
                        // we save the answer because it will be modified
4211
                        $temp = $answer;
4212
                        $answer = '';
4213
                        $j = 0;
4214
                        // initialise answer tags
4215
                        $userTags = $correctTags = $realText = [];
4216
                        // the loop will stop at the end of the text
4217
                        while (1) {
4218
                            // quits the loop if there are no more blanks (detect '[')
4219
                            if (false == $temp || false === ($pos = api_strpos($temp, '['))) {
4220
                                // adds the end of the text
4221
                                $answer = $temp;
4222
                                $realText[] = $answer;
4223
4224
                                break; //no more "blanks", quit the loop
4225
                            }
4226
                            // adds the piece of text that is before the blank
4227
                            // and ends with '[' into a general storage array
4228
                            $realText[] = api_substr($temp, 0, $pos + 1);
4229
                            $answer .= api_substr($temp, 0, $pos + 1);
4230
                            // take the string remaining (after the last "[" we found)
4231
                            $temp = api_substr($temp, $pos + 1);
4232
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
4233
                            if (false === ($pos = api_strpos($temp, ']'))) {
4234
                                // adds the end of the text
4235
                                $answer .= $temp;
4236
4237
                                break;
4238
                            }
4239
4240
                            if ($from_database) {
4241
                                $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4242
                                        WHERE
4243
                                            exe_id = $exeId AND
4244
                                            question_id = $questionId ";
4245
                                $result = Database::query($sql);
4246
                                $str = Database::result($result, 0, 'answer');
4247
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
4248
                                $str = str_replace('\r\n', '', $str);
4249
                                $choice = $arr[1];
4250
                                if (isset($choice[$j])) {
4251
                                    $tmp = api_strrpos($choice[$j], ' / ');
4252
                                    if ($tmp) {
4253
                                        $choice[$j] = api_substr($choice[$j], 0, $tmp);
4254
                                    } else {
4255
                                        $tmp = ltrim($tmp, '[');
4256
                                        $tmp = rtrim($tmp, ']');
4257
                                    }
4258
                                    $choice[$j] = trim($choice[$j]);
4259
                                    // Needed to let characters ' and " to work as part of an answer
4260
                                    $choice[$j] = stripslashes($choice[$j]);
4261
                                } else {
4262
                                    $choice[$j] = null;
4263
                                }
4264
                            } else {
4265
                                // This value is the user input not escaped while correct answer is escaped by ckeditor
4266
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
4267
                            }
4268
                            $userTags[] = $choice[$j];
4269
                            // put the contents of the [] answer tag into correct_tags[]
4270
                            $correctTags[] = api_substr($temp, 0, $pos);
4271
                            $j++;
4272
                            $temp = api_substr($temp, $pos + 1);
4273
                        }
4274
                        $answer = '';
4275
                        $realCorrectTags = $correctTags;
4276
                        $calculatedStatus = Display::label(get_lang('Incorrect'), 'danger');
4277
                        $expectedAnswer = '';
4278
                        $calculatedChoice = '';
4279
4280
                        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...
4281
                            if (0 == $i) {
4282
                                $answer .= $realText[0];
4283
                            }
4284
                            // Needed to parse ' and " characters
4285
                            $userTags[$i] = stripslashes($userTags[$i]);
4286
                            if ($correctTags[$i] == $userTags[$i]) {
4287
                                // gives the related weighting to the student
4288
                                $questionScore += $answerWeighting[$i];
4289
                                // increments total score
4290
                                $totalScore += $answerWeighting[$i];
4291
                                // adds the word in green at the end of the string
4292
                                $answer .= $correctTags[$i];
4293
                                $calculatedChoice = $correctTags[$i];
4294
                            } elseif (!empty($userTags[$i])) {
4295
                                // else if the word entered by the student IS NOT the same as
4296
                                // the one defined by the professor
4297
                                // adds the word in red at the end of the string, and strikes it
4298
                                $answer .= '<font color="red"><s>'.$userTags[$i].'</s></font>';
4299
                                $calculatedChoice = $userTags[$i];
4300
                            } else {
4301
                                // adds a tabulation if no word has been typed by the student
4302
                                $answer .= ''; // remove &nbsp; that causes issue
4303
                            }
4304
                            // adds the correct word, followed by ] to close the blank
4305
                            if (EXERCISE_FEEDBACK_TYPE_EXAM != $this->results_disabled) {
4306
                                $answer .= ' / <font color="green"><b>'.$realCorrectTags[$i].'</b></font>';
4307
                                $calculatedStatus = Display::label(get_lang('Correct'), 'success');
4308
                                $expectedAnswer = $realCorrectTags[$i];
4309
                            }
4310
                            $answer .= ']';
4311
                            if (isset($realText[$i + 1])) {
4312
                                $answer .= $realText[$i + 1];
4313
                            }
4314
                        }
4315
                    } else {
4316
                        if ($from_database) {
4317
                            $sql = "SELECT *
4318
                                    FROM $TBL_TRACK_ATTEMPT
4319
                                    WHERE
4320
                                        exe_id = $exeId AND
4321
                                        question_id = $questionId ";
4322
                            $result = Database::query($sql);
4323
                            $resultData = Database::fetch_array($result, 'ASSOC');
4324
                            $answer = $resultData['answer'];
4325
                            $questionScore = $resultData['marks'];
4326
                        }
4327
                    }
4328
4329
                    break;
4330
                case FREE_ANSWER:
4331
                    if ($from_database) {
4332
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4333
                                 WHERE
4334
                                    exe_id = $exeId AND
4335
                                    question_id= ".$questionId;
4336
                        $result = Database::query($sql);
4337
                        $data = Database::fetch_array($result);
4338
4339
                        $choice = $data['answer'];
4340
                        $choice = str_replace('\r\n', '', $choice);
4341
                        $choice = stripslashes($choice);
4342
                        $questionScore = $data['marks'];
4343
4344
                        if (-1 == $questionScore) {
4345
                            $totalScore += 0;
4346
                        } else {
4347
                            $totalScore += $questionScore;
4348
                        }
4349
                        if ('' == $questionScore) {
4350
                            $questionScore = 0;
4351
                        }
4352
                        $arrques = $questionName;
4353
                        $arrans = $choice;
4354
                    } else {
4355
                        $studentChoice = $choice;
4356
                        if ($studentChoice) {
4357
                            //Fixing negative puntation see #2193
4358
                            $questionScore = 0;
4359
                            $totalScore += 0;
4360
                        }
4361
                    }
4362
4363
                    break;
4364
                case ORAL_EXPRESSION:
4365
                    if ($from_database) {
4366
                        $query = "SELECT answer, marks
4367
                                  FROM $TBL_TRACK_ATTEMPT
4368
                                  WHERE
4369
                                        exe_id = $exeId AND
4370
                                        question_id = $questionId
4371
                                 ";
4372
                        $resq = Database::query($query);
4373
                        $row = Database::fetch_assoc($resq);
4374
                        $choice = $row['answer'];
4375
                        $choice = str_replace('\r\n', '', $choice);
4376
                        $choice = stripslashes($choice);
4377
                        $questionScore = $row['marks'];
4378
                        if (-1 == $questionScore) {
4379
                            $totalScore += 0;
4380
                        } else {
4381
                            $totalScore += $questionScore;
4382
                        }
4383
                        $arrques = $questionName;
4384
                        $arrans = $choice;
4385
                    } else {
4386
                        $studentChoice = $choice;
4387
                        if ($studentChoice) {
4388
                            //Fixing negative puntation see #2193
4389
                            $questionScore = 0;
4390
                            $totalScore += 0;
4391
                        }
4392
                    }
4393
4394
                    break;
4395
                case DRAGGABLE:
4396
                case MATCHING_DRAGGABLE:
4397
                case MATCHING:
4398
                    if ($from_database) {
4399
                        $sql = "SELECT id, answer, id_auto
4400
                                FROM $table_ans
4401
                                WHERE
4402
                                    c_id = $course_id AND
4403
                                    question_id = $questionId AND
4404
                                    correct = 0
4405
                                ";
4406
                        $result = Database::query($sql);
4407
                        // Getting the real answer
4408
                        $real_list = [];
4409
                        while ($realAnswer = Database::fetch_array($result)) {
4410
                            $real_list[$realAnswer['id_auto']] = $realAnswer['answer'];
4411
                        }
4412
4413
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
4414
                                FROM $table_ans
4415
                                WHERE
4416
                                    c_id = $course_id AND
4417
                                    question_id = $questionId AND
4418
                                    correct <> 0
4419
                                ORDER BY id_auto";
4420
                        $result = Database::query($sql);
4421
                        $options = [];
4422
                        while ($row = Database::fetch_array($result, 'ASSOC')) {
4423
                            $options[] = $row;
4424
                        }
4425
4426
                        $questionScore = 0;
4427
                        $counterAnswer = 1;
4428
                        foreach ($options as $a_answers) {
4429
                            $i_answer_id = $a_answers['id']; //3
4430
                            $s_answer_label = $a_answers['answer']; // your daddy - your mother
4431
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
4432
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
4433
4434
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4435
                                    WHERE
4436
                                        exe_id = '$exeId' AND
4437
                                        question_id = '$questionId' AND
4438
                                        position = '$i_answer_id_auto'";
4439
                            $result = Database::query($sql);
4440
                            $s_user_answer = 0;
4441
                            if (Database::num_rows($result) > 0) {
4442
                                //  rich - good looking
4443
                                $s_user_answer = Database::result($result, 0, 0);
4444
                            }
4445
                            $i_answerWeighting = $a_answers['ponderation'];
4446
                            $user_answer = '';
4447
                            $status = Display::label(get_lang('Incorrect'), 'danger');
4448
4449
                            if (!empty($s_user_answer)) {
4450
                                if (DRAGGABLE == $answerType) {
4451
                                    if ($s_user_answer == $i_answer_correct_answer) {
4452
                                        $questionScore += $i_answerWeighting;
4453
                                        $totalScore += $i_answerWeighting;
4454
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
4455
                                        if ($this->showExpectedChoice()) {
4456
                                            $user_answer = $answerMatching[$i_answer_id_auto];
4457
                                        }
4458
                                        $status = Display::label(get_lang('Correct'), 'success');
4459
                                    } else {
4460
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4461
                                        if ($this->showExpectedChoice()) {
4462
                                            $data = $options[$real_list[$s_user_answer] - 1];
4463
                                            $user_answer = $data['answer'];
4464
                                        }
4465
                                    }
4466
                                } else {
4467
                                    if ($s_user_answer == $i_answer_correct_answer) {
4468
                                        $questionScore += $i_answerWeighting;
4469
                                        $totalScore += $i_answerWeighting;
4470
                                        $status = Display::label(get_lang('Correct'), 'success');
4471
4472
                                        // Try with id
4473
                                        if (isset($real_list[$i_answer_id])) {
4474
                                            $user_answer = Display::span(
4475
                                                $real_list[$i_answer_id],
4476
                                                ['style' => 'color: #008000; font-weight: bold;']
4477
                                            );
4478
                                        }
4479
4480
                                        // Try with $i_answer_id_auto
4481
                                        if (empty($user_answer)) {
4482
                                            if (isset($real_list[$i_answer_id_auto])) {
4483
                                                $user_answer = Display::span(
4484
                                                    $real_list[$i_answer_id_auto],
4485
                                                    ['style' => 'color: #008000; font-weight: bold;']
4486
                                                );
4487
                                            }
4488
                                        }
4489
4490
                                        if (isset($real_list[$i_answer_correct_answer])) {
4491
                                            $user_answer = Display::span(
4492
                                                $real_list[$i_answer_correct_answer],
4493
                                                ['style' => 'color: #008000; font-weight: bold;']
4494
                                            );
4495
                                        }
4496
                                    } else {
4497
                                        $user_answer = Display::span(
4498
                                            $real_list[$s_user_answer],
4499
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
4500
                                        );
4501
                                        if ($this->showExpectedChoice()) {
4502
                                            if (isset($real_list[$s_user_answer])) {
4503
                                                $user_answer = Display::span($real_list[$s_user_answer]);
4504
                                            }
4505
                                        }
4506
                                    }
4507
                                }
4508
                            } elseif (DRAGGABLE == $answerType) {
4509
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4510
                                if ($this->showExpectedChoice()) {
4511
                                    $user_answer = '';
4512
                                }
4513
                            } else {
4514
                                $user_answer = Display::span(
4515
                                    get_lang('Incorrect').' &nbsp;',
4516
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
4517
                                );
4518
                                if ($this->showExpectedChoice()) {
4519
                                    $user_answer = '';
4520
                                }
4521
                            }
4522
4523
                            if ($show_result) {
4524
                                if (false === $this->showExpectedChoice() &&
4525
                                    false === $showTotalScoreAndUserChoicesInLastAttempt
4526
                                ) {
4527
                                    $user_answer = '';
4528
                                }
4529
                                switch ($answerType) {
4530
                                    case MATCHING:
4531
                                    case MATCHING_DRAGGABLE:
4532
                                        echo '<tr>';
4533
                                        if (!in_array(
4534
                                            $this->results_disabled,
4535
                                            [
4536
                                                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
4537
                                            ]
4538
                                        )
4539
                                        ) {
4540
                                            echo '<td>'.$s_answer_label.'</td>';
4541
                                            echo '<td>'.$user_answer.'</td>';
4542
                                        } else {
4543
                                            echo '<td>'.$s_answer_label.'</td>';
4544
                                            $status = Display::label(get_lang('Correct'), 'success');
4545
                                        }
4546
4547
                                        if ($this->showExpectedChoice()) {
4548
                                            if ($this->showExpectedChoiceColumn()) {
4549
                                                echo '<td>';
4550
                                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4551
                                                    if (isset($real_list[$i_answer_correct_answer]) &&
4552
                                                        true == $showTotalScoreAndUserChoicesInLastAttempt
4553
                                                    ) {
4554
                                                        echo Display::span(
4555
                                                            $real_list[$i_answer_correct_answer]
4556
                                                        );
4557
                                                    }
4558
                                                }
4559
                                                echo '</td>';
4560
                                            }
4561
                                            echo '<td>'.$status.'</td>';
4562
                                        } else {
4563
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4564
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4565
                                                    true === $showTotalScoreAndUserChoicesInLastAttempt
4566
                                                ) {
4567
                                                    if ($this->showExpectedChoiceColumn()) {
4568
                                                        echo '<td>';
4569
                                                        echo Display::span(
4570
                                                            $real_list[$i_answer_correct_answer],
4571
                                                            ['style' => 'color: #008000; font-weight: bold;']
4572
                                                        );
4573
                                                        echo '</td>';
4574
                                                    }
4575
                                                }
4576
                                            }
4577
                                        }
4578
                                        echo '</tr>';
4579
4580
                                        break;
4581
                                    case DRAGGABLE:
4582
                                        if (false == $showTotalScoreAndUserChoicesInLastAttempt) {
4583
                                            $s_answer_label = '';
4584
                                        }
4585
                                        echo '<tr>';
4586
                                        if ($this->showExpectedChoice()) {
4587
                                            if (!in_array($this->results_disabled, [
4588
                                                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
4589
                                                //RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
4590
                                            ])
4591
                                            ) {
4592
                                                echo '<td>'.$user_answer.'</td>';
4593
                                            } else {
4594
                                                $status = Display::label(get_lang('Correct'), 'success');
4595
                                            }
4596
                                            echo '<td>'.$s_answer_label.'</td>';
4597
                                            echo '<td>'.$status.'</td>';
4598
                                        } else {
4599
                                            echo '<td>'.$s_answer_label.'</td>';
4600
                                            echo '<td>'.$user_answer.'</td>';
4601
                                            echo '<td>';
4602
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4603
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4604
                                                    true === $showTotalScoreAndUserChoicesInLastAttempt
4605
                                                ) {
4606
                                                    echo Display::span(
4607
                                                        $real_list[$i_answer_correct_answer],
4608
                                                        ['style' => 'color: #008000; font-weight: bold;']
4609
                                                    );
4610
                                                }
4611
                                            }
4612
                                            echo '</td>';
4613
                                        }
4614
                                        echo '</tr>';
4615
4616
                                        break;
4617
                                }
4618
                            }
4619
                            $counterAnswer++;
4620
                        }
4621
4622
                        break 2; // break the switch and the "for" condition
4623
                    } else {
4624
                        if ($answerCorrect) {
4625
                            if (isset($choice[$answerAutoId]) &&
4626
                                $answerCorrect == $choice[$answerAutoId]
4627
                            ) {
4628
                                $correctAnswerId[] = $answerAutoId;
4629
                                $questionScore += $answerWeighting;
4630
                                $totalScore += $answerWeighting;
4631
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4632
                            } else {
4633
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4634
                                    $user_answer = Display::span(
4635
                                        $answerMatching[$choice[$answerAutoId]],
4636
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4637
                                    );
4638
                                }
4639
                            }
4640
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4641
                        }
4642
                    }
4643
4644
                    break;
4645
                case HOT_SPOT:
4646
                    if ($from_database) {
4647
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4648
                        // Check auto id
4649
                        $sql = "SELECT hotspot_correct
4650
                                FROM $TBL_TRACK_HOTSPOT
4651
                                WHERE
4652
                                    hotspot_exe_id = $exeId AND
4653
                                    hotspot_question_id= $questionId AND
4654
                                    hotspot_answer_id = $answerAutoId
4655
                                ORDER BY hotspot_id ASC";
4656
                        $result = Database::query($sql);
4657
                        if (Database::num_rows($result)) {
4658
                            $studentChoice = Database::result(
4659
                                $result,
4660
                                0,
4661
                                'hotspot_correct'
4662
                            );
4663
4664
                            if ($studentChoice) {
4665
                                $questionScore += $answerWeighting;
4666
                                $totalScore += $answerWeighting;
4667
                            }
4668
                        } else {
4669
                            // If answer.id is different:
4670
                            $sql = "SELECT hotspot_correct
4671
                                FROM $TBL_TRACK_HOTSPOT
4672
                                WHERE
4673
                                    hotspot_exe_id = $exeId AND
4674
                                    hotspot_question_id= $questionId AND
4675
                                    hotspot_answer_id = ".(int) $answerId.'
4676
                                ORDER BY hotspot_id ASC';
4677
                            $result = Database::query($sql);
4678
4679
                            if (Database::num_rows($result)) {
4680
                                $studentChoice = Database::result(
4681
                                    $result,
4682
                                    0,
4683
                                    'hotspot_correct'
4684
                                );
4685
4686
                                if ($studentChoice) {
4687
                                    $questionScore += $answerWeighting;
4688
                                    $totalScore += $answerWeighting;
4689
                                }
4690
                            } else {
4691
                                // check answer.iid
4692
                                if (!empty($answerIid)) {
4693
                                    $sql = "SELECT hotspot_correct
4694
                                            FROM $TBL_TRACK_HOTSPOT
4695
                                            WHERE
4696
                                                hotspot_exe_id = $exeId AND
4697
                                                hotspot_question_id= $questionId AND
4698
                                                hotspot_answer_id = $answerIid
4699
                                            ORDER BY hotspot_id ASC";
4700
                                    $result = Database::query($sql);
4701
4702
                                    $studentChoice = Database::result(
4703
                                        $result,
4704
                                        0,
4705
                                        'hotspot_correct'
4706
                                    );
4707
4708
                                    if ($studentChoice) {
4709
                                        $questionScore += $answerWeighting;
4710
                                        $totalScore += $answerWeighting;
4711
                                    }
4712
                                }
4713
                            }
4714
                        }
4715
                    } else {
4716
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4717
                            $choice[$answerAutoId] = 0;
4718
                            $choice[$answerIid] = 0;
4719
                        } else {
4720
                            $studentChoice = $choice[$answerAutoId];
4721
                            if (empty($studentChoice)) {
4722
                                $studentChoice = $choice[$answerIid];
4723
                            }
4724
                            $choiceIsValid = false;
4725
                            if (!empty($studentChoice)) {
4726
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4727
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4728
                                $choicePoint = Geometry::decodePoint($studentChoice);
4729
4730
                                switch ($hotspotType) {
4731
                                    case 'square':
4732
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4733
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4734
4735
                                        break;
4736
                                    case 'circle':
4737
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4738
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4739
4740
                                        break;
4741
                                    case 'poly':
4742
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4743
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4744
4745
                                        break;
4746
                                }
4747
                            }
4748
4749
                            $choice[$answerAutoId] = 0;
4750
                            if ($choiceIsValid) {
4751
                                $questionScore += $answerWeighting;
4752
                                $totalScore += $answerWeighting;
4753
                                $choice[$answerAutoId] = 1;
4754
                                $choice[$answerIid] = 1;
4755
                            }
4756
                        }
4757
                    }
4758
4759
                    break;
4760
                case HOT_SPOT_ORDER:
4761
                    // @todo never added to chamilo
4762
                    // for hotspot with fixed order
4763
                    $studentChoice = $choice['order'][$answerId];
4764
                    if ($studentChoice == $answerId) {
4765
                        $questionScore += $answerWeighting;
4766
                        $totalScore += $answerWeighting;
4767
                        $studentChoice = true;
4768
                    } else {
4769
                        $studentChoice = false;
4770
                    }
4771
4772
                    break;
4773
                case HOT_SPOT_DELINEATION:
4774
                    // for hotspot with delineation
4775
                    if ($from_database) {
4776
                        // getting the user answer
4777
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4778
                        $query = "SELECT hotspot_correct, hotspot_coordinate
4779
                                    FROM $TBL_TRACK_HOTSPOT
4780
                                    WHERE
4781
                                        hotspot_exe_id = $exeId AND
4782
                                        hotspot_question_id= $questionId AND
4783
                                        hotspot_answer_id = '1'";
4784
                        // By default we take 1 because it's a delineation
4785
                        $resq = Database::query($query);
4786
                        $row = Database::fetch_array($resq, 'ASSOC');
4787
4788
                        $choice = $row['hotspot_correct'];
4789
                        $user_answer = $row['hotspot_coordinate'];
4790
4791
                        // THIS is very important otherwise the poly_compile will throw an error!!
4792
                        // round-up the coordinates
4793
                        $coords = explode('/', $user_answer);
4794
                        $coords = array_filter($coords);
4795
                        $user_array = '';
4796
                        foreach ($coords as $coord) {
4797
                            list($x, $y) = explode(';', $coord);
4798
                            $user_array .= round($x).';'.round($y).'/';
4799
                        }
4800
                        $user_array = substr($user_array, 0, -1) ?: '';
4801
                    } else {
4802
                        if (!empty($studentChoice)) {
4803
                            $correctAnswerId[] = $answerAutoId;
4804
                            $newquestionList[] = $questionId;
4805
                        }
4806
4807
                        if (1 === $answerId) {
4808
                            $studentChoice = $choice[$answerId];
4809
                            $questionScore += $answerWeighting;
4810
                        }
4811
                        if (isset($_SESSION['exerciseResultCoordinates'][$questionId])) {
4812
                            $user_array = $_SESSION['exerciseResultCoordinates'][$questionId];
4813
                        }
4814
                    }
4815
                    $_SESSION['hotspot_coord'][$questionId][1] = $delineation_cord;
4816
                    $_SESSION['hotspot_dest'][$questionId][1] = $answer_delineation_destination;
4817
4818
                    break;
4819
                case ANNOTATION:
4820
                    if ($from_database) {
4821
                        $sql = "SELECT answer, marks
4822
                                FROM $TBL_TRACK_ATTEMPT
4823
                                WHERE
4824
                                  exe_id = $exeId AND
4825
                                  question_id = $questionId ";
4826
                        $resq = Database::query($sql);
4827
                        $data = Database::fetch_array($resq);
4828
4829
                        $questionScore = empty($data['marks']) ? 0 : $data['marks'];
4830
                        $arrques = $questionName;
4831
4832
                        break;
4833
                    }
4834
                    $studentChoice = $choice;
4835
                    if ($studentChoice) {
4836
                        $questionScore = 0;
4837
                    }
4838
4839
                    break;
4840
            }
4841
4842
            if ($show_result) {
4843
                if ('exercise_result' === $from) {
4844
                    // Display answers (if not matching type, or if the answer is correct)
4845
                    if (!in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4846
                        $answerCorrect
4847
                    ) {
4848
                        if (in_array(
4849
                            $answerType,
4850
                            [
4851
                                UNIQUE_ANSWER,
4852
                                UNIQUE_ANSWER_IMAGE,
4853
                                UNIQUE_ANSWER_NO_OPTION,
4854
                                MULTIPLE_ANSWER,
4855
                                MULTIPLE_ANSWER_COMBINATION,
4856
                                GLOBAL_MULTIPLE_ANSWER,
4857
                                READING_COMPREHENSION,
4858
                            ]
4859
                        )) {
4860
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4861
                                $this,
4862
                                $feedback_type,
4863
                                $answerType,
4864
                                $studentChoice,
4865
                                $answer,
4866
                                $answerComment,
4867
                                $answerCorrect,
4868
                                0,
4869
                                0,
4870
                                0,
4871
                                $results_disabled,
4872
                                $showTotalScoreAndUserChoicesInLastAttempt,
4873
                                $this->export
4874
                            );
4875
                        } elseif (MULTIPLE_ANSWER_TRUE_FALSE == $answerType) {
4876
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4877
                                $this,
4878
                                $feedback_type,
4879
                                $answerType,
4880
                                $studentChoice,
4881
                                $answer,
4882
                                $answerComment,
4883
                                $answerCorrect,
4884
                                0,
4885
                                $questionId,
4886
                                0,
4887
                                $results_disabled,
4888
                                $showTotalScoreAndUserChoicesInLastAttempt
4889
                            );
4890
                        } elseif (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $answerType) {
4891
                            ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
4892
                                $this,
4893
                                $feedback_type,
4894
                                $studentChoice,
4895
                                $studentChoiceDegree,
4896
                                $answer,
4897
                                $answerComment,
4898
                                $answerCorrect,
4899
                                $questionId,
4900
                                $results_disabled
4901
                            );
4902
                        } elseif (MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE == $answerType) {
4903
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4904
                                $this,
4905
                                $feedback_type,
4906
                                $answerType,
4907
                                $studentChoice,
4908
                                $answer,
4909
                                $answerComment,
4910
                                $answerCorrect,
4911
                                0,
4912
                                0,
4913
                                0,
4914
                                $results_disabled,
4915
                                $showTotalScoreAndUserChoicesInLastAttempt
4916
                            );
4917
                        } elseif (FILL_IN_BLANKS == $answerType) {
4918
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4919
                                $this,
4920
                                $feedback_type,
4921
                                $answer,
4922
                                0,
4923
                                0,
4924
                                $results_disabled,
4925
                                '',
4926
                                $showTotalScoreAndUserChoicesInLastAttempt
4927
                            );
4928
                        } elseif (CALCULATED_ANSWER == $answerType) {
4929
                            ExerciseShowFunctions::display_calculated_answer(
4930
                                $this,
4931
                                $feedback_type,
4932
                                $answer,
4933
                                0,
4934
                                0,
4935
                                $results_disabled,
4936
                                $showTotalScoreAndUserChoicesInLastAttempt,
4937
                                $expectedAnswer,
4938
                                $calculatedChoice,
4939
                                $calculatedStatus
4940
                            );
4941
                        } elseif (FREE_ANSWER == $answerType) {
4942
                            ExerciseShowFunctions::display_free_answer(
4943
                                $feedback_type,
4944
                                $choice,
4945
                                $exeId,
4946
                                $questionId,
4947
                                $questionScore,
4948
                                $results_disabled
4949
                            );
4950
                        } elseif (ORAL_EXPRESSION == $answerType) {
4951
                            // to store the details of open questions in an array to be used in mail
4952
                            /** @var OralExpression $objQuestionTmp */
4953
                            ExerciseShowFunctions::display_oral_expression_answer(
4954
                                $feedback_type,
4955
                                $choice,
4956
                                0,
4957
                                0,
4958
                                $objQuestionTmp->getFileUrl(true),
4959
                                $results_disabled,
4960
                                $questionScore
4961
                            );
4962
                        } elseif (HOT_SPOT == $answerType) {
4963
                            $correctAnswerId = 0;
4964
                            /** @var TrackEHotspot $hotspot */
4965
                            foreach ($orderedHotSpots as $correctAnswerId => $hotspot) {
4966
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4967
                                    break;
4968
                                }
4969
                            }
4970
4971
                            // force to show whether the choice is correct or not
4972
                            $showTotalScoreAndUserChoicesInLastAttempt = true;
4973
                            ExerciseShowFunctions::display_hotspot_answer(
4974
                                $feedback_type,
4975
                                ++$correctAnswerId,
4976
                                $answer,
4977
                                $studentChoice,
4978
                                $answerComment,
4979
                                $results_disabled,
4980
                                $correctAnswerId,
4981
                                $showTotalScoreAndUserChoicesInLastAttempt
4982
                            );
4983
                        } elseif (HOT_SPOT_ORDER == $answerType) {
4984
                            ExerciseShowFunctions::display_hotspot_order_answer(
4985
                                $feedback_type,
4986
                                $answerId,
4987
                                $answer,
4988
                                $studentChoice,
4989
                                $answerComment
4990
                            );
4991
                        } elseif (HOT_SPOT_DELINEATION == $answerType) {
4992
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4993
4994
                            // Round-up the coordinates
4995
                            $coords = explode('/', $user_answer);
4996
                            $coords = array_filter($coords);
4997
                            $user_array = '';
4998
                            foreach ($coords as $coord) {
4999
                                if (!empty($coord)) {
5000
                                    $parts = explode(';', $coord);
5001
                                    if (!empty($parts)) {
5002
                                        $user_array .= round($parts[0]).';'.round($parts[1]).'/';
5003
                                    }
5004
                                }
5005
                            }
5006
                            $user_array = substr($user_array, 0, -1) ?: '';
5007
                            if ($next) {
5008
                                $user_answer = $user_array;
5009
                                // We compare only the delineation not the other points
5010
                                $answer_question = $_SESSION['hotspot_coord'][$questionId][1];
5011
                                $answerDestination = $_SESSION['hotspot_dest'][$questionId][1];
5012
5013
                                // Calculating the area
5014
                                $poly_user = convert_coordinates($user_answer, '/');
5015
                                $poly_answer = convert_coordinates($answer_question, '|');
5016
                                $max_coord = poly_get_max($poly_user, $poly_answer);
5017
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
5018
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5019
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
5020
5021
                                $overlap = $poly_results['both'];
5022
                                $poly_answer_area = $poly_results['s1'];
5023
                                $poly_user_area = $poly_results['s2'];
5024
                                $missing = $poly_results['s1Only'];
5025
                                $excess = $poly_results['s2Only'];
5026
5027
                                // //this is an area in pixels
5028
                                if ($debug > 0) {
5029
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
5030
                                }
5031
5032
                                if ($overlap < 1) {
5033
                                    // Shortcut to avoid complicated calculations
5034
                                    $final_overlap = 0;
5035
                                    $final_missing = 100;
5036
                                    $final_excess = 100;
5037
                                } else {
5038
                                    // the final overlap is the percentage of the initial polygon
5039
                                    // that is overlapped by the user's polygon
5040
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
5041
                                    if ($debug > 1) {
5042
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
5043
                                    }
5044
                                    // the final missing area is the percentage of the initial polygon
5045
                                    // that is not overlapped by the user's polygon
5046
                                    $final_missing = 100 - $final_overlap;
5047
                                    if ($debug > 1) {
5048
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
5049
                                    }
5050
                                    // the final excess area is the percentage of the initial polygon's size
5051
                                    // that is covered by the user's polygon outside of the initial polygon
5052
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
5053
                                    if ($debug > 1) {
5054
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
5055
                                    }
5056
                                }
5057
5058
                                // Checking the destination parameters parsing the "@@"
5059
                                $destination_items = explode('@@', $answerDestination);
5060
                                $threadhold_total = $destination_items[0];
5061
                                $threadhold_items = explode(';', $threadhold_total);
5062
                                $threadhold1 = $threadhold_items[0]; // overlap
5063
                                $threadhold2 = $threadhold_items[1]; // excess
5064
                                $threadhold3 = $threadhold_items[2]; // missing
5065
5066
                                // if is delineation
5067
                                if (1 === $answerId) {
5068
                                    //setting colors
5069
                                    if ($final_overlap >= $threadhold1) {
5070
                                        $overlap_color = true;
5071
                                    }
5072
                                    if ($final_excess <= $threadhold2) {
5073
                                        $excess_color = true;
5074
                                    }
5075
                                    if ($final_missing <= $threadhold3) {
5076
                                        $missing_color = true;
5077
                                    }
5078
5079
                                    // if pass
5080
                                    if ($final_overlap >= $threadhold1 &&
5081
                                        $final_missing <= $threadhold3 &&
5082
                                        $final_excess <= $threadhold2
5083
                                    ) {
5084
                                        $next = 1; //go to the oars
5085
                                        $result_comment = get_lang('Acceptable');
5086
                                        $final_answer = 1; // do not update with  update_exercise_attempt
5087
                                    } else {
5088
                                        $next = 0;
5089
                                        $result_comment = get_lang('Unacceptable');
5090
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
5091
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
5092
                                        // checking the destination parameters parsing the "@@"
5093
                                        $destination_items = explode('@@', $answerDestination);
5094
                                    }
5095
                                } elseif ($answerId > 1) {
5096
                                    if ('noerror' == $objAnswerTmp->selectHotspotType($answerId)) {
5097
                                        if ($debug > 0) {
5098
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
5099
                                        }
5100
                                        //type no error shouldn't be treated
5101
                                        $next = 1;
5102
5103
                                        continue;
5104
                                    }
5105
                                    if ($debug > 0) {
5106
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
5107
                                    }
5108
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
5109
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
5110
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
5111
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5112
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
5113
5114
                                    if (false == $overlap) {
5115
                                        //all good, no overlap
5116
                                        $next = 1;
5117
5118
                                        continue;
5119
                                    } else {
5120
                                        if ($debug > 0) {
5121
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
5122
                                        }
5123
                                        $organs_at_risk_hit++;
5124
                                        //show the feedback
5125
                                        $next = 0;
5126
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
5127
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
5128
5129
                                        $destination_items = explode('@@', $answerDestination);
5130
                                        $try_hotspot = $destination_items[1];
5131
                                        $lp_hotspot = $destination_items[2];
5132
                                        $select_question_hotspot = $destination_items[3];
5133
                                        $url_hotspot = $destination_items[4];
5134
                                    }
5135
                                }
5136
                            } else {
5137
                                // the first delineation feedback
5138
                                if ($debug > 0) {
5139
                                    error_log(__LINE__.' first', 0);
5140
                                }
5141
                            }
5142
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
5143
                            echo '<tr>';
5144
                            echo Display::tag('td', $answerMatching[$answerId]);
5145
                            echo Display::tag(
5146
                                'td',
5147
                                "$user_answer / ".Display::tag(
5148
                                    'strong',
5149
                                    $answerMatching[$answerCorrect],
5150
                                    ['style' => 'color: #008000; font-weight: bold;']
5151
                                )
5152
                            );
5153
                            echo '</tr>';
5154
                        } elseif (ANNOTATION == $answerType) {
5155
                            ExerciseShowFunctions::displayAnnotationAnswer(
5156
                                $feedback_type,
5157
                                $exeId,
5158
                                $questionId,
5159
                                $questionScore,
5160
                                $results_disabled
5161
                            );
5162
                        }
5163
                    }
5164
                } else {
5165
                    if ($debug) {
5166
                        error_log('Showing questions $from '.$from);
5167
                    }
5168
5169
                    switch ($answerType) {
5170
                        case UNIQUE_ANSWER:
5171
                        case UNIQUE_ANSWER_IMAGE:
5172
                        case UNIQUE_ANSWER_NO_OPTION:
5173
                        case MULTIPLE_ANSWER:
5174
                        case GLOBAL_MULTIPLE_ANSWER:
5175
                        case MULTIPLE_ANSWER_COMBINATION:
5176
                        case READING_COMPREHENSION:
5177
                            if (1 == $answerId) {
5178
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
5179
                                    $this,
5180
                                    $feedback_type,
5181
                                    $answerType,
5182
                                    $studentChoice,
5183
                                    $answer,
5184
                                    $answerComment,
5185
                                    $answerCorrect,
5186
                                    $exeId,
5187
                                    $questionId,
5188
                                    $answerId,
5189
                                    $results_disabled,
5190
                                    $showTotalScoreAndUserChoicesInLastAttempt,
5191
                                    $this->export
5192
                                );
5193
                            } else {
5194
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
5195
                                    $this,
5196
                                    $feedback_type,
5197
                                    $answerType,
5198
                                    $studentChoice,
5199
                                    $answer,
5200
                                    $answerComment,
5201
                                    $answerCorrect,
5202
                                    $exeId,
5203
                                    $questionId,
5204
                                    '',
5205
                                    $results_disabled,
5206
                                    $showTotalScoreAndUserChoicesInLastAttempt,
5207
                                    $this->export
5208
                                );
5209
                            }
5210
5211
                            break;
5212
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
5213
                            if (1 == $answerId) {
5214
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
5215
                                    $this,
5216
                                    $feedback_type,
5217
                                    $answerType,
5218
                                    $studentChoice,
5219
                                    $answer,
5220
                                    $answerComment,
5221
                                    $answerCorrect,
5222
                                    $exeId,
5223
                                    $questionId,
5224
                                    $answerId,
5225
                                    $results_disabled,
5226
                                    $showTotalScoreAndUserChoicesInLastAttempt
5227
                                );
5228
                            } else {
5229
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
5230
                                    $this,
5231
                                    $feedback_type,
5232
                                    $answerType,
5233
                                    $studentChoice,
5234
                                    $answer,
5235
                                    $answerComment,
5236
                                    $answerCorrect,
5237
                                    $exeId,
5238
                                    $questionId,
5239
                                    '',
5240
                                    $results_disabled,
5241
                                    $showTotalScoreAndUserChoicesInLastAttempt
5242
                                );
5243
                            }
5244
5245
                            break;
5246
                        case MULTIPLE_ANSWER_TRUE_FALSE:
5247
                            if (1 == $answerId) {
5248
                                ExerciseShowFunctions::display_multiple_answer_true_false(
5249
                                    $this,
5250
                                    $feedback_type,
5251
                                    $answerType,
5252
                                    $studentChoice,
5253
                                    $answer,
5254
                                    $answerComment,
5255
                                    $answerCorrect,
5256
                                    $exeId,
5257
                                    $questionId,
5258
                                    $answerId,
5259
                                    $results_disabled,
5260
                                    $showTotalScoreAndUserChoicesInLastAttempt
5261
                                );
5262
                            } else {
5263
                                ExerciseShowFunctions::display_multiple_answer_true_false(
5264
                                    $this,
5265
                                    $feedback_type,
5266
                                    $answerType,
5267
                                    $studentChoice,
5268
                                    $answer,
5269
                                    $answerComment,
5270
                                    $answerCorrect,
5271
                                    $exeId,
5272
                                    $questionId,
5273
                                    '',
5274
                                    $results_disabled,
5275
                                    $showTotalScoreAndUserChoicesInLastAttempt
5276
                                );
5277
                            }
5278
5279
                            break;
5280
                        case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
5281
                            if (1 == $answerId) {
5282
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5283
                                    $this,
5284
                                    $feedback_type,
5285
                                    $studentChoice,
5286
                                    $studentChoiceDegree,
5287
                                    $answer,
5288
                                    $answerComment,
5289
                                    $answerCorrect,
5290
                                    $questionId,
5291
                                    $results_disabled
5292
                                );
5293
                            } else {
5294
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5295
                                    $this,
5296
                                    $feedback_type,
5297
                                    $studentChoice,
5298
                                    $studentChoiceDegree,
5299
                                    $answer,
5300
                                    $answerComment,
5301
                                    $answerCorrect,
5302
                                    $questionId,
5303
                                    $results_disabled
5304
                                );
5305
                            }
5306
5307
                            break;
5308
                        case FILL_IN_BLANKS:
5309
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
5310
                                $this,
5311
                                $feedback_type,
5312
                                $answer,
5313
                                $exeId,
5314
                                $questionId,
5315
                                $results_disabled,
5316
                                $str,
5317
                                $showTotalScoreAndUserChoicesInLastAttempt
5318
                            );
5319
5320
                            break;
5321
                        case CALCULATED_ANSWER:
5322
                            ExerciseShowFunctions::display_calculated_answer(
5323
                                $this,
5324
                                $feedback_type,
5325
                                $answer,
5326
                                $exeId,
5327
                                $questionId,
5328
                                $results_disabled,
5329
                                '',
5330
                                $showTotalScoreAndUserChoicesInLastAttempt
5331
                            );
5332
5333
                            break;
5334
                        case FREE_ANSWER:
5335
                            echo ExerciseShowFunctions::display_free_answer(
5336
                                $feedback_type,
5337
                                $choice,
5338
                                $exeId,
5339
                                $questionId,
5340
                                $questionScore,
5341
                                $results_disabled
5342
                            );
5343
5344
                            break;
5345
                        case ORAL_EXPRESSION:
5346
                            echo '<tr>
5347
                                <td valign="top">'.
5348
                                ExerciseShowFunctions::display_oral_expression_answer(
5349
                                    $feedback_type,
5350
                                    $choice,
5351
                                    $exeId,
5352
                                    $questionId,
5353
                                    $objQuestionTmp->getFileUrl(),
5354
                                    $results_disabled,
5355
                                    $questionScore
5356
                                ).'</td>
5357
                                </tr>
5358
                                </table>';
5359
5360
                            break;
5361
                        case HOT_SPOT:
5362
                            ExerciseShowFunctions::display_hotspot_answer(
5363
                                $feedback_type,
5364
                                $answerId,
5365
                                $answer,
5366
                                $studentChoice,
5367
                                $answerComment,
5368
                                $results_disabled,
5369
                                $answerId,
5370
                                $showTotalScoreAndUserChoicesInLastAttempt
5371
                            );
5372
5373
                            break;
5374
                        case HOT_SPOT_DELINEATION:
5375
                            $user_answer = $user_array;
5376
                            if ($next) {
5377
                                $user_answer = $user_array;
5378
                                // we compare only the delineation not the other points
5379
                                $answer_question = $_SESSION['hotspot_coord'][$questionId][1];
5380
                                $answerDestination = $_SESSION['hotspot_dest'][$questionId][1];
5381
5382
                                // calculating the area
5383
                                $poly_user = convert_coordinates($user_answer, '/');
5384
                                $poly_answer = convert_coordinates($answer_question, '|');
5385
                                $max_coord = poly_get_max($poly_user, $poly_answer);
5386
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
5387
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5388
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
5389
5390
                                $overlap = $poly_results['both'];
5391
                                $poly_answer_area = $poly_results['s1'];
5392
                                $poly_user_area = $poly_results['s2'];
5393
                                $missing = $poly_results['s1Only'];
5394
                                $excess = $poly_results['s2Only'];
5395
                                if ($debug > 0) {
5396
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
5397
                                }
5398
                                if ($overlap < 1) {
5399
                                    //shortcut to avoid complicated calculations
5400
                                    $final_overlap = 0;
5401
                                    $final_missing = 100;
5402
                                    $final_excess = 100;
5403
                                } else {
5404
                                    // the final overlap is the percentage of the initial polygon
5405
                                    // that is overlapped by the user's polygon
5406
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
5407
5408
                                    // the final missing area is the percentage of the initial polygon that
5409
                                    // is not overlapped by the user's polygon
5410
                                    $final_missing = 100 - $final_overlap;
5411
                                    // the final excess area is the percentage of the initial polygon's size that is
5412
                                    // covered by the user's polygon outside of the initial polygon
5413
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
5414
5415
                                    if ($debug > 1) {
5416
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap);
5417
                                        error_log(__LINE__.' - Final excess is '.$final_excess);
5418
                                        error_log(__LINE__.' - Final missing is '.$final_missing);
5419
                                    }
5420
                                }
5421
5422
                                // Checking the destination parameters parsing the "@@"
5423
                                $destination_items = explode('@@', $answerDestination);
5424
                                $threadhold_total = $destination_items[0];
5425
                                $threadhold_items = explode(';', $threadhold_total);
5426
                                $threadhold1 = $threadhold_items[0]; // overlap
5427
                                $threadhold2 = $threadhold_items[1]; // excess
5428
                                $threadhold3 = $threadhold_items[2]; //missing
5429
                                // if is delineation
5430
                                if (1 === $answerId) {
5431
                                    //setting colors
5432
                                    if ($final_overlap >= $threadhold1) {
5433
                                        $overlap_color = true;
5434
                                    }
5435
                                    if ($final_excess <= $threadhold2) {
5436
                                        $excess_color = true;
5437
                                    }
5438
                                    if ($final_missing <= $threadhold3) {
5439
                                        $missing_color = true;
5440
                                    }
5441
5442
                                    // if pass
5443
                                    if ($final_overlap >= $threadhold1 &&
5444
                                        $final_missing <= $threadhold3 &&
5445
                                        $final_excess <= $threadhold2
5446
                                    ) {
5447
                                        $next = 1; //go to the oars
5448
                                        $result_comment = get_lang('Acceptable');
5449
                                        $final_answer = 1; // do not update with  update_exercise_attempt
5450
                                    } else {
5451
                                        $next = 0;
5452
                                        $result_comment = get_lang('Unacceptable');
5453
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
5454
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
5455
                                        //checking the destination parameters parsing the "@@"
5456
                                        $destination_items = explode('@@', $answerDestination);
5457
                                    }
5458
                                } elseif ($answerId > 1) {
5459
                                    if ('noerror' === $objAnswerTmp->selectHotspotType($answerId)) {
5460
                                        if ($debug > 0) {
5461
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
5462
                                        }
5463
                                        //type no error shouldn't be treated
5464
                                        $next = 1;
5465
5466
                                        break;
5467
                                    }
5468
                                    if ($debug > 0) {
5469
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
5470
                                    }
5471
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
5472
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
5473
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
5474
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5475
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
5476
5477
                                    if (false == $overlap) {
5478
                                        //all good, no overlap
5479
                                        $next = 1;
5480
5481
                                        break;
5482
                                    } else {
5483
                                        if ($debug > 0) {
5484
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
5485
                                        }
5486
                                        $organs_at_risk_hit++;
5487
                                        //show the feedback
5488
                                        $next = 0;
5489
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
5490
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
5491
5492
                                        $destination_items = explode('@@', $answerDestination);
5493
                                        $try_hotspot = $destination_items[1];
5494
                                        $lp_hotspot = $destination_items[2];
5495
                                        $select_question_hotspot = $destination_items[3];
5496
                                        $url_hotspot = $destination_items[4];
5497
                                    }
5498
                                }
5499
                            }
5500
5501
                            break;
5502
                        case HOT_SPOT_ORDER:
5503
                            ExerciseShowFunctions::display_hotspot_order_answer(
5504
                                $feedback_type,
5505
                                $answerId,
5506
                                $answer,
5507
                                $studentChoice,
5508
                                $answerComment
5509
                            );
5510
5511
                            break;
5512
                        case DRAGGABLE:
5513
                        case MATCHING_DRAGGABLE:
5514
                        case MATCHING:
5515
                            echo '<tr>';
5516
                            echo Display::tag('td', $answerMatching[$answerId]);
5517
                            echo Display::tag(
5518
                                'td',
5519
                                "$user_answer / ".Display::tag(
5520
                                    'strong',
5521
                                    $answerMatching[$answerCorrect],
5522
                                    ['style' => 'color: #008000; font-weight: bold;']
5523
                                )
5524
                            );
5525
                            echo '</tr>';
5526
5527
                            break;
5528
                        case ANNOTATION:
5529
                            ExerciseShowFunctions::displayAnnotationAnswer(
5530
                                $feedback_type,
5531
                                $exeId,
5532
                                $questionId,
5533
                                $questionScore,
5534
                                $results_disabled
5535
                            );
5536
5537
                            break;
5538
                    }
5539
                }
5540
            }
5541
        } // end for that loops over all answers of the current question
5542
5543
        if ($debug) {
5544
            error_log('-- end answer loop --');
5545
        }
5546
5547
        $final_answer = true;
5548
5549
        foreach ($real_answers as $my_answer) {
5550
            if (!$my_answer) {
5551
                $final_answer = false;
5552
            }
5553
        }
5554
5555
        //we add the total score after dealing with the answers
5556
        if (MULTIPLE_ANSWER_COMBINATION == $answerType ||
5557
            MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE == $answerType
5558
        ) {
5559
            if ($final_answer) {
5560
                //getting only the first score where we save the weight of all the question
5561
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
5562
                $questionScore += $answerWeighting;
5563
            }
5564
        }
5565
5566
        $extra_data = [
5567
            'final_overlap' => $final_overlap,
5568
            'final_missing' => $final_missing,
5569
            'final_excess' => $final_excess,
5570
            'overlap_color' => $overlap_color,
5571
            'missing_color' => $missing_color,
5572
            'excess_color' => $excess_color,
5573
            'threadhold1' => $threadhold1,
5574
            'threadhold2' => $threadhold2,
5575
            'threadhold3' => $threadhold3,
5576
        ];
5577
5578
        if ('exercise_result' === $from) {
5579
            // if answer is hotspot. To the difference of exercise_show.php,
5580
            //  we use the results from the session (from_db=0)
5581
            // TODO Change this, because it is wrong to show the user
5582
            //  some results that haven't been stored in the database yet
5583
            if (HOT_SPOT == $answerType || HOT_SPOT_ORDER == $answerType || HOT_SPOT_DELINEATION == $answerType) {
5584
                if ($debug) {
5585
                    error_log('$from AND this is a hotspot kind of question ');
5586
                }
5587
                if (HOT_SPOT_DELINEATION === $answerType) {
5588
                    if ($showHotSpotDelineationTable) {
5589
                        if (!is_numeric($final_overlap)) {
5590
                            $final_overlap = 0;
5591
                        }
5592
                        if (!is_numeric($final_missing)) {
5593
                            $final_missing = 0;
5594
                        }
5595
                        if (!is_numeric($final_excess)) {
5596
                            $final_excess = 0;
5597
                        }
5598
5599
                        if ($final_overlap > 100) {
5600
                            $final_overlap = 100;
5601
                        }
5602
5603
                        $table_resume = '<table class="data_table">
5604
                                <tr class="row_odd" >
5605
                                    <td></td>
5606
                                    <td ><b>'.get_lang('Requirements').'</b></td>
5607
                                    <td><b>'.get_lang('Your answer').'</b></td>
5608
                                </tr>
5609
                                <tr class="row_even">
5610
                                    <td><b>'.get_lang('Overlapping areaping area').'</b></td>
5611
                                    <td>'.get_lang('Minimumimum').' '.$threadhold1.'</td>
5612
                                    <td class="text-right '.($overlap_color ? 'text-success' : 'text-danger').'">'
5613
                            .$final_overlap < 0 ? 0 : (int) $final_overlap.'</td>
5614
                                </tr>
5615
                                <tr>
5616
                                    <td><b>'.get_lang('Excessive areaive area').'</b></td>
5617
                                    <td>'.get_lang('max. 20 characters, e.g. <i>INNOV21</i>').' '.$threadhold2.'</td>
5618
                                    <td class="text-right '.($excess_color ? 'text-success' : 'text-danger').'">'
5619
                            .$final_excess < 0 ? 0 : (int) $final_excess.'</td>
5620
                                </tr>
5621
                                <tr class="row_even">
5622
                                    <td><b>'.get_lang('Missing area area').'</b></td>
5623
                                    <td>'.get_lang('max. 20 characters, e.g. <i>INNOV21</i>').' '.$threadhold3.'</td>
5624
                                    <td class="text-right '.($missing_color ? 'text-success' : 'text-danger').'">'
5625
                            .$final_missing < 0 ? 0 : (int) $final_missing.'</td>
5626
                                </tr>
5627
                            </table>';
5628
                        if (0 == $next) {
5629
                            /*$try = $try_hotspot;
5630
                            $lp = $lp_hotspot;
5631
                            $destinationid = $select_question_hotspot;
5632
                            $url = $url_hotspot;*/
5633
                        } else {
5634
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
5635
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
5636
                        }
5637
5638
                        $message = '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>
5639
                                    <p style="text-align:center">';
5640
                        $message .= '<p>'.get_lang('Your delineation :').'</p>';
5641
                        $message .= $table_resume;
5642
                        $message .= '<br />'.get_lang('Your result is :').' '.$result_comment.'<br />';
5643
                        if ($organs_at_risk_hit > 0) {
5644
                            $message .= '<p><b>'.get_lang('One (or more) area at risk has been hit').'</b></p>';
5645
                        }
5646
                        $message .= '<p>'.$comment.'</p>';
5647
                        echo $message;
5648
5649
                        $_SESSION['hotspot_delineation_result'][$this->selectId()][$questionId][0] = $message;
5650
                        $_SESSION['hotspot_delineation_result'][$this->selectId()][$questionId][1] = $_SESSION['exerciseResultCoordinates'][$questionId];
5651
                    } else {
5652
                        echo $hotspot_delineation_result[0];
5653
                    }
5654
5655
                    // Save the score attempts
5656
                    if (1) {
5657
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5658
                        $final_answer = isset($hotspot_delineation_result[1]) ? $hotspot_delineation_result[1] : '';
5659
                        if (0 == $final_answer) {
5660
                            $questionScore = 0;
5661
                        }
5662
                        // we always insert the answer_id 1 = delineation
5663
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5664
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5665
                        $hotspotValue = isset($hotspot_delineation_result[1]) ? 1 === (int) $hotspot_delineation_result[1] ? 1 : 0 : 0;
5666
                        Event::saveExerciseAttemptHotspot(
5667
                            $exeId,
5668
                            $quesId,
5669
                            1,
5670
                            $hotspotValue,
5671
                            $exerciseResultCoordinates[$quesId]
5672
                        );
5673
                    } else {
5674
                        if (0 == $final_answer) {
5675
                            $questionScore = 0;
5676
                            $answer = 0;
5677
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5678
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5679
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5680
                                    Event::saveExerciseAttemptHotspot(
5681
                                        $exeId,
5682
                                        $quesId,
5683
                                        $idx,
5684
                                        0,
5685
                                        $val
5686
                                    );
5687
                                }
5688
                            }
5689
                        } else {
5690
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5691
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5692
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5693
                                    $hotspotValue = 1 === (int) $choice[$idx] ? 1 : 0;
5694
                                    Event::saveExerciseAttemptHotspot(
5695
                                        $exeId,
5696
                                        $quesId,
5697
                                        $idx,
5698
                                        $hotspotValue,
5699
                                        $val
5700
                                    );
5701
                                }
5702
                            }
5703
                        }
5704
                    }
5705
                }
5706
            }
5707
5708
            $relPath = api_get_path(WEB_CODE_PATH);
5709
5710
            if (HOT_SPOT == $answerType || HOT_SPOT_ORDER == $answerType) {
5711
                // We made an extra table for the answers
5712
                if ($show_result) {
5713
                    echo '</table></td></tr>';
5714
                    echo '
5715
                        <tr>
5716
                            <td colspan="2">
5717
                                <p><em>'.get_lang('Image zones')."</em></p>
5718
                                <div id=\"hotspot-solution-$questionId\"></div>
5719
                                <script>
5720
                                    $(function() {
5721
                                        new HotspotQuestion({
5722
                                            questionId: $questionId,
5723
                                            exerciseId: {$this->id},
5724
                                            exeId: $exeId,
5725
                                            selector: '#hotspot-solution-$questionId',
5726
                                            for: 'solution',
5727
                                            relPath: '$relPath'
5728
                                        });
5729
                                    });
5730
                                </script>
5731
                            </td>
5732
                        </tr>
5733
                    ";
5734
                }
5735
            } elseif (ANNOTATION == $answerType) {
5736
                if ($show_result) {
5737
                    echo '
5738
                        <p><em>'.get_lang('Annotation').'</em></p>
5739
                        <div id="annotation-canvas-'.$questionId.'"></div>
5740
                        <script>
5741
                            AnnotationQuestion({
5742
                                questionId: parseInt('.$questionId.'),
5743
                                exerciseId: parseInt('.$exeId.'),
5744
                                relPath: \''.$relPath.'\',
5745
                                courseId: parseInt('.$course_id.')
5746
                            });
5747
                        </script>
5748
                    ';
5749
                }
5750
            }
5751
5752
            //if ($origin != 'learnpath') {
5753
            if ($show_result && ANNOTATION != $answerType) {
5754
                echo '</table>';
5755
            }
5756
            //	}
5757
        }
5758
        unset($objAnswerTmp);
5759
5760
        $totalWeighting += $questionWeighting;
5761
        // Store results directly in the database
5762
        // For all in one page exercises, the results will be
5763
        // stored by exercise_results.php (using the session)
5764
        if ($save_results) {
5765
            if ($debug) {
5766
                error_log("Save question results $save_results");
5767
                error_log('choice: ');
5768
                error_log(print_r($choice, 1));
5769
            }
5770
5771
            if (empty($choice)) {
5772
                $choice = 0;
5773
            }
5774
            // with certainty degree
5775
            if (empty($choiceDegreeCertainty)) {
5776
                $choiceDegreeCertainty = 0;
5777
            }
5778
            if (MULTIPLE_ANSWER_TRUE_FALSE == $answerType ||
5779
                MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE == $answerType ||
5780
                MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $answerType
5781
            ) {
5782
                if (0 != $choice) {
5783
                    $reply = array_keys($choice);
5784
                    $countReply = count($reply);
5785
                    for ($i = 0; $i < $countReply; $i++) {
5786
                        $chosenAnswer = $reply[$i];
5787
                        if (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $answerType) {
5788
                            if (0 != $choiceDegreeCertainty) {
5789
                                $replyDegreeCertainty = array_keys($choiceDegreeCertainty);
5790
                                $answerDegreeCertainty = isset($replyDegreeCertainty[$i]) ? $replyDegreeCertainty[$i] : '';
5791
                                $answerValue = isset($choiceDegreeCertainty[$answerDegreeCertainty]) ? $choiceDegreeCertainty[$answerDegreeCertainty] : '';
5792
                                Event::saveQuestionAttempt(
5793
                                    $questionScore,
5794
                                    $chosenAnswer.':'.$choice[$chosenAnswer].':'.$answerValue,
5795
                                    $quesId,
5796
                                    $exeId,
5797
                                    $i,
5798
                                    $this->id,
5799
                                    $updateResults
5800
                                );
5801
                            }
5802
                        } else {
5803
                            Event::saveQuestionAttempt(
5804
                                $questionScore,
5805
                                $chosenAnswer.':'.$choice[$chosenAnswer],
5806
                                $quesId,
5807
                                $exeId,
5808
                                $i,
5809
                                $this->id,
5810
                                $updateResults
5811
                            );
5812
                        }
5813
                        if ($debug) {
5814
                            error_log('result =>'.$questionScore.' '.$chosenAnswer.':'.$choice[$chosenAnswer]);
5815
                        }
5816
                    }
5817
                } else {
5818
                    Event::saveQuestionAttempt(
5819
                        $questionScore,
5820
                        0,
5821
                        $quesId,
5822
                        $exeId,
5823
                        0,
5824
                        $this->id
5825
                    );
5826
                }
5827
            } elseif (MULTIPLE_ANSWER == $answerType || GLOBAL_MULTIPLE_ANSWER == $answerType) {
5828
                if (0 != $choice) {
5829
                    $reply = array_keys($choice);
5830
                    for ($i = 0; $i < count($reply); $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...
5831
                        $ans = $reply[$i];
5832
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5833
                    }
5834
                } else {
5835
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5836
                }
5837
            } elseif (MULTIPLE_ANSWER_COMBINATION == $answerType) {
5838
                if (0 != $choice) {
5839
                    $reply = array_keys($choice);
5840
                    for ($i = 0; $i < count($reply); $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...
5841
                        $ans = $reply[$i];
5842
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5843
                    }
5844
                } else {
5845
                    Event::saveQuestionAttempt(
5846
                        $questionScore,
5847
                        0,
5848
                        $quesId,
5849
                        $exeId,
5850
                        0,
5851
                        $this->id
5852
                    );
5853
                }
5854
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5855
                if (isset($matching)) {
5856
                    foreach ($matching as $j => $val) {
5857
                        Event::saveQuestionAttempt(
5858
                            $questionScore,
5859
                            $val,
5860
                            $quesId,
5861
                            $exeId,
5862
                            $j,
5863
                            $this->id
5864
                        );
5865
                    }
5866
                }
5867
            } elseif (FREE_ANSWER == $answerType) {
5868
                $answer = $choice;
5869
                Event::saveQuestionAttempt(
5870
                    $questionScore,
5871
                    $answer,
5872
                    $quesId,
5873
                    $exeId,
5874
                    0,
5875
                    $this->id
5876
                );
5877
            } elseif (ORAL_EXPRESSION == $answerType) {
5878
                $answer = $choice;
5879
                Event::saveQuestionAttempt(
5880
                    $questionScore,
5881
                    $answer,
5882
                    $quesId,
5883
                    $exeId,
5884
                    0,
5885
                    $this->id,
5886
                    false,
5887
                    $objQuestionTmp->getAbsoluteFilePath()
5888
                );
5889
            } elseif (
5890
                in_array(
5891
                    $answerType,
5892
                    [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION]
5893
                )
5894
            ) {
5895
                $answer = $choice;
5896
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5897
            } elseif (HOT_SPOT == $answerType || ANNOTATION == $answerType) {
5898
                $answer = [];
5899
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5900
                    if ($debug) {
5901
                        error_log('Checking result coordinates');
5902
                    }
5903
                    Database::delete(
5904
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5905
                        [
5906
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5907
                                $exeId,
5908
                                $questionId,
5909
                                api_get_course_int_id(),
5910
                            ],
5911
                        ]
5912
                    );
5913
5914
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5915
                        $answer[] = $val;
5916
                        $hotspotValue = 1 === (int) $choice[$idx] ? 1 : 0;
5917
                        if ($debug) {
5918
                            error_log('Hotspot value: '.$hotspotValue);
5919
                        }
5920
                        Event::saveExerciseAttemptHotspot(
5921
                            $exeId,
5922
                            $quesId,
5923
                            $idx,
5924
                            $hotspotValue,
5925
                            $val,
5926
                            false,
5927
                            $this->id
5928
                        );
5929
                    }
5930
                } else {
5931
                    if ($debug) {
5932
                        error_log('Empty: exerciseResultCoordinates');
5933
                    }
5934
                }
5935
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5936
            } else {
5937
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5938
            }
5939
        }
5940
5941
        if (0 == $propagate_neg && $questionScore < 0) {
5942
            $questionScore = 0;
5943
        }
5944
5945
        if ($save_results) {
5946
            $statsTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5947
            $sql = "UPDATE $statsTable SET
5948
                        score = score + ".(float) $questionScore."
5949
                    WHERE exe_id = $exeId";
5950
            Database::query($sql);
5951
        }
5952
5953
        $return = [
5954
            'score' => $questionScore,
5955
            'weight' => $questionWeighting,
5956
            'extra' => $extra_data,
5957
            'open_question' => $arrques,
5958
            'open_answer' => $arrans,
5959
            'answer_type' => $answerType,
5960
            'generated_oral_file' => $generatedFile,
5961
            'user_answered' => $userAnsweredQuestion,
5962
            'correct_answer_id' => $correctAnswerId,
5963
            'answer_destination' => $answerDestination,
5964
        ];
5965
5966
        return $return;
5967
    }
5968
5969
    /**
5970
     * Sends a notification when a user ends an examn.
5971
     *
5972
     * @param string $type                  'start' or 'end' of an exercise
5973
     * @param array  $question_list_answers
5974
     * @param string $origin
5975
     * @param int    $exe_id
5976
     * @param float  $score
5977
     * @param float  $weight
5978
     *
5979
     * @return bool
5980
     */
5981
    public function send_mail_notification_for_exam(
5982
        $type = 'end',
5983
        $question_list_answers,
5984
        $origin,
5985
        $exe_id,
5986
        $score = null,
5987
        $weight = null
5988
    ) {
5989
        $setting = api_get_course_setting('email_alert_manager_on_new_quiz');
5990
5991
        if (empty($setting) && empty($this->getNotifications())) {
5992
            return false;
5993
        }
5994
5995
        $settingFromExercise = $this->getNotifications();
5996
        if (!empty($settingFromExercise)) {
5997
            $setting = $settingFromExercise;
5998
        }
5999
6000
        // Email configuration settings
6001
        $courseCode = api_get_course_id();
6002
        $courseInfo = api_get_course_info($courseCode);
6003
6004
        if (empty($courseInfo)) {
6005
            return false;
6006
        }
6007
6008
        $sessionId = api_get_session_id();
6009
6010
        $sessionData = '';
6011
        if (!empty($sessionId)) {
6012
            $sessionInfo = api_get_session_info($sessionId);
6013
            if (!empty($sessionInfo)) {
6014
                $sessionData = '<tr>'
6015
                    .'<td>'.get_lang('Session name').'</td>'
6016
                    .'<td>'.$sessionInfo['name'].'</td>'
6017
                    .'</tr>';
6018
            }
6019
        }
6020
6021
        $sendStart = false;
6022
        $sendEnd = false;
6023
        $sendEndOpenQuestion = false;
6024
        $sendEndOralQuestion = false;
6025
6026
        foreach ($setting as $option) {
6027
            switch ($option) {
6028
                case 0:
6029
                    return false;
6030
6031
                    break;
6032
                case 1: // End
6033
                    if ('end' == $type) {
6034
                        $sendEnd = true;
6035
                    }
6036
6037
                    break;
6038
                case 2: // start
6039
                    if ('start' == $type) {
6040
                        $sendStart = true;
6041
                    }
6042
6043
                    break;
6044
                case 3: // end + open
6045
                    if ('end' == $type) {
6046
                        $sendEndOpenQuestion = true;
6047
                    }
6048
6049
                    break;
6050
                case 4: // end + oral
6051
                    if ('end' == $type) {
6052
                        $sendEndOralQuestion = true;
6053
                    }
6054
6055
                    break;
6056
            }
6057
        }
6058
6059
        $user_info = api_get_user_info(api_get_user_id());
6060
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.
6061
            api_get_cidreq(true, true, 'qualify').'&id='.$exe_id.'&action=qualify';
6062
6063
        if (!empty($sessionId)) {
6064
            $addGeneralCoach = true;
6065
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
6066
            if (true === $setting) {
6067
                $addGeneralCoach = false;
6068
            }
6069
            $teachers = CourseManager::get_coach_list_from_course_code(
6070
                $courseCode,
6071
                $sessionId,
6072
                $addGeneralCoach
6073
            );
6074
        } else {
6075
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
6076
        }
6077
6078
        if ($sendEndOpenQuestion) {
6079
            $this->sendNotificationForOpenQuestions(
6080
                $question_list_answers,
6081
                $origin,
6082
                $user_info,
6083
                $url,
6084
                $teachers
6085
            );
6086
        }
6087
6088
        if ($sendEndOralQuestion) {
6089
            $this->sendNotificationForOralQuestions(
6090
                $question_list_answers,
6091
                $origin,
6092
                $exe_id,
6093
                $user_info,
6094
                $url,
6095
                $teachers
6096
            );
6097
        }
6098
6099
        if (!$sendEnd && !$sendStart) {
6100
            return false;
6101
        }
6102
6103
        $scoreLabel = '';
6104
        if ($sendEnd &&
6105
            true == api_get_configuration_value('send_score_in_exam_notification_mail_to_manager')
6106
        ) {
6107
            $notificationPercentage = api_get_configuration_value('send_notification_score_in_percentage');
6108
            $scoreLabel = ExerciseLib::show_score($score, $weight, $notificationPercentage, true);
6109
            $scoreLabel = '<tr>
6110
                            <td>'.get_lang('Score')."</td>
6111
                            <td>&nbsp;$scoreLabel</td>
6112
                        </tr>";
6113
        }
6114
6115
        if ($sendEnd) {
6116
            $msg = get_lang('A learner attempted an exercise').'<br /><br />';
6117
        } else {
6118
            $msg = get_lang('Student just started an exercise').'<br /><br />';
6119
        }
6120
6121
        $msg .= get_lang('Attempt details').' : <br /><br />
6122
                    <table>
6123
                        <tr>
6124
                            <td>'.get_lang('Course name').'</td>
6125
                            <td>#course#</td>
6126
                        </tr>
6127
                        '.$sessionData.'
6128
                        <tr>
6129
                            <td>'.get_lang('Test').'</td>
6130
                            <td>&nbsp;#exercise#</td>
6131
                        </tr>
6132
                        <tr>
6133
                            <td>'.get_lang('Learner name').'</td>
6134
                            <td>&nbsp;#student_complete_name#</td>
6135
                        </tr>
6136
                        <tr>
6137
                            <td>'.get_lang('Learner e-mail').'</td>
6138
                            <td>&nbsp;#email#</td>
6139
                        </tr>
6140
                        '.$scoreLabel.'
6141
                    </table>';
6142
6143
        $variables = [
6144
            '#email#' => $user_info['email'],
6145
            '#exercise#' => $this->exercise,
6146
            '#student_complete_name#' => $user_info['complete_name'],
6147
            '#course#' => Display::url(
6148
                $courseInfo['title'],
6149
                $courseInfo['course_public_url'].'?sid='.$sessionId
6150
            ),
6151
        ];
6152
6153
        if ($sendEnd) {
6154
            $msg .= '<br /><a href="#url#">'.get_lang('Click this link to check the answer and/or give feedback').'</a>';
6155
            $variables['#url#'] = $url;
6156
        }
6157
6158
        $content = str_replace(array_keys($variables), array_values($variables), $msg);
6159
6160
        if ($sendEnd) {
6161
            $subject = get_lang('A learner attempted an exercise');
6162
        } else {
6163
            $subject = get_lang('Student just started an exercise');
6164
        }
6165
6166
        if (!empty($teachers)) {
6167
            foreach ($teachers as $user_id => $teacher_data) {
6168
                MessageManager::send_message_simple(
6169
                    $user_id,
6170
                    $subject,
6171
                    $content
6172
                );
6173
            }
6174
        }
6175
    }
6176
6177
    /**
6178
     * @param array $user_data         result of api_get_user_info()
6179
     * @param array $trackExerciseInfo result of get_stat_track_exercise_info
6180
     *
6181
     * @return string
6182
     */
6183
    public function showExerciseResultHeader(
6184
        $user_data,
6185
        $trackExerciseInfo,
6186
        $saveUserResult
6187
    ) {
6188
        if (api_get_configuration_value('hide_user_info_in_quiz_result')) {
6189
            return '';
6190
        }
6191
6192
        $start_date = null;
6193
6194
        if (isset($trackExerciseInfo['start_date'])) {
6195
            $start_date = api_convert_and_format_date($trackExerciseInfo['start_date']);
6196
        }
6197
        $duration = isset($trackExerciseInfo['duration_formatted']) ? $trackExerciseInfo['duration_formatted'] : null;
6198
        $ip = isset($trackExerciseInfo['user_ip']) ? $trackExerciseInfo['user_ip'] : null;
6199
6200
        if (!empty($user_data)) {
6201
            $userFullName = $user_data['complete_name'];
6202
            if (api_is_teacher() || api_is_platform_admin(true, true)) {
6203
                $userFullName = '<a href="'.$user_data['profile_url'].'" title="'.get_lang('GoToStudentDetails').'">'.
6204
                    $user_data['complete_name'].'</a>';
6205
            }
6206
6207
            $data = [
6208
                'name_url' => $userFullName,
6209
                'complete_name' => $user_data['complete_name'],
6210
                'username' => $user_data['username'],
6211
                'avatar' => $user_data['avatar_medium'],
6212
                'url' => $user_data['profile_url'],
6213
            ];
6214
6215
            if (!empty($user_data['official_code'])) {
6216
                $data['code'] = $user_data['official_code'];
6217
            }
6218
        }
6219
        // Description can be very long and is generally meant to explain
6220
        //   rules *before* the exam. Leaving here to make display easier if
6221
        //   necessary
6222
        /*
6223
        if (!empty($this->description)) {
6224
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
6225
        }
6226
        */
6227
        if (!empty($start_date)) {
6228
            $data['start_date'] = $start_date;
6229
        }
6230
6231
        if (!empty($duration)) {
6232
            $data['duration'] = $duration;
6233
        }
6234
6235
        if (!empty($ip)) {
6236
            $data['ip'] = $ip;
6237
        }
6238
6239
        if (api_get_configuration_value('save_titles_as_html')) {
6240
            $data['title'] = $this->get_formated_title().get_lang('Result');
6241
        } else {
6242
            $data['title'] = PHP_EOL.$this->exercise.' : '.get_lang('Result');
6243
        }
6244
6245
        $questionsCount = count(explode(',', $trackExerciseInfo['data_tracking']));
6246
        $savedAnswersCount = $this->countUserAnswersSavedInExercise($trackExerciseInfo['exe_id']);
6247
6248
        $data['number_of_answers'] = $questionsCount;
6249
        $data['number_of_answers_saved'] = $savedAnswersCount;
6250
6251
        if (false !== api_get_configuration_value('quiz_confirm_saved_answers')) {
6252
            $em = Database::getManager();
6253
6254
            if ($saveUserResult) {
6255
                $trackConfirmation = new TrackEExerciseConfirmation();
6256
                $trackConfirmation
6257
                    ->setUserId($trackExerciseInfo['exe_user_id'])
6258
                    ->setQuizId($trackExerciseInfo['exe_exo_id'])
6259
                    ->setAttemptId($trackExerciseInfo['exe_id'])
6260
                    ->setQuestionsCount($questionsCount)
6261
                    ->setSavedAnswersCount($savedAnswersCount)
6262
                    ->setCourseId($trackExerciseInfo['c_id'])
6263
                    ->setSessionId($trackExerciseInfo['session_id'])
6264
                    ->setCreatedAt(api_get_utc_datetime(null, false, true));
6265
6266
                $em->persist($trackConfirmation);
6267
                $em->flush();
6268
            } else {
6269
                $trackConfirmation = $em
6270
                    ->getRepository('ChamiloCoreBundle:TrackEExerciseConfirmation')
6271
                    ->findOneBy(
6272
                        [
6273
                            'attemptId' => $trackExerciseInfo['exe_id'],
6274
                            'quizId' => $trackExerciseInfo['exe_exo_id'],
6275
                            'courseId' => $trackExerciseInfo['c_id'],
6276
                            'sessionId' => $trackExerciseInfo['session_id'],
6277
                        ]
6278
                    );
6279
            }
6280
6281
            $data['track_confirmation'] = $trackConfirmation;
6282
        }
6283
6284
        $tpl = new Template(null, false, false, false, false, false, false);
6285
        $tpl->assign('data', $data);
6286
        $layoutTemplate = $tpl->get_template('exercise/partials/result_exercise.tpl');
6287
6288
        return $tpl->fetch($layoutTemplate);
6289
    }
6290
6291
    /**
6292
     * Returns the exercise result.
6293
     *
6294
     * @param 	int		attempt id
6295
     *
6296
     * @return array
6297
     */
6298
    public function get_exercise_result($exe_id)
6299
    {
6300
        $result = [];
6301
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
6302
6303
        if (!empty($track_exercise_info)) {
6304
            $totalScore = 0;
6305
            $objExercise = new self();
6306
            $objExercise->read($track_exercise_info['exe_exo_id']);
6307
            if (!empty($track_exercise_info['data_tracking'])) {
6308
                $question_list = explode(',', $track_exercise_info['data_tracking']);
6309
            }
6310
            foreach ($question_list as $questionId) {
6311
                $question_result = $objExercise->manage_answer(
6312
                    $exe_id,
6313
                    $questionId,
6314
                    '',
6315
                    'exercise_show',
6316
                    [],
6317
                    false,
6318
                    true,
6319
                    false,
6320
                    $objExercise->selectPropagateNeg()
6321
                );
6322
                $totalScore += $question_result['score'];
6323
            }
6324
6325
            if (0 == $objExercise->selectPropagateNeg() && $totalScore < 0) {
6326
                $totalScore = 0;
6327
            }
6328
            $result = [
6329
                'score' => $totalScore,
6330
                'weight' => $track_exercise_info['max_score'],
6331
            ];
6332
        }
6333
6334
        return $result;
6335
    }
6336
6337
    /**
6338
     * Checks if the exercise is visible due a lot of conditions
6339
     * visibility, time limits, student attempts
6340
     * Return associative array
6341
     * value : true if exercise visible
6342
     * message : HTML formatted message
6343
     * rawMessage : text message.
6344
     *
6345
     * @param int  $lpId
6346
     * @param int  $lpItemId
6347
     * @param int  $lpItemViewId
6348
     * @param bool $filterByAdmin
6349
     *
6350
     * @return array
6351
     */
6352
    public function is_visible(
6353
        $lpId = 0,
6354
        $lpItemId = 0,
6355
        $lpItemViewId = 0,
6356
        $filterByAdmin = true
6357
    ) {
6358
        // 1. By default the exercise is visible
6359
        $isVisible = true;
6360
        $message = null;
6361
6362
        // 1.1 Admins and teachers can access to the exercise
6363
        if ($filterByAdmin) {
6364
            if (api_is_platform_admin() || api_is_course_admin()) {
6365
                return ['value' => true, 'message' => ''];
6366
            }
6367
        }
6368
6369
        // Deleted exercise.
6370
        if (-1 == $this->active) {
6371
            return [
6372
                'value' => false,
6373
                'message' => Display::return_message(
6374
                    get_lang('TestNotFound'),
6375
                    'warning',
6376
                    false
6377
                ),
6378
                'rawMessage' => get_lang('TestNotFound'),
6379
            ];
6380
        }
6381
6382
        $repo = Container::getExerciseRepository();
6383
        $exercise = $repo->find($this->iId);
6384
6385
        if (null === $exercise) {
6386
            return [];
6387
        }
6388
6389
        $link = $exercise->getFirstResourceLinkFromCourseSession(api_get_course_entity($this->course_id));
6390
6391
        if ($link->isDraft()) {
6392
            $this->active = 0;
6393
        }
6394
6395
        // 2. If the exercise is not active.
6396
        if (empty($lpId)) {
6397
            // 2.1 LP is OFF
6398
            if (0 == $this->active) {
6399
                return [
6400
                    'value' => false,
6401
                    'message' => Display::return_message(
6402
                        get_lang('TestNotFound'),
6403
                        'warning',
6404
                        false
6405
                    ),
6406
                    'rawMessage' => get_lang('TestNotFound'),
6407
                ];
6408
            }
6409
        } else {
6410
            $lp = Container::getLpRepository()->find($lpId);
6411
            // 2.1 LP is loaded
6412
            if ($lp && 0 == $this->active &&
6413
                !learnpath::is_lp_visible_for_student($lp, api_get_user_id())
6414
            ) {
6415
                return [
6416
                    'value' => false,
6417
                    'message' => Display::return_message(
6418
                        get_lang('TestNotFound'),
6419
                        'warning',
6420
                        false
6421
                    ),
6422
                    'rawMessage' => get_lang('TestNotFound'),
6423
                ];
6424
            }
6425
        }
6426
6427
        // 3. We check if the time limits are on
6428
        $limitTimeExists = false;
6429
        if (!empty($this->start_time) || !empty($this->end_time)) {
6430
            $limitTimeExists = true;
6431
        }
6432
6433
        if ($limitTimeExists) {
6434
            $timeNow = time();
6435
            $existsStartDate = false;
6436
            $nowIsAfterStartDate = true;
6437
            $existsEndDate = false;
6438
            $nowIsBeforeEndDate = true;
6439
6440
            if (!empty($this->start_time)) {
6441
                $existsStartDate = true;
6442
            }
6443
6444
            if (!empty($this->end_time)) {
6445
                $existsEndDate = true;
6446
            }
6447
6448
            // check if we are before-or-after end-or-start date
6449
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
6450
                $nowIsAfterStartDate = false;
6451
            }
6452
6453
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
6454
                $nowIsBeforeEndDate = false;
6455
            }
6456
6457
            // lets check all cases
6458
            if ($existsStartDate && !$existsEndDate) {
6459
                // exists start date and dont exists end date
6460
                if ($nowIsAfterStartDate) {
6461
                    // after start date, no end date
6462
                    $isVisible = true;
6463
                    $message = sprintf(
6464
                        get_lang('TestAvailableSinceX'),
6465
                        api_convert_and_format_date($this->start_time)
6466
                    );
6467
                } else {
6468
                    // before start date, no end date
6469
                    $isVisible = false;
6470
                    $message = sprintf(
6471
                        get_lang('TestAvailableFromX'),
6472
                        api_convert_and_format_date($this->start_time)
6473
                    );
6474
                }
6475
            } elseif (!$existsStartDate && $existsEndDate) {
6476
                // doesnt exist start date, exists end date
6477
                if ($nowIsBeforeEndDate) {
6478
                    // before end date, no start date
6479
                    $isVisible = true;
6480
                    $message = sprintf(
6481
                        get_lang('TestAvailableUntilX'),
6482
                        api_convert_and_format_date($this->end_time)
6483
                    );
6484
                } else {
6485
                    // after end date, no start date
6486
                    $isVisible = false;
6487
                    $message = sprintf(
6488
                        get_lang('TestAvailableUntilX'),
6489
                        api_convert_and_format_date($this->end_time)
6490
                    );
6491
                }
6492
            } elseif ($existsStartDate && $existsEndDate) {
6493
                // exists start date and end date
6494
                if ($nowIsAfterStartDate) {
6495
                    if ($nowIsBeforeEndDate) {
6496
                        // after start date and before end date
6497
                        $isVisible = true;
6498
                        $message = sprintf(
6499
                            get_lang('TestIsActivatedFromXToY'),
6500
                            api_convert_and_format_date($this->start_time),
6501
                            api_convert_and_format_date($this->end_time)
6502
                        );
6503
                    } else {
6504
                        // after start date and after end date
6505
                        $isVisible = false;
6506
                        $message = sprintf(
6507
                            get_lang('TestWasActivatedFromXToY'),
6508
                            api_convert_and_format_date($this->start_time),
6509
                            api_convert_and_format_date($this->end_time)
6510
                        );
6511
                    }
6512
                } else {
6513
                    if ($nowIsBeforeEndDate) {
6514
                        // before start date and before end date
6515
                        $isVisible = false;
6516
                        $message = sprintf(
6517
                            get_lang('TestWillBeActivatedFromXToY'),
6518
                            api_convert_and_format_date($this->start_time),
6519
                            api_convert_and_format_date($this->end_time)
6520
                        );
6521
                    }
6522
                    // case before start date and after end date is impossible
6523
                }
6524
            } elseif (!$existsStartDate && !$existsEndDate) {
6525
                // doesnt exist start date nor end date
6526
                $isVisible = true;
6527
                $message = '';
6528
            }
6529
        }
6530
6531
        // 4. We check if the student have attempts
6532
        if ($isVisible) {
6533
            $exerciseAttempts = $this->selectAttempts();
6534
6535
            if ($exerciseAttempts > 0) {
6536
                $attemptCount = Event::get_attempt_count_not_finished(
6537
                    api_get_user_id(),
6538
                    $this->id,
6539
                    $lpId,
6540
                    $lpItemId,
6541
                    $lpItemViewId
6542
                );
6543
6544
                if ($attemptCount >= $exerciseAttempts) {
6545
                    $message = sprintf(
6546
                        get_lang('Reachedmax. 20 characters, e.g. <i>INNOV21</i>Attempts'),
6547
                        $this->name,
6548
                        $exerciseAttempts
6549
                    );
6550
                    $isVisible = false;
6551
                }
6552
            }
6553
        }
6554
6555
        $rawMessage = '';
6556
        if (!empty($message)) {
6557
            $rawMessage = $message;
6558
            $message = Display::return_message($message, 'warning', false);
6559
        }
6560
6561
        return [
6562
            'value' => $isVisible,
6563
            'message' => $message,
6564
            'rawMessage' => $rawMessage,
6565
        ];
6566
    }
6567
6568
    /**
6569
     * @return bool
6570
     */
6571
    public function added_in_lp()
6572
    {
6573
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
6574
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
6575
                WHERE
6576
                    c_id = {$this->course_id} AND
6577
                    item_type = '".TOOL_QUIZ."' AND
6578
                    path = '{$this->id}'";
6579
        $result = Database::query($sql);
6580
        if (Database::num_rows($result) > 0) {
6581
            return true;
6582
        }
6583
6584
        return false;
6585
    }
6586
6587
    /**
6588
     * Returns an array with this form.
6589
     *
6590
     * @example
6591
     * <code>
6592
     * array (size=3)
6593
     * 999 =>
6594
     * array (size=3)
6595
     * 0 => int 3422
6596
     * 1 => int 3423
6597
     * 2 => int 3424
6598
     * 100 =>
6599
     * array (size=2)
6600
     * 0 => int 3469
6601
     * 1 => int 3470
6602
     * 101 =>
6603
     * array (size=1)
6604
     * 0 => int 3482
6605
     * </code>
6606
     * The array inside the key 999 means the question list that belongs to the media id = 999,
6607
     * this case is special because 999 means "no media".
6608
     *
6609
     * @return array
6610
     */
6611
    public function getMediaList()
6612
    {
6613
        return $this->mediaList;
6614
    }
6615
6616
    /**
6617
     * Is media question activated?
6618
     *
6619
     * @return bool
6620
     */
6621
    public function mediaIsActivated()
6622
    {
6623
        $mediaQuestions = $this->getMediaList();
6624
        $active = false;
6625
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6626
            $media_count = count($mediaQuestions);
6627
            if ($media_count > 1) {
6628
                return true;
6629
            } elseif (1 == $media_count) {
6630
                if (isset($mediaQuestions[999])) {
6631
                    return false;
6632
                } else {
6633
                    return true;
6634
                }
6635
            }
6636
        }
6637
6638
        return $active;
6639
    }
6640
6641
    /**
6642
     * Gets question list from the exercise.
6643
     *
6644
     * @return array
6645
     */
6646
    public function getQuestionList()
6647
    {
6648
        return $this->questionList;
6649
    }
6650
6651
    /**
6652
     * Question list with medias compressed like this.
6653
     *
6654
     * @example
6655
     * <code>
6656
     * array(
6657
     *      question_id_1,
6658
     *      question_id_2,
6659
     *      media_id, <- this media id contains question ids
6660
     *      question_id_3,
6661
     * )
6662
     * </code>
6663
     *
6664
     * @return array
6665
     */
6666
    public function getQuestionListWithMediasCompressed()
6667
    {
6668
        return $this->questionList;
6669
    }
6670
6671
    /**
6672
     * Question list with medias uncompressed like this.
6673
     *
6674
     * @example
6675
     * <code>
6676
     * array(
6677
     *      question_id,
6678
     *      question_id,
6679
     *      question_id, <- belongs to a media id
6680
     *      question_id, <- belongs to a media id
6681
     *      question_id,
6682
     * )
6683
     * </code>
6684
     *
6685
     * @return array
6686
     */
6687
    public function getQuestionListWithMediasUncompressed()
6688
    {
6689
        return $this->questionListUncompressed;
6690
    }
6691
6692
    /**
6693
     * Sets the question list when the exercise->read() is executed.
6694
     *
6695
     * @param bool $adminView Whether to view the set the list of *all* questions or just the normal student view
6696
     */
6697
    public function setQuestionList($adminView = false)
6698
    {
6699
        // Getting question list.
6700
        $questionList = $this->selectQuestionList(true, $adminView);
6701
        $this->setMediaList($questionList);
6702
        $this->questionList = $this->transformQuestionListWithMedias(
6703
            $questionList,
6704
            false
6705
        );
6706
        $this->questionListUncompressed = $this->transformQuestionListWithMedias(
6707
            $questionList,
6708
            true
6709
        );
6710
    }
6711
6712
    /**
6713
     * @params array question list
6714
     * @params bool expand or not question list (true show all questions,
6715
     * false show media question id instead of the question ids)
6716
     */
6717
    public function transformQuestionListWithMedias(
6718
        $question_list,
6719
        $expand_media_questions = false
6720
    ) {
6721
        $new_question_list = [];
6722
        if (!empty($question_list)) {
6723
            $media_questions = $this->getMediaList();
6724
            $media_active = $this->mediaIsActivated($media_questions);
6725
6726
            if ($media_active) {
6727
                $counter = 1;
6728
                foreach ($question_list as $question_id) {
6729
                    $add_question = true;
6730
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6731
                        if (999 != $media_id && in_array($question_id, $question_list_in_media)) {
6732
                            $add_question = false;
6733
                            if (!in_array($media_id, $new_question_list)) {
6734
                                $new_question_list[$counter] = $media_id;
6735
                                $counter++;
6736
                            }
6737
6738
                            break;
6739
                        }
6740
                    }
6741
                    if ($add_question) {
6742
                        $new_question_list[$counter] = $question_id;
6743
                        $counter++;
6744
                    }
6745
                }
6746
                if ($expand_media_questions) {
6747
                    $media_key_list = array_keys($media_questions);
6748
                    foreach ($new_question_list as &$question_id) {
6749
                        if (in_array($question_id, $media_key_list)) {
6750
                            $question_id = $media_questions[$question_id];
6751
                        }
6752
                    }
6753
                    $new_question_list = array_flatten($new_question_list);
6754
                }
6755
            } else {
6756
                $new_question_list = $question_list;
6757
            }
6758
        }
6759
6760
        return $new_question_list;
6761
    }
6762
6763
    /**
6764
     * Get question list depend on the random settings.
6765
     *
6766
     * @return array
6767
     */
6768
    public function get_validated_question_list()
6769
    {
6770
        $isRandomByCategory = $this->isRandomByCat();
6771
        if (0 == $isRandomByCategory) {
6772
            if ($this->isRandom()) {
6773
                return $this->getRandomList();
6774
            }
6775
6776
            return $this->selectQuestionList();
6777
        }
6778
6779
        if ($this->isRandom()) {
6780
            // USE question categories
6781
            // get questions by category for this exercise
6782
            // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6783
            // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6784
            // value is the array of question id of this category
6785
            $questionList = [];
6786
            $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6787
            $isRandomByCategory = $this->getRandomByCategory();
6788
            // We sort categories based on the term between [] in the head
6789
            // of the category's description
6790
            /* examples of categories :
6791
             * [biologie] Maitriser les mecanismes de base de la genetique
6792
             * [biologie] Relier les moyens de depenses et les agents infectieux
6793
             * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6794
             * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6795
             * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6796
             * [chimie] Connaître les charges des particules
6797
             * We want that in the order of the groups defined by the term
6798
             * between brackets at the beginning of the category title
6799
            */
6800
            // If test option is Grouped By Categories
6801
            if (2 == $isRandomByCategory) {
6802
                $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6803
            }
6804
            foreach ($tabCategoryQuestions as $tabquestion) {
6805
                $number_of_random_question = $this->random;
6806
                if (-1 == $this->random) {
6807
                    $number_of_random_question = count($this->questionList);
6808
                }
6809
                $questionList = array_merge(
6810
                    $questionList,
6811
                    TestCategory::getNElementsFromArray(
6812
                        $tabquestion,
6813
                        $number_of_random_question
6814
                    )
6815
                );
6816
            }
6817
            // shuffle the question list if test is not grouped by categories
6818
            if (1 == $isRandomByCategory) {
6819
                shuffle($questionList); // or not
6820
            }
6821
6822
            return $questionList;
6823
        }
6824
6825
        // Problem, random by category has been selected and
6826
        // we have no $this->isRandom number of question selected
6827
        // Should not happened
6828
6829
        return [];
6830
    }
6831
6832
    public function get_question_list($expand_media_questions = false)
6833
    {
6834
        $question_list = $this->get_validated_question_list();
6835
6836
        return $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6837
    }
6838
6839
    public function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6840
    {
6841
        $new_question_list = [];
6842
        if (!empty($question_list)) {
6843
            $media_questions = $this->getMediaList();
6844
            $media_active = $this->mediaIsActivated($media_questions);
6845
6846
            if ($media_active) {
6847
                $counter = 1;
6848
                foreach ($question_list as $question_id) {
6849
                    $add_question = true;
6850
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6851
                        if (999 != $media_id && in_array($question_id, $question_list_in_media)) {
6852
                            $add_question = false;
6853
                            if (!in_array($media_id, $new_question_list)) {
6854
                                $new_question_list[$counter] = $media_id;
6855
                                $counter++;
6856
                            }
6857
6858
                            break;
6859
                        }
6860
                    }
6861
                    if ($add_question) {
6862
                        $new_question_list[$counter] = $question_id;
6863
                        $counter++;
6864
                    }
6865
                }
6866
                if ($expand_media_questions) {
6867
                    $media_key_list = array_keys($media_questions);
6868
                    foreach ($new_question_list as &$question_id) {
6869
                        if (in_array($question_id, $media_key_list)) {
6870
                            $question_id = $media_questions[$question_id];
6871
                        }
6872
                    }
6873
                    $new_question_list = array_flatten($new_question_list);
6874
                }
6875
            } else {
6876
                $new_question_list = $question_list;
6877
            }
6878
        }
6879
6880
        return $new_question_list;
6881
    }
6882
6883
    /**
6884
     * @param int $exe_id
6885
     *
6886
     * @return array
6887
     */
6888
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6889
    {
6890
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6891
        $exe_id = (int) $exe_id;
6892
        $sql_track = "SELECT * FROM $table WHERE exe_id = $exe_id ";
6893
        $result = Database::query($sql_track);
6894
        $new_array = [];
6895
        if (Database::num_rows($result) > 0) {
6896
            $new_array = Database::fetch_array($result, 'ASSOC');
6897
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6898
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6899
            $new_array['duration_formatted'] = '';
6900
            if (!empty($new_array['exe_duration']) && !empty($start_date) && !empty($end_date)) {
6901
                $time = api_format_time($new_array['exe_duration'], 'js');
6902
                $new_array['duration_formatted'] = $time;
6903
            }
6904
        }
6905
6906
        return $new_array;
6907
    }
6908
6909
    /**
6910
     * @param int $exeId
6911
     *
6912
     * @return bool
6913
     */
6914
    public function removeAllQuestionToRemind($exeId)
6915
    {
6916
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6917
        $exeId = (int) $exeId;
6918
        if (empty($exeId)) {
6919
            return false;
6920
        }
6921
        $sql = "UPDATE $table
6922
                SET questions_to_check = ''
6923
                WHERE exe_id = $exeId ";
6924
        Database::query($sql);
6925
6926
        return true;
6927
    }
6928
6929
    /**
6930
     * @param int   $exeId
6931
     * @param array $questionList
6932
     *
6933
     * @return bool
6934
     */
6935
    public function addAllQuestionToRemind($exeId, $questionList = [])
6936
    {
6937
        $exeId = (int) $exeId;
6938
        if (empty($questionList)) {
6939
            return false;
6940
        }
6941
6942
        $questionListToString = implode(',', $questionList);
6943
        $questionListToString = Database::escape_string($questionListToString);
6944
6945
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6946
        $sql = "UPDATE $table
6947
                SET questions_to_check = '$questionListToString'
6948
                WHERE exe_id = $exeId";
6949
        Database::query($sql);
6950
6951
        return true;
6952
    }
6953
6954
    /**
6955
     * @param int    $exe_id
6956
     * @param int    $question_id
6957
     * @param string $action
6958
     */
6959
    public function editQuestionToRemind($exe_id, $question_id, $action = 'add')
6960
    {
6961
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6962
        $question_id = (int) $question_id;
6963
        $exe_id = (int) $exe_id;
6964
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6965
        if ($exercise_info) {
6966
            if (empty($exercise_info['questions_to_check'])) {
6967
                if ('add' == $action) {
6968
                    $sql = "UPDATE $track_exercises
6969
                            SET questions_to_check = '$question_id'
6970
                            WHERE exe_id = $exe_id ";
6971
                    Database::query($sql);
6972
                }
6973
            } else {
6974
                $remind_list = explode(',', $exercise_info['questions_to_check']);
6975
                $remind_list_string = '';
6976
                if ('add' == $action) {
6977
                    if (!in_array($question_id, $remind_list)) {
6978
                        $newRemindList = [];
6979
                        $remind_list[] = $question_id;
6980
                        $questionListInSession = Session::read('questionList');
6981
                        if (!empty($questionListInSession)) {
6982
                            foreach ($questionListInSession as $originalQuestionId) {
6983
                                if (in_array($originalQuestionId, $remind_list)) {
6984
                                    $newRemindList[] = $originalQuestionId;
6985
                                }
6986
                            }
6987
                        }
6988
                        $remind_list_string = implode(',', $newRemindList);
6989
                    }
6990
                } elseif ('delete' == $action) {
6991
                    if (!empty($remind_list)) {
6992
                        if (in_array($question_id, $remind_list)) {
6993
                            $remind_list = array_flip($remind_list);
6994
                            unset($remind_list[$question_id]);
6995
                            $remind_list = array_flip($remind_list);
6996
6997
                            if (!empty($remind_list)) {
6998
                                sort($remind_list);
6999
                                array_filter($remind_list);
7000
                                $remind_list_string = implode(',', $remind_list);
7001
                            }
7002
                        }
7003
                    }
7004
                }
7005
                $value = Database::escape_string($remind_list_string);
7006
                $sql = "UPDATE $track_exercises
7007
                        SET questions_to_check = '$value'
7008
                        WHERE exe_id = $exe_id ";
7009
                Database::query($sql);
7010
            }
7011
        }
7012
    }
7013
7014
    /**
7015
     * @param string $answer
7016
     */
7017
    public function fill_in_blank_answer_to_array($answer)
7018
    {
7019
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
7020
7021
        return $teacher_answer_list[0];
7022
    }
7023
7024
    /**
7025
     * @param string $answer
7026
     *
7027
     * @return string
7028
     */
7029
    public function fill_in_blank_answer_to_string($answer)
7030
    {
7031
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
7032
        $result = '';
7033
        if (!empty($teacher_answer_list)) {
7034
            $i = 0;
7035
            foreach ($teacher_answer_list as $teacher_item) {
7036
                $value = null;
7037
                //Cleaning student answer list
7038
                $value = strip_tags($teacher_item);
7039
                $value = api_substr($value, 1, api_strlen($value) - 2);
7040
                $value = explode('/', $value);
7041
                if (!empty($value[0])) {
7042
                    $value = trim($value[0]);
7043
                    $value = str_replace('&nbsp;', '', $value);
7044
                    $result .= $value;
7045
                }
7046
            }
7047
        }
7048
7049
        return $result;
7050
    }
7051
7052
    /**
7053
     * @return string
7054
     */
7055
    public function returnTimeLeftDiv()
7056
    {
7057
        $html = '<div id="clock_warning" style="display:none">';
7058
        $html .= Display::return_message(
7059
            get_lang('Time limit reached'),
7060
            'warning'
7061
        );
7062
        $html .= ' ';
7063
        $html .= sprintf(
7064
            get_lang('Just a moment, please. You will be redirected in %s seconds...'),
7065
            '<span id="counter_to_redirect" class="red_alert"></span>'
7066
        );
7067
        $html .= '</div>';
7068
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
7069
7070
        return $html;
7071
    }
7072
7073
    /**
7074
     * Get categories added in the exercise--category matrix.
7075
     *
7076
     * @return array
7077
     */
7078
    public function getCategoriesInExercise()
7079
    {
7080
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
7081
        if (!empty($this->id)) {
7082
            $sql = "SELECT * FROM $table
7083
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
7084
            $result = Database::query($sql);
7085
            $list = [];
7086
            if (Database::num_rows($result)) {
7087
                while ($row = Database::fetch_array($result, 'ASSOC')) {
7088
                    $list[$row['category_id']] = $row;
7089
                }
7090
7091
                return $list;
7092
            }
7093
        }
7094
7095
        return [];
7096
    }
7097
7098
    /**
7099
     * Get total number of question that will be parsed when using the category/exercise.
7100
     *
7101
     * @return int
7102
     */
7103
    public function getNumberQuestionExerciseCategory()
7104
    {
7105
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
7106
        if (!empty($this->id)) {
7107
            $sql = "SELECT SUM(count_questions) count_questions
7108
                    FROM $table
7109
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
7110
            $result = Database::query($sql);
7111
            if (Database::num_rows($result)) {
7112
                $row = Database::fetch_array($result);
7113
7114
                return (int) $row['count_questions'];
7115
            }
7116
        }
7117
7118
        return 0;
7119
    }
7120
7121
    /**
7122
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table.
7123
     *
7124
     * @param array $categories
7125
     */
7126
    public function save_categories_in_exercise($categories)
7127
    {
7128
        if (!empty($categories) && !empty($this->id)) {
7129
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
7130
            $sql = "DELETE FROM $table
7131
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
7132
            Database::query($sql);
7133
            if (!empty($categories)) {
7134
                foreach ($categories as $categoryId => $countQuestions) {
7135
                    $params = [
7136
                        'c_id' => $this->course_id,
7137
                        'exercise_id' => $this->id,
7138
                        'category_id' => $categoryId,
7139
                        'count_questions' => $countQuestions,
7140
                    ];
7141
                    Database::insert($table, $params);
7142
                }
7143
            }
7144
        }
7145
    }
7146
7147
    /**
7148
     * @param array  $questionList
7149
     * @param int    $currentQuestion
7150
     * @param array  $conditions
7151
     * @param string $link
7152
     *
7153
     * @return string
7154
     */
7155
    public function progressExercisePaginationBar(
7156
        $questionList,
7157
        $currentQuestion,
7158
        $conditions,
7159
        $link
7160
    ) {
7161
        $mediaQuestions = $this->getMediaList();
7162
7163
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
7164
        $counter = 0;
7165
        $nextValue = 0;
7166
        $wasMedia = false;
7167
        $before = 0;
7168
        $counterNoMedias = 0;
7169
        foreach ($questionList as $questionId) {
7170
            $isCurrent = $currentQuestion == $counterNoMedias + 1 ? true : false;
7171
7172
            if (!empty($nextValue)) {
7173
                if ($wasMedia) {
7174
                    $nextValue = $nextValue - $before + 1;
7175
                }
7176
            }
7177
7178
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
7179
                $fixedValue = $counterNoMedias;
7180
7181
                $html .= Display::progressPaginationBar(
7182
                    $nextValue,
7183
                    $mediaQuestions[$questionId],
7184
                    $currentQuestion,
7185
                    $fixedValue,
7186
                    $conditions,
7187
                    $link,
7188
                    true,
7189
                    true
7190
                );
7191
7192
                $counter += count($mediaQuestions[$questionId]) - 1;
7193
                $before = count($questionList);
7194
                $wasMedia = true;
7195
                $nextValue += count($questionList);
7196
            } else {
7197
                $html .= Display::parsePaginationItem(
7198
                    $questionId,
7199
                    $isCurrent,
7200
                    $conditions,
7201
                    $link,
7202
                    $counter
7203
                );
7204
                $counter++;
7205
                $nextValue++;
7206
                $wasMedia = false;
7207
            }
7208
            $counterNoMedias++;
7209
        }
7210
        $html .= '</ul></div>';
7211
7212
        return $html;
7213
    }
7214
7215
    /**
7216
     *  Shows a list of numbers that represents the question to answer in a exercise.
7217
     *
7218
     * @param array  $categories
7219
     * @param int    $current
7220
     * @param array  $conditions
7221
     * @param string $link
7222
     *
7223
     * @return string
7224
     */
7225
    public function progressExercisePaginationBarWithCategories(
7226
        $categories,
7227
        $current,
7228
        $conditions = [],
7229
        $link = null
7230
    ) {
7231
        $html = null;
7232
        $counterNoMedias = 0;
7233
        $nextValue = 0;
7234
        $wasMedia = false;
7235
        $before = 0;
7236
7237
        if (!empty($categories)) {
7238
            $selectionType = $this->getQuestionSelectionType();
7239
            $useRootAsCategoryTitle = false;
7240
7241
            // Grouping questions per parent category see BT#6540
7242
            if (in_array(
7243
                $selectionType,
7244
                [
7245
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
7246
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM,
7247
                ]
7248
            )) {
7249
                $useRootAsCategoryTitle = true;
7250
            }
7251
7252
            // If the exercise is set to only show the titles of the categories
7253
            // at the root of the tree, then pre-order the categories tree by
7254
            // removing children and summing their questions into the parent
7255
            // categories
7256
            if ($useRootAsCategoryTitle) {
7257
                // The new categories list starts empty
7258
                $newCategoryList = [];
7259
                foreach ($categories as $category) {
7260
                    $rootElement = $category['root'];
7261
7262
                    if (isset($category['parent_info'])) {
7263
                        $rootElement = $category['parent_info']['id'];
7264
                    }
7265
7266
                    //$rootElement = $category['id'];
7267
                    // If the current category's ancestor was never seen
7268
                    // before, then declare it and assign the current
7269
                    // category to it.
7270
                    if (!isset($newCategoryList[$rootElement])) {
7271
                        $newCategoryList[$rootElement] = $category;
7272
                    } else {
7273
                        // If it was already seen, then merge the previous with
7274
                        // the current category
7275
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
7276
                        $category['question_list'] = array_merge($oldQuestionList, $category['question_list']);
7277
                        $newCategoryList[$rootElement] = $category;
7278
                    }
7279
                }
7280
                // Now use the newly built categories list, with only parents
7281
                $categories = $newCategoryList;
7282
            }
7283
7284
            foreach ($categories as $category) {
7285
                $questionList = $category['question_list'];
7286
                // Check if in this category there questions added in a media
7287
                $mediaQuestionId = $category['media_question'];
7288
                $isMedia = false;
7289
                $fixedValue = null;
7290
7291
                // Media exists!
7292
                if (999 != $mediaQuestionId) {
7293
                    $isMedia = true;
7294
                    $fixedValue = $counterNoMedias;
7295
                }
7296
7297
                //$categoryName = $category['path']; << show the path
7298
                $categoryName = $category['name'];
7299
7300
                if ($useRootAsCategoryTitle) {
7301
                    if (isset($category['parent_info'])) {
7302
                        $categoryName = $category['parent_info']['title'];
7303
                    }
7304
                }
7305
                $html .= '<div class="row">';
7306
                $html .= '<div class="span2">'.$categoryName.'</div>';
7307
                $html .= '<div class="span8">';
7308
7309
                if (!empty($nextValue)) {
7310
                    if ($wasMedia) {
7311
                        $nextValue = $nextValue - $before + 1;
7312
                    }
7313
                }
7314
                $html .= Display::progressPaginationBar(
7315
                    $nextValue,
7316
                    $questionList,
7317
                    $current,
7318
                    $fixedValue,
7319
                    $conditions,
7320
                    $link,
7321
                    $isMedia,
7322
                    true
7323
                );
7324
                $html .= '</div>';
7325
                $html .= '</div>';
7326
7327
                if (999 == $mediaQuestionId) {
7328
                    $counterNoMedias += count($questionList);
7329
                } else {
7330
                    $counterNoMedias++;
7331
                }
7332
7333
                $nextValue += count($questionList);
7334
                $before = count($questionList);
7335
7336
                if (999 != $mediaQuestionId) {
7337
                    $wasMedia = true;
7338
                } else {
7339
                    $wasMedia = false;
7340
                }
7341
            }
7342
        }
7343
7344
        return $html;
7345
    }
7346
7347
    /**
7348
     * Renders a question list.
7349
     *
7350
     * @param array $questionList    (with media questions compressed)
7351
     * @param int   $currentQuestion
7352
     * @param array $exerciseResult
7353
     * @param array $attemptList
7354
     * @param array $remindList
7355
     */
7356
    public function renderQuestionList(
7357
        $questionList,
7358
        $currentQuestion,
7359
        $exerciseResult,
7360
        $attemptList,
7361
        $remindList
7362
    ) {
7363
        $mediaQuestions = $this->getMediaList();
7364
        $i = 0;
7365
7366
        // Normal question list render (medias compressed)
7367
        foreach ($questionList as $questionId) {
7368
            $i++;
7369
            // For sequential exercises
7370
7371
            if (ONE_PER_PAGE == $this->type) {
7372
                // If it is not the right question, goes to the next loop iteration
7373
                if ($currentQuestion != $i) {
7374
                    continue;
7375
                } else {
7376
                    if (!in_array(
7377
                        $this->getFeedbackType(),
7378
                        [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
7379
                    )) {
7380
                        // if the user has already answered this question
7381
                        if (isset($exerciseResult[$questionId])) {
7382
                            echo Display::return_message(
7383
                                get_lang('You already answered the question'),
7384
                                'normal'
7385
                            );
7386
7387
                            break;
7388
                        }
7389
                    }
7390
                }
7391
            }
7392
7393
            // The $questionList contains the media id we check
7394
            // if this questionId is a media question type
7395
            if (isset($mediaQuestions[$questionId]) &&
7396
                999 != $mediaQuestions[$questionId]
7397
            ) {
7398
                // The question belongs to a media
7399
                $mediaQuestionList = $mediaQuestions[$questionId];
7400
                $objQuestionTmp = Question::read($questionId);
7401
7402
                $counter = 1;
7403
                if (MEDIA_QUESTION == $objQuestionTmp->type) {
7404
                    echo $objQuestionTmp->show_media_content();
7405
7406
                    $countQuestionsInsideMedia = count($mediaQuestionList);
7407
7408
                    // Show questions that belongs to a media
7409
                    if (!empty($mediaQuestionList)) {
7410
                        // In order to parse media questions we use letters a, b, c, etc.
7411
                        $letterCounter = 97;
7412
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
7413
                            $isLastQuestionInMedia = false;
7414
                            if ($counter == $countQuestionsInsideMedia) {
7415
                                $isLastQuestionInMedia = true;
7416
                            }
7417
                            $this->renderQuestion(
7418
                                $questionIdInsideMedia,
7419
                                $attemptList,
7420
                                $remindList,
7421
                                chr($letterCounter),
7422
                                $currentQuestion,
7423
                                $mediaQuestionList,
7424
                                $isLastQuestionInMedia,
7425
                                $questionList
7426
                            );
7427
                            $letterCounter++;
7428
                            $counter++;
7429
                        }
7430
                    }
7431
                } else {
7432
                    $this->renderQuestion(
7433
                        $questionId,
7434
                        $attemptList,
7435
                        $remindList,
7436
                        $i,
7437
                        $currentQuestion,
7438
                        null,
7439
                        null,
7440
                        $questionList
7441
                    );
7442
                    $i++;
7443
                }
7444
            } else {
7445
                // Normal question render.
7446
                $this->renderQuestion(
7447
                    $questionId,
7448
                    $attemptList,
7449
                    $remindList,
7450
                    $i,
7451
                    $currentQuestion,
7452
                    null,
7453
                    null,
7454
                    $questionList
7455
                );
7456
            }
7457
7458
            // For sequential exercises.
7459
            if (ONE_PER_PAGE == $this->type) {
7460
                // quits the loop
7461
                break;
7462
            }
7463
        }
7464
        // end foreach()
7465
7466
        if (ALL_ON_ONE_PAGE == $this->type) {
7467
            $exercise_actions = $this->show_button($questionId, $currentQuestion);
7468
            echo Display::div($exercise_actions, ['class' => 'exercise_actions']);
7469
        }
7470
    }
7471
7472
    /**
7473
     * @param int   $questionId
7474
     * @param array $attemptList
7475
     * @param array $remindList
7476
     * @param int   $i
7477
     * @param int   $current_question
7478
     * @param array $questions_in_media
7479
     * @param bool  $last_question_in_media
7480
     * @param array $realQuestionList
7481
     * @param bool  $generateJS
7482
     */
7483
    public function renderQuestion(
7484
        $questionId,
7485
        $attemptList,
7486
        $remindList,
7487
        $i,
7488
        $current_question,
7489
        $questions_in_media = [],
7490
        $last_question_in_media = false,
7491
        $realQuestionList,
7492
        $generateJS = true
7493
    ) {
7494
        // With this option on the question is loaded via AJAX
7495
        //$generateJS = true;
7496
        //$this->loadQuestionAJAX = true;
7497
7498
        if ($generateJS && $this->loadQuestionAJAX) {
7499
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
7500
            $params = [
7501
                'questionId' => $questionId,
7502
                'attemptList' => $attemptList,
7503
                'remindList' => $remindList,
7504
                'i' => $i,
7505
                'current_question' => $current_question,
7506
                'questions_in_media' => $questions_in_media,
7507
                'last_question_in_media' => $last_question_in_media,
7508
            ];
7509
            $params = json_encode($params);
7510
7511
            $script = '<script>
7512
            $(function(){
7513
                var params = '.$params.';
7514
                $.ajax({
7515
                    type: "GET",
7516
                    data: params,
7517
                    url: "'.$url.'",
7518
                    success: function(return_value) {
7519
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
7520
                    }
7521
                });
7522
            });
7523
            </script>
7524
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
7525
            echo $script;
7526
        } else {
7527
            global $origin;
7528
            $question_obj = Question::read($questionId);
7529
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
7530
            $remind_highlight = null;
7531
7532
            // Hides questions when reviewing a ALL_ON_ONE_PAGE exercise
7533
            // see #4542 no_remind_highlight class hide with jquery
7534
            if (ALL_ON_ONE_PAGE == $this->type && isset($_GET['reminder']) && 2 == $_GET['reminder']) {
7535
                $remind_highlight = 'no_remind_highlight';
7536
                if (in_array($question_obj->type, Question::question_type_no_review())) {
7537
                    return null;
7538
                }
7539
            }
7540
7541
            $attributes = ['id' => 'remind_list['.$questionId.']'];
7542
7543
            // Showing the question
7544
            $exercise_actions = null;
7545
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
7546
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
7547
7548
            // Shows the question + possible answers
7549
            $showTitle = 1 == $this->getHideQuestionTitle() ? false : true;
7550
            echo $this->showQuestion(
7551
                $question_obj,
7552
                false,
7553
                $origin,
7554
                $i,
7555
                $showTitle,
7556
                false,
7557
                $user_choice,
7558
                false,
7559
                null,
7560
                false,
7561
                $this->getModelType(),
7562
                $this->categoryMinusOne
7563
            );
7564
7565
            // Button save and continue
7566
            switch ($this->type) {
7567
                case ONE_PER_PAGE:
7568
                    $exercise_actions .= $this->show_button(
7569
                        $questionId,
7570
                        $current_question,
7571
                        null,
7572
                        $remindList
7573
                    );
7574
7575
                    break;
7576
                case ALL_ON_ONE_PAGE:
7577
                    if (api_is_allowed_to_session_edit()) {
7578
                        $button = [
7579
                            Display::button(
7580
                                'save_now',
7581
                                get_lang('Save and continue'),
7582
                                ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7583
                            ),
7584
                            '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>',
7585
                        ];
7586
                        $exercise_actions .= Display::div(
7587
                            implode(PHP_EOL, $button),
7588
                            ['class' => 'exercise_save_now_button']
7589
                        );
7590
                    }
7591
7592
                    break;
7593
            }
7594
7595
            if (!empty($questions_in_media)) {
7596
                $count_of_questions_inside_media = count($questions_in_media);
7597
                if ($count_of_questions_inside_media > 1 && api_is_allowed_to_session_edit()) {
7598
                    $button = [
7599
                        Display::button(
7600
                            'save_now',
7601
                            get_lang('Save and continue'),
7602
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7603
                        ),
7604
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;',
7605
                    ];
7606
                    $exercise_actions = Display::div(
7607
                        implode(PHP_EOL, $button),
7608
                        ['class' => 'exercise_save_now_button']
7609
                    );
7610
                }
7611
7612
                if ($last_question_in_media && ONE_PER_PAGE == $this->type) {
7613
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
7614
                }
7615
            }
7616
7617
            // Checkbox review answers
7618
            if ($this->review_answers &&
7619
                !in_array($question_obj->type, Question::question_type_no_review())
7620
            ) {
7621
                $remind_question_div = Display::tag(
7622
                    'label',
7623
                    Display::input(
7624
                        'checkbox',
7625
                        'remind_list['.$questionId.']',
7626
                        '',
7627
                        $attributes
7628
                    ).get_lang('Revise question later'),
7629
                    [
7630
                        'class' => 'checkbox',
7631
                        'for' => 'remind_list['.$questionId.']',
7632
                    ]
7633
                );
7634
                $exercise_actions .= Display::div(
7635
                    $remind_question_div,
7636
                    ['class' => 'exercise_save_now_button']
7637
                );
7638
            }
7639
7640
            echo Display::div(' ', ['class' => 'clear']);
7641
7642
            $paginationCounter = null;
7643
            if (ONE_PER_PAGE == $this->type) {
7644
                if (empty($questions_in_media)) {
7645
                    $paginationCounter = Display::paginationIndicator(
7646
                        $current_question,
7647
                        count($realQuestionList)
7648
                    );
7649
                } else {
7650
                    if ($last_question_in_media) {
7651
                        $paginationCounter = Display::paginationIndicator(
7652
                            $current_question,
7653
                            count($realQuestionList)
7654
                        );
7655
                    }
7656
                }
7657
            }
7658
7659
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
7660
            echo Display::div($exercise_actions, ['class' => 'form-actions']);
7661
            echo '</div>';
7662
        }
7663
    }
7664
7665
    /**
7666
     * Returns an array of categories details for the questions of the current
7667
     * exercise.
7668
     *
7669
     * @return array
7670
     */
7671
    public function getQuestionWithCategories()
7672
    {
7673
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
7674
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
7675
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
7676
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
7677
        $sql = "SELECT DISTINCT cat.*
7678
                FROM $TBL_EXERCICE_QUESTION e
7679
                INNER JOIN $TBL_QUESTIONS q
7680
                ON (e.question_id = q.id AND e.c_id = q.c_id)
7681
                INNER JOIN $categoryRelTable catRel
7682
                ON (catRel.question_id = e.question_id AND catRel.c_id = e.c_id)
7683
                INNER JOIN $categoryTable cat
7684
                ON (cat.id = catRel.category_id AND cat.c_id = e.c_id)
7685
                WHERE
7686
                  e.c_id = {$this->course_id} AND
7687
                  e.exercice_id	= ".(int) ($this->id);
7688
7689
        $result = Database::query($sql);
7690
        $categoriesInExercise = [];
7691
        if (Database::num_rows($result)) {
7692
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
7693
        }
7694
7695
        return $categoriesInExercise;
7696
    }
7697
7698
    /**
7699
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option.
7700
     */
7701
    public function get_max_score()
7702
    {
7703
        $out_max_score = 0;
7704
        // list of question's id !!! the array key start at 1 !!!
7705
        $questionList = $this->selectQuestionList(true);
7706
7707
        // test is randomQuestions - see field random of test
7708
        if ($this->random > 0 && 0 == $this->randomByCat) {
7709
            $numberRandomQuestions = $this->random;
7710
            $questionScoreList = [];
7711
            foreach ($questionList as $questionId) {
7712
                $tmpobj_question = Question::read($questionId);
7713
                if (is_object($tmpobj_question)) {
7714
                    $questionScoreList[] = $tmpobj_question->weighting;
7715
                }
7716
            }
7717
7718
            rsort($questionScoreList);
7719
            // add the first $numberRandomQuestions value of score array to get max_score
7720
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
7721
                $out_max_score += $questionScoreList[$i];
7722
            }
7723
        } elseif ($this->random > 0 && $this->randomByCat > 0) {
7724
            // test is random by category
7725
            // get the $numberRandomQuestions best score question of each category
7726
            $numberRandomQuestions = $this->random;
7727
            $tab_categories_scores = [];
7728
            foreach ($questionList as $questionId) {
7729
                $question_category_id = TestCategory::getCategoryForQuestion($questionId);
7730
                if (!is_array($tab_categories_scores[$question_category_id])) {
7731
                    $tab_categories_scores[$question_category_id] = [];
7732
                }
7733
                $tmpobj_question = Question::read($questionId);
7734
                if (is_object($tmpobj_question)) {
7735
                    $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
7736
                }
7737
            }
7738
7739
            // here we've got an array with first key, the category_id, second key, score of question for this cat
7740
            foreach ($tab_categories_scores as $tab_scores) {
7741
                rsort($tab_scores);
7742
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
7743
                    $out_max_score += $tab_scores[$i];
7744
                }
7745
            }
7746
        } else {
7747
            // standard test, just add each question score
7748
            foreach ($questionList as $questionId) {
7749
                $question = Question::read($questionId, $this->course);
7750
                $out_max_score += $question->weighting;
7751
            }
7752
        }
7753
7754
        return $out_max_score;
7755
    }
7756
7757
    /**
7758
     * @return string
7759
     */
7760
    public function get_formated_title()
7761
    {
7762
        if (api_get_configuration_value('save_titles_as_html')) {
7763
        }
7764
7765
        return api_html_entity_decode($this->selectTitle());
7766
    }
7767
7768
    /**
7769
     * @param string $title
7770
     *
7771
     * @return string
7772
     */
7773
    public static function get_formated_title_variable($title)
7774
    {
7775
        return api_html_entity_decode($title);
7776
    }
7777
7778
    /**
7779
     * @return string
7780
     */
7781
    public function format_title()
7782
    {
7783
        return api_htmlentities($this->title);
7784
    }
7785
7786
    /**
7787
     * @param string $title
7788
     *
7789
     * @return string
7790
     */
7791
    public static function format_title_variable($title)
7792
    {
7793
        return api_htmlentities($title);
7794
    }
7795
7796
    /**
7797
     * @param int $courseId
7798
     * @param int $sessionId
7799
     *
7800
     * @return array exercises
7801
     */
7802
    public function getExercisesByCourseSession($courseId, $sessionId)
7803
    {
7804
        $courseId = (int) $courseId;
7805
        $sessionId = (int) $sessionId;
7806
7807
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7808
        $sql = "SELECT * FROM $tbl_quiz cq
7809
                WHERE
7810
                    cq.c_id = %s AND
7811
                    (cq.session_id = %s OR cq.session_id = 0) AND
7812
                    cq.active = 0
7813
                ORDER BY cq.id";
7814
        $sql = sprintf($sql, $courseId, $sessionId);
7815
7816
        $result = Database::query($sql);
7817
7818
        $rows = [];
7819
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7820
            $rows[] = $row;
7821
        }
7822
7823
        return $rows;
7824
    }
7825
7826
    /**
7827
     * @param int   $courseId
7828
     * @param int   $sessionId
7829
     * @param array $quizId
7830
     *
7831
     * @return array exercises
7832
     */
7833
    public function getExerciseAndResult($courseId, $sessionId, $quizId = [])
7834
    {
7835
        if (empty($quizId)) {
7836
            return [];
7837
        }
7838
7839
        $sessionId = (int) $sessionId;
7840
        $courseId = (int) $courseId;
7841
7842
        $ids = is_array($quizId) ? $quizId : [$quizId];
7843
        $ids = array_map('intval', $ids);
7844
        $ids = implode(',', $ids);
7845
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7846
        if (0 != $sessionId) {
7847
            $sql = "SELECT * FROM $track_exercises te
7848
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7849
              WHERE
7850
              te.id = %s AND
7851
              te.session_id = %s AND
7852
              cq.id IN (%s)
7853
              ORDER BY cq.id";
7854
7855
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
7856
        } else {
7857
            $sql = "SELECT * FROM $track_exercises te
7858
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
7859
              WHERE
7860
              te.id = %s AND
7861
              cq.id IN (%s)
7862
              ORDER BY cq.id";
7863
            $sql = sprintf($sql, $courseId, $ids);
7864
        }
7865
        $result = Database::query($sql);
7866
        $rows = [];
7867
        while ($row = Database::fetch_array($result, 'ASSOC')) {
7868
            $rows[] = $row;
7869
        }
7870
7871
        return $rows;
7872
    }
7873
7874
    /**
7875
     * @param $exeId
7876
     * @param $exercise_stat_info
7877
     * @param $remindList
7878
     * @param $currentQuestion
7879
     *
7880
     * @return int|null
7881
     */
7882
    public static function getNextQuestionId(
7883
        $exeId,
7884
        $exercise_stat_info,
7885
        $remindList,
7886
        $currentQuestion
7887
    ) {
7888
        $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete');
7889
7890
        if (isset($result[$exeId])) {
7891
            $result = $result[$exeId];
7892
        } else {
7893
            return null;
7894
        }
7895
7896
        $data_tracking = $exercise_stat_info['data_tracking'];
7897
        $data_tracking = explode(',', $data_tracking);
7898
7899
        // if this is the final question do nothing.
7900
        if ($currentQuestion == count($data_tracking)) {
7901
            return null;
7902
        }
7903
7904
        $currentQuestion--;
7905
7906
        if (!empty($result['question_list'])) {
7907
            $answeredQuestions = [];
7908
            foreach ($result['question_list'] as $question) {
7909
                if (!empty($question['answer'])) {
7910
                    $answeredQuestions[] = $question['question_id'];
7911
                }
7912
            }
7913
7914
            // Checking answered questions
7915
            $counterAnsweredQuestions = 0;
7916
            foreach ($data_tracking as $questionId) {
7917
                if (!in_array($questionId, $answeredQuestions)) {
7918
                    if ($currentQuestion != $counterAnsweredQuestions) {
7919
                        break;
7920
                    }
7921
                }
7922
                $counterAnsweredQuestions++;
7923
            }
7924
7925
            $counterRemindListQuestions = 0;
7926
            // Checking questions saved in the reminder list
7927
            if (!empty($remindList)) {
7928
                foreach ($data_tracking as $questionId) {
7929
                    if (in_array($questionId, $remindList)) {
7930
                        // Skip the current question
7931
                        if ($currentQuestion != $counterRemindListQuestions) {
7932
                            break;
7933
                        }
7934
                    }
7935
                    $counterRemindListQuestions++;
7936
                }
7937
7938
                if ($counterRemindListQuestions < $currentQuestion) {
7939
                    return null;
7940
                }
7941
7942
                if (!empty($counterRemindListQuestions)) {
7943
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
7944
                        return $counterAnsweredQuestions;
7945
                    } else {
7946
                        return $counterRemindListQuestions;
7947
                    }
7948
                }
7949
            }
7950
7951
            return $counterAnsweredQuestions;
7952
        }
7953
    }
7954
7955
    /**
7956
     * Gets the position of a questionId in the question list.
7957
     *
7958
     * @param $questionId
7959
     *
7960
     * @return int
7961
     */
7962
    public function getPositionInCompressedQuestionList($questionId)
7963
    {
7964
        $questionList = $this->getQuestionListWithMediasCompressed();
7965
        $mediaQuestions = $this->getMediaList();
7966
        $position = 1;
7967
        foreach ($questionList as $id) {
7968
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
7969
                $mediaQuestionList = $mediaQuestions[$id];
7970
                if (in_array($questionId, $mediaQuestionList)) {
7971
                    return $position;
7972
                } else {
7973
                    $position++;
7974
                }
7975
            } else {
7976
                if ($id == $questionId) {
7977
                    return $position;
7978
                } else {
7979
                    $position++;
7980
                }
7981
            }
7982
        }
7983
7984
        return 1;
7985
    }
7986
7987
    /**
7988
     * Get the correct answers in all attempts.
7989
     *
7990
     * @param int  $learnPathId
7991
     * @param int  $learnPathItemId
7992
     * @param bool $onlyCorrect
7993
     *
7994
     * @return array
7995
     */
7996
    public function getAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0, $onlyCorrect = true)
7997
    {
7998
        $attempts = Event::getExerciseResultsByUser(
7999
            api_get_user_id(),
8000
            $this->id,
8001
            api_get_course_int_id(),
8002
            api_get_session_id(),
8003
            $learnPathId,
8004
            $learnPathItemId,
8005
            'DESC'
8006
        );
8007
8008
        $list = [];
8009
        foreach ($attempts as $attempt) {
8010
            foreach ($attempt['question_list'] as $answers) {
8011
                foreach ($answers as $answer) {
8012
                    $objAnswer = new Answer($answer['question_id']);
8013
                    if ($onlyCorrect) {
8014
                        switch ($objAnswer->getQuestionType()) {
8015
                            case FILL_IN_BLANKS:
8016
                                $isCorrect = FillBlanks::isCorrect($answer['answer']);
8017
8018
                                break;
8019
                            case MATCHING:
8020
                            case DRAGGABLE:
8021
                            case MATCHING_DRAGGABLE:
8022
                                $isCorrect = Matching::isCorrect(
8023
                                    $answer['position'],
8024
                                    $answer['answer'],
8025
                                    $answer['question_id']
8026
                                );
8027
8028
                                break;
8029
                            case ORAL_EXPRESSION:
8030
                                $isCorrect = false;
8031
8032
                                break;
8033
                            default:
8034
                                $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
8035
                        }
8036
                        if ($isCorrect) {
8037
                            $list[$answer['question_id']][] = $answer;
8038
                        }
8039
                    } else {
8040
                        $list[$answer['question_id']][] = $answer;
8041
                    }
8042
                }
8043
            }
8044
8045
            if (false === $onlyCorrect) {
8046
                // Only take latest attempt
8047
                break;
8048
            }
8049
        }
8050
8051
        return $list;
8052
    }
8053
8054
    /**
8055
     * Get the correct answers in all attempts.
8056
     *
8057
     * @param int $learnPathId
8058
     * @param int $learnPathItemId
8059
     *
8060
     * @return array
8061
     */
8062
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
8063
    {
8064
        return $this->getAnswersInAllAttempts($learnPathId, $learnPathItemId);
8065
    }
8066
8067
    /**
8068
     * @return bool
8069
     */
8070
    public function showPreviousButton()
8071
    {
8072
        $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
8073
        if (false === $allow) {
8074
            return true;
8075
        }
8076
8077
        return $this->showPreviousButton;
8078
    }
8079
8080
    public function getPreventBackwards()
8081
    {
8082
        $allow = api_get_configuration_value('quiz_prevent_backwards_move');
8083
        if (false === $allow) {
8084
            return 0;
8085
        }
8086
8087
        return (int) $this->preventBackwards;
8088
    }
8089
8090
    /**
8091
     * @return int
8092
     */
8093
    public function getExerciseCategoryId()
8094
    {
8095
        if (empty($this->exerciseCategoryId)) {
8096
            return null;
8097
        }
8098
8099
        return (int) $this->exerciseCategoryId;
8100
    }
8101
8102
    /**
8103
     * @param int $value
8104
     */
8105
    public function setExerciseCategoryId($value)
8106
    {
8107
        if (!empty($value)) {
8108
            $this->exerciseCategoryId = (int) $value;
8109
        }
8110
    }
8111
8112
    /**
8113
     * @param array $values
8114
     *
8115
     * @throws \Doctrine\DBAL\DBALException
8116
     */
8117
    public function setPageResultConfiguration($values)
8118
    {
8119
        $pageConfig = api_get_configuration_value('allow_quiz_results_page_config');
8120
        if ($pageConfig) {
8121
            $params = [
8122
                'hide_expected_answer' => isset($values['hide_expected_answer']) ? $values['hide_expected_answer'] : '',
8123
                'hide_question_score' => isset($values['hide_question_score']) ? $values['hide_question_score'] : '',
8124
                'hide_total_score' => isset($values['hide_total_score']) ? $values['hide_total_score'] : '',
8125
            ];
8126
            $type = Type::getType('array');
8127
            $platform = Database::getManager()->getConnection()->getDatabasePlatform();
8128
            $this->pageResultConfiguration = $type->convertToDatabaseValue($params, $platform);
8129
        }
8130
    }
8131
8132
    /**
8133
     * @param array $defaults
8134
     */
8135
    public function setPageResultConfigurationDefaults(&$defaults)
8136
    {
8137
        $configuration = $this->getPageResultConfiguration();
8138
        if (!empty($configuration) && !empty($defaults)) {
8139
            $defaults = array_merge($defaults, $configuration);
8140
        }
8141
    }
8142
8143
    /**
8144
     * @return array
8145
     */
8146
    public function getPageResultConfiguration()
8147
    {
8148
        $pageConfig = api_get_configuration_value('allow_quiz_results_page_config');
8149
        if ($pageConfig) {
8150
            /*$params = [
8151
                'hide_expected_answer' => isset($values['hide_expected_answer']) ? $values['hide_expected_answer'] : '',
8152
                'hide_question_score' => isset($values['hide_question_score']) ? $values['hide_question_score'] : '',
8153
                'hide_total_score' => isset($values['hide_total_score']) ? $values['hide_total_score'] : ''
8154
            ];*/
8155
            $type = Type::getType('array');
8156
            $platform = Database::getManager()->getConnection()->getDatabasePlatform();
8157
8158
            return $type->convertToPHPValue($this->pageResultConfiguration, $platform);
8159
        }
8160
8161
        return [];
8162
    }
8163
8164
    /**
8165
     * @param string $attribute
8166
     *
8167
     * @return mixed|null
8168
     */
8169
    public function getPageConfigurationAttribute($attribute)
8170
    {
8171
        $result = $this->getPageResultConfiguration();
8172
8173
        if (!empty($result)) {
8174
            return isset($result[$attribute]) ? $result[$attribute] : null;
8175
        }
8176
8177
        return null;
8178
    }
8179
8180
    /**
8181
     * @param bool $showPreviousButton
8182
     *
8183
     * @return Exercise
8184
     */
8185
    public function setShowPreviousButton($showPreviousButton)
8186
    {
8187
        $this->showPreviousButton = $showPreviousButton;
8188
8189
        return $this;
8190
    }
8191
8192
    /**
8193
     * @param array $notifications
8194
     */
8195
    public function setNotifications($notifications)
8196
    {
8197
        $this->notifications = $notifications;
8198
    }
8199
8200
    /**
8201
     * @return array
8202
     */
8203
    public function getNotifications()
8204
    {
8205
        return $this->notifications;
8206
    }
8207
8208
    /**
8209
     * @return bool
8210
     */
8211
    public function showExpectedChoice()
8212
    {
8213
        return api_get_configuration_value('show_exercise_expected_choice');
8214
    }
8215
8216
    /**
8217
     * @return bool
8218
     */
8219
    public function showExpectedChoiceColumn()
8220
    {
8221
        if (!in_array($this->results_disabled, [
8222
            RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
8223
        ])
8224
        ) {
8225
            $hide = (int) $this->getPageConfigurationAttribute('hide_expected_answer');
8226
            if (1 === $hide) {
8227
                return false;
8228
            }
8229
8230
            return true;
8231
        }
8232
8233
        return false;
8234
    }
8235
8236
    /**
8237
     * @param string $class
8238
     * @param string $scoreLabel
8239
     * @param string $result
8240
     * @param array
8241
     *
8242
     * @return string
8243
     */
8244
    public function getQuestionRibbon($class, $scoreLabel, $result, $array)
8245
    {
8246
        $hide = (int) $this->getPageConfigurationAttribute('hide_question_score');
8247
        if (1 === $hide) {
8248
            return '';
8249
        }
8250
8251
        if ($this->showExpectedChoice()) {
8252
            $html = null;
8253
            $hideLabel = api_get_configuration_value('exercise_hide_label');
8254
            $label = '<div class="rib rib-'.$class.'">
8255
                        <h3>'.$scoreLabel.'</h3>
8256
                      </div>';
8257
            if (!empty($result)) {
8258
                $label .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
8259
            }
8260
            if (true === $hideLabel) {
8261
                $answerUsed = (int) $array['used'];
8262
                $answerMissing = (int) $array['missing'] - $answerUsed;
8263
                for ($i = 1; $i <= $answerUsed; $i++) {
8264
                    $html .= '<span class="score-img">'.
8265
                        Display::return_icon('attempt-check.png', null, null, ICON_SIZE_SMALL).
8266
                        '</span>';
8267
                }
8268
                for ($i = 1; $i <= $answerMissing; $i++) {
8269
                    $html .= '<span class="score-img">'.
8270
                        Display::return_icon('attempt-nocheck.png', null, null, ICON_SIZE_SMALL).
8271
                        '</span>';
8272
                }
8273
                $label = '<div class="score-title">'.get_lang('Correct answers').': '.$result.'</div>';
8274
                $label .= '<div class="score-limits">';
8275
                $label .= $html;
8276
                $label .= '</div>';
8277
            }
8278
8279
            return '<div class="ribbon">
8280
                '.$label.'
8281
                </div>'
8282
                ;
8283
        } else {
8284
            $html = '<div class="ribbon">
8285
                        <div class="rib rib-'.$class.'">
8286
                            <h3>'.$scoreLabel.'</h3>
8287
                        </div>';
8288
            if (!empty($result)) {
8289
                $html .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
8290
            }
8291
            $html .= '</div>';
8292
8293
            return $html;
8294
        }
8295
    }
8296
8297
    /**
8298
     * @return int
8299
     */
8300
    public function getAutoLaunch()
8301
    {
8302
        return $this->autolaunch;
8303
    }
8304
8305
    /**
8306
     * Clean auto launch settings for all exercise in course/course-session.
8307
     */
8308
    public function enableAutoLaunch()
8309
    {
8310
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
8311
        $sql = "UPDATE $table SET autolaunch = 1
8312
                WHERE iid = ".$this->iId;
8313
        Database::query($sql);
8314
    }
8315
8316
    /**
8317
     * Clean auto launch settings for all exercise in course/course-session.
8318
     */
8319
    public function cleanCourseLaunchSettings()
8320
    {
8321
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
8322
        $sql = "UPDATE $table SET autolaunch = 0
8323
                WHERE c_id = ".$this->course_id.' AND session_id = '.$this->sessionId;
8324
        Database::query($sql);
8325
    }
8326
8327
    /**
8328
     * Get the title without HTML tags.
8329
     *
8330
     * @return string
8331
     */
8332
    public function getUnformattedTitle()
8333
    {
8334
        return strip_tags(api_html_entity_decode($this->title));
8335
    }
8336
8337
    /**
8338
     * @param int $start
8339
     * @param int $length
8340
     *
8341
     * @return array
8342
     */
8343
    public function getQuestionForTeacher($start = 0, $length = 10)
8344
    {
8345
        $start = (int) $start;
8346
        if ($start < 0) {
8347
            $start = 0;
8348
        }
8349
8350
        $length = (int) $length;
8351
8352
        $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8353
        $question = Database::get_course_table(TABLE_QUIZ_QUESTION);
8354
        $sql = "SELECT DISTINCT e.question_id
8355
                FROM $quizRelQuestion e
8356
                INNER JOIN $question q
8357
                ON (e.question_id = q.iid AND e.c_id = q.c_id)
8358
                WHERE
8359
                    e.c_id = {$this->course_id} AND
8360
                    e.exercice_id = '".$this->id."'
8361
                ORDER BY question_order
8362
                LIMIT $start, $length
8363
            ";
8364
        $result = Database::query($sql);
8365
        $questionList = [];
8366
        while ($object = Database::fetch_object($result)) {
8367
            $questionList[] = $object->question_id;
8368
        }
8369
8370
        return $questionList;
8371
    }
8372
8373
    /**
8374
     * @param int   $exerciseId
8375
     * @param array $courseInfo
8376
     * @param int   $sessionId
8377
     *
8378
     * @throws \Doctrine\ORM\OptimisticLockException
8379
     *
8380
     * @return bool
8381
     */
8382
    public function generateStats($exerciseId, $courseInfo, $sessionId)
8383
    {
8384
        $allowStats = api_get_configuration_value('allow_gradebook_stats');
8385
        if (!$allowStats) {
8386
            return false;
8387
        }
8388
8389
        if (empty($courseInfo)) {
8390
            return false;
8391
        }
8392
8393
        $courseId = $courseInfo['real_id'];
8394
8395
        $sessionId = (int) $sessionId;
8396
        $exerciseId = (int) $exerciseId;
8397
8398
        $result = $this->read($exerciseId);
8399
8400
        if (empty($result)) {
8401
            api_not_allowed(true);
8402
        }
8403
8404
        $statusToFilter = empty($sessionId) ? STUDENT : 0;
8405
8406
        $studentList = CourseManager::get_user_list_from_course_code(
8407
            $courseInfo['code'],
8408
            $sessionId,
8409
            null,
8410
            null,
8411
            $statusToFilter
8412
        );
8413
8414
        if (empty($studentList)) {
8415
            Display::addFlash(Display::return_message(get_lang('No users in course')));
8416
            header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
8417
            exit;
8418
        }
8419
8420
        $tblStats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8421
8422
        $studentIdList = [];
8423
        if (!empty($studentList)) {
8424
            $studentIdList = array_column($studentList, 'user_id');
8425
        }
8426
8427
        if (false == $this->exercise_was_added_in_lp) {
8428
            $sql = "SELECT * FROM $tblStats
8429
                        WHERE
8430
                            exe_exo_id = $exerciseId AND
8431
                            orig_lp_id = 0 AND
8432
                            orig_lp_item_id = 0 AND
8433
                            status <> 'incomplete' AND
8434
                            session_id = $sessionId AND
8435
                            c_id = $courseId
8436
                        ";
8437
        } else {
8438
            $lpId = null;
8439
            if (!empty($this->lpList)) {
8440
                // Taking only the first LP
8441
                $lpId = $this->getLpBySession($sessionId);
8442
                $lpId = $lpId['lp_id'];
8443
            }
8444
8445
            $sql = "SELECT *
8446
                        FROM $tblStats
8447
                        WHERE
8448
                            exe_exo_id = $exerciseId AND
8449
                            orig_lp_id = $lpId AND
8450
                            status <> 'incomplete' AND
8451
                            session_id = $sessionId AND
8452
                            c_id = $courseId ";
8453
        }
8454
8455
        $sql .= ' ORDER BY exe_id DESC';
8456
8457
        $studentCount = 0;
8458
        $sum = 0;
8459
        $bestResult = 0;
8460
        $sumResult = 0;
8461
        $result = Database::query($sql);
8462
        while ($data = Database::fetch_array($result, 'ASSOC')) {
8463
            // Only take into account users in the current student list.
8464
            if (!empty($studentIdList)) {
8465
                if (!in_array($data['exe_user_id'], $studentIdList)) {
8466
                    continue;
8467
                }
8468
            }
8469
8470
            if (!isset($students[$data['exe_user_id']])) {
8471
                if (0 != $data['exe_weighting']) {
8472
                    $students[$data['exe_user_id']] = $data['exe_result'];
8473
                    if ($data['exe_result'] > $bestResult) {
8474
                        $bestResult = $data['exe_result'];
8475
                    }
8476
                    $sumResult += $data['exe_result'];
8477
                }
8478
            }
8479
        }
8480
8481
        $count = count($studentList);
8482
        $average = $sumResult / $count;
8483
        $em = Database::getManager();
8484
8485
        $links = AbstractLink::getGradebookLinksFromItem(
8486
            $this->id,
8487
            LINK_EXERCISE,
8488
            $courseInfo['code'],
8489
            $sessionId
8490
        );
8491
8492
        if (empty($links)) {
8493
            $links = AbstractLink::getGradebookLinksFromItem(
8494
                $this->iId,
8495
                LINK_EXERCISE,
8496
                $courseInfo['code'],
8497
                $sessionId
8498
            );
8499
        }
8500
8501
        if (!empty($links)) {
8502
            $repo = $em->getRepository('ChamiloCoreBundle:GradebookLink');
8503
8504
            foreach ($links as $link) {
8505
                $linkId = $link['id'];
8506
                /** @var GradebookLink $exerciseLink */
8507
                $exerciseLink = $repo->find($linkId);
8508
                if ($exerciseLink) {
8509
                    $exerciseLink
8510
                        ->setUserScoreList($students)
8511
                        ->setBestScore($bestResult)
8512
                        ->setAverageScore($average)
8513
                        ->setScoreWeight($this->get_max_score());
8514
                    $em->persist($exerciseLink);
8515
                    $em->flush();
8516
                }
8517
            }
8518
        }
8519
    }
8520
8521
    /**
8522
     * @param int    $categoryId
8523
     * @param string $keyword
8524
     *
8525
     * @return string
8526
     */
8527
    public static function exerciseGridResource($categoryId, $keyword)
8528
    {
8529
        $courseId = api_get_course_int_id();
8530
        $sessionId = api_get_session_id();
8531
        $course = api_get_course_entity($courseId);
8532
        $session = api_get_session_entity($sessionId);
8533
8534
        // 1. Set entity
8535
        $source = new Entity('ChamiloCourseBundle:CQuiz');
8536
        $repo = Container::getExerciseRepository();
8537
8538
        // 2. Get query builder from repo.
8539
        $qb = $repo->getResourcesByCourse($course, $session);
8540
8541
        if (!empty($categoryId)) {
8542
            $qb->andWhere($qb->expr()->eq('resource.exerciseCategory', $categoryId));
8543
        } else {
8544
            $qb->andWhere($qb->expr()->isNull('resource.exerciseCategory'));
8545
        }
8546
8547
        // 3. Set QueryBuilder to the source.
8548
        $source->initQueryBuilder($qb);
8549
8550
        // 4. Get the grid builder.
8551
        $builder = Container::$container->get('apy_grid.factory');
8552
8553
        $editAccess = Container::getAuthorizationChecker()->isGranted(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER);
8554
8555
        $allowFilters = false;
8556
8557
        // 5. Set parameters and properties.
8558
        $grid = $builder->createBuilder(
8559
            'grid',
8560
            $source,
8561
            [
8562
                'persistence' => false,
8563
                'route' => 'home',
8564
                'filterable' => $allowFilters,
8565
                'sortable' => $editAccess,
8566
                'max_per_page' => 10,
8567
            ]
8568
        )->add(
8569
            'id',
8570
            'number',
8571
            [
8572
                'title' => '#',
8573
                'primary' => true,
8574
                'visible' => false,
8575
            ]
8576
        )->add(
8577
            'title',
8578
            'text',
8579
            [
8580
                'title' => get_lang('Name'),
8581
                'safe' => false, // does not escape html
8582
            ]
8583
        );
8584
8585
        $grid = $grid->getGrid();
8586
        $grid->setId('grid_category_'.$categoryId);
8587
8588
        // Url link.
8589
        $grid->getColumn('title')->manipulateRenderCell(
8590
            function ($value, $row, $router) use ($course, $session, $sessionId) {
8591
                /** @var CQuiz $exercise */
8592
                $exercise = $row->getEntity();
8593
                $link = $exercise->getFirstResourceLinkFromCourseSession($course, $session);
8594
8595
                $attributes = [];
8596
                if ($link && $link->isDraft()) {
8597
                    $attributes['class'] = ' text-muted ';
8598
                }
8599
8600
                $url = $router->generate(
8601
                    'legacy_main',
8602
                    [
8603
                        'name' => 'exercise/overview.php',
8604
                        'cid' => $course->getId(),
8605
                        'sid' => $sessionId,
8606
                        'exerciseId' => $row->getField('id'),
8607
                    ]
8608
                );
8609
8610
                return Display::url($value, $url, $attributes);
8611
            }
8612
        );
8613
8614
        // 7. Add actions
8615
        if ($editAccess) {
8616
            // Edit
8617
            $myRowAction = new RowAction(
8618
                get_lang('Edit'),
8619
                'legacy_main',
8620
                false,
8621
                '_self',
8622
                ['class' => 'btn btn-secondary', 'icon' => 'fa fa-pen', 'title' => get_lang('Edit')]
8623
            );
8624
8625
            $myRowAction->setRouteParameters(
8626
                [
8627
                    'id',
8628
                    'name' => 'exercise/exercise_admin.php',
8629
                    'cid' => $course->getId(),
8630
                    'sid' => $sessionId,
8631
                ]
8632
            );
8633
            $grid->addRowAction($myRowAction);
8634
8635
            // Add questions
8636
            $myRowAction = new RowAction(
8637
                get_lang('Add questions'),
8638
                'legacy_main',
8639
                false,
8640
                '_self',
8641
                ['class' => 'btn btn-secondary']
8642
            );
8643
            $myRowAction->setRouteParameters(
8644
                [
8645
                    'id',
8646
                    'name' => 'exercise/admin.php',
8647
                    'cid' => $course->getId(),
8648
                    'sid' => $sessionId,
8649
                ]
8650
            );
8651
            $myRowAction->addManipulateRender(
8652
                function (RowAction $action, Row $row) use ($session, $repo) {
8653
                    return $repo->rowCanBeEdited($action, $row, $session);
8654
                }
8655
            );
8656
            $grid->addRowAction($myRowAction);
8657
8658
            // Shortcut
8659
            $myRowAction = new RowAction(
8660
                get_lang('Add shortcut'),
8661
                'legacy_main',
8662
                false,
8663
                '_self',
8664
                ['class' => 'btn btn-secondary']
8665
            );
8666
            $myRowAction->setRouteParameters(
8667
                [
8668
                    'id',
8669
                    'name' => 'exercise/exercise.php',
8670
                    'action' => 'add_shortcut',
8671
                    'cid' => $course->getId(),
8672
                    'sid' => $sessionId,
8673
                ]
8674
            );
8675
            $shortcutRepository = Container::getShortcutRepository();
8676
            $myRowAction->addManipulateRender(
8677
                function (RowAction $action, Row $row) use ($session, $course, $repo, $shortcutRepository, $sessionId) {
8678
                    /** @var CQuiz $exercise */
8679
                    $exercise = $row->getEntity();
8680
8681
                    $shortcut = $shortcutRepository->getShortcutFromResource($exercise);
8682
                    if ($shortcut) {
8683
                        $action->setTitle(get_lang('Remove shortcut'));
8684
                        $action->setRouteParameters(
8685
                            [
8686
                                'id',
8687
                                'name' => 'exercise/exercise.php',
8688
                                'action' => 'remove_shortcut',
8689
                                'cid' => $course->getId(),
8690
                                'sid' => $sessionId,
8691
                            ]
8692
                        );
8693
                    }
8694
8695
                    return $repo->rowCanBeEdited($action, $row, $session);
8696
                }
8697
            );
8698
            $grid->addRowAction($myRowAction);
8699
8700
            // Results
8701
            $myRowAction = new RowAction(
8702
                get_lang('Results'),
8703
                'legacy_main',
8704
                false,
8705
                '_self',
8706
                ['class' => 'btn btn-secondary']
8707
            );
8708
            $myRowAction->setRouteParameters(
8709
                [
8710
                    'id',
8711
                    'name' => 'exercise/exercise_report.php',
8712
                    'cid' => $course->getId(),
8713
                    'sid' => $sessionId,
8714
                ]
8715
            );
8716
            $myRowAction->addManipulateRender(
8717
                function (RowAction $action, Row $row) use ($session, $repo) {
8718
                    return $repo->rowCanBeEdited($action, $row, $session);
8719
                }
8720
            );
8721
8722
            // To use icon instead of col
8723
            //$myRowAction->render()
8724
            $grid->addRowAction($myRowAction);
8725
8726
            // Hide/show
8727
            $myRowAction = new RowAction(
8728
                get_lang('Active'),
8729
                'legacy_main',
8730
                false,
8731
                '_self'
8732
            );
8733
8734
            $myRowAction->addManipulateRender(
8735
                function (RowAction $action, Row $row) use ($session, $repo, $course, $sessionId) {
8736
                    $action = $repo->rowCanBeEdited($action, $row, $session);
8737
                    /** @var CQuiz $exercise */
8738
                    $exercise = $row->getEntity();
8739
                    $link = $exercise->getFirstResourceLinkFromCourseSession($course, $session);
8740
8741
                    if (null !== $link) {
8742
                        $visibleChoice = 'enable';
8743
                        $attributes = ['class' => 'btn btn-secondary'];
8744
                        $title = get_lang('Enable');
8745
                        if (ResourceLink::VISIBILITY_PUBLISHED === $link->getVisibility()) {
8746
                            $visibleChoice = 'disable';
8747
                            $attributes = ['class' => 'btn btn-secondary'];
8748
                            $title = get_lang('Disable');
8749
                        }
8750
                        $params = [
8751
                            'id' => $exercise->getIid(),
8752
                            'name' => 'exercise/exercise.php',
8753
                            'cid' => $course->getId(),
8754
                            'sid' => $sessionId,
8755
                            'action' => $visibleChoice,
8756
                        ];
8757
8758
                        if ($action) {
8759
                            $action->setTitle($title);
8760
                            $action->setAttributes($attributes);
8761
                            $action->setRouteParameters($params);
8762
                        }
8763
8764
                        return $action;
8765
                    }
8766
                }
8767
            );
8768
8769
            $grid->addRowAction($myRowAction);
8770
8771
            // Delete
8772
            $myRowAction = new RowAction(
8773
                get_lang('Delete'),
8774
                'legacy_main',
8775
                true,
8776
                '_self',
8777
                ['class' => 'btn btn-danger']
8778
            );
8779
            $myRowAction->setRouteParameters(
8780
                [
8781
                    'id',
8782
                    'name' => 'exercise/exercise.php',
8783
                    'cid' => $course->getId(),
8784
                    'sid' => $sessionId,
8785
                    'action' => 'delete',
8786
                ]
8787
            );
8788
            $myRowAction->addManipulateRender(
8789
                function (RowAction $action, Row $row) use ($session, $repo) {
8790
                    return $repo->rowCanBeEdited($action, $row, $session);
8791
                }
8792
            );
8793
            $grid->addRowAction($myRowAction);
8794
8795
            if (empty($session)) {
8796
                // Add mass actions
8797
                $deleteMassAction = new MassAction(
8798
                    'Delete',
8799
                    ['Exercise', 'deleteResource'],
8800
                    true,
8801
                    [],
8802
                    ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER
8803
                );
8804
                $grid->addMassAction($deleteMassAction);
8805
            }
8806
        }
8807
8808
        // 8. Set route and request
8809
        $grid
8810
            ->setRouteUrl(api_get_self().'?'.api_get_cidreq())
8811
            ->handleRequest(Container::getRequest())
8812
        ;
8813
8814
        return Container::$container->get('twig')->render(
8815
            '@ChamiloCore/Resource/grid.html.twig',
8816
            ['grid' => $grid]
8817
        );
8818
    }
8819
8820
    /**
8821
     * @return int value in minutes
8822
     */
8823
    public function getResultAccess()
8824
    {
8825
        $extraFieldValue = new ExtraFieldValue('exercise');
8826
        $value = $extraFieldValue->get_values_by_handler_and_field_variable(
8827
            $this->iId,
8828
            'results_available_for_x_minutes'
8829
        );
8830
8831
        if (!empty($value) && isset($value['value'])) {
8832
            return (int) $value['value'];
8833
        }
8834
8835
        return 0;
8836
    }
8837
8838
    /**
8839
     * @param array $exerciseResultInfo
8840
     *
8841
     * @return bool
8842
     */
8843
    public function getResultAccessTimeDiff($exerciseResultInfo)
8844
    {
8845
        $value = $this->getResultAccess();
8846
        if (!empty($value)) {
8847
            $endDate = new DateTime($exerciseResultInfo['exe_date'], new DateTimeZone('UTC'));
8848
            $endDate->add(new DateInterval('PT'.$value.'M'));
8849
            $now = time();
8850
            if ($endDate->getTimestamp() > $now) {
8851
                return (int) $endDate->getTimestamp() - $now;
8852
            }
8853
        }
8854
8855
        return 0;
8856
    }
8857
8858
    /**
8859
     * @param array $exerciseResultInfo
8860
     *
8861
     * @return bool
8862
     */
8863
    public function hasResultsAccess($exerciseResultInfo)
8864
    {
8865
        $diff = $this->getResultAccessTimeDiff($exerciseResultInfo);
8866
        if (0 === $diff) {
8867
            return false;
8868
        }
8869
8870
        return true;
8871
    }
8872
8873
    /**
8874
     * @return int
8875
     */
8876
    public function getResultsAccess()
8877
    {
8878
        $extraFieldValue = new ExtraFieldValue('exercise');
8879
        $value = $extraFieldValue->get_values_by_handler_and_field_variable(
8880
            $this->iId,
8881
            'results_available_for_x_minutes'
8882
        );
8883
        if (!empty($value)) {
8884
            return (int) $value;
8885
        }
8886
8887
        return 0;
8888
    }
8889
8890
    /**
8891
     * @param int   $questionId
8892
     * @param bool  $show_results
8893
     * @param array $question_result
8894
     */
8895
    public function getDelineationResult(Question $objQuestionTmp, $questionId, $show_results, $question_result)
8896
    {
8897
        $id = (int) $objQuestionTmp->id;
8898
        $questionId = (int) $questionId;
8899
8900
        $final_overlap = $question_result['extra']['final_overlap'];
8901
        $final_missing = $question_result['extra']['final_missing'];
8902
        $final_excess = $question_result['extra']['final_excess'];
8903
8904
        $overlap_color = $question_result['extra']['overlap_color'];
8905
        $missing_color = $question_result['extra']['missing_color'];
8906
        $excess_color = $question_result['extra']['excess_color'];
8907
8908
        $threadhold1 = $question_result['extra']['threadhold1'];
8909
        $threadhold2 = $question_result['extra']['threadhold2'];
8910
        $threadhold3 = $question_result['extra']['threadhold3'];
8911
8912
        if ($show_results) {
8913
            if ($overlap_color) {
8914
                $overlap_color = 'green';
8915
            } else {
8916
                $overlap_color = 'red';
8917
            }
8918
8919
            if ($missing_color) {
8920
                $missing_color = 'green';
8921
            } else {
8922
                $missing_color = 'red';
8923
            }
8924
            if ($excess_color) {
8925
                $excess_color = 'green';
8926
            } else {
8927
                $excess_color = 'red';
8928
            }
8929
8930
            if (!is_numeric($final_overlap)) {
8931
                $final_overlap = 0;
8932
            }
8933
8934
            if (!is_numeric($final_missing)) {
8935
                $final_missing = 0;
8936
            }
8937
            if (!is_numeric($final_excess)) {
8938
                $final_excess = 0;
8939
            }
8940
8941
            if ($final_excess > 100) {
8942
                $final_excess = 100;
8943
            }
8944
8945
            $table_resume = '
8946
                    <table class="data_table">
8947
                        <tr class="row_odd" >
8948
                            <td>&nbsp;</td>
8949
                            <td><b>'.get_lang('Requirements').'</b></td>
8950
                            <td><b>'.get_lang('Your answer').'</b></td>
8951
                        </tr>
8952
                        <tr class="row_even">
8953
                            <td><b>'.get_lang('Overlapping areaping area').'</b></td>
8954
                            <td>'.get_lang('Minimumimum').' '.$threadhold1.'</td>
8955
                            <td>
8956
                                <div style="color:'.$overlap_color.'">
8957
                                    '.$final_overlap < 0 ? 0 : (int) $final_overlap.'
8958
                                </div>
8959
                            </td>
8960
                        </tr>
8961
                        <tr>
8962
                            <td><b>'.get_lang('Excessive areaive area').'</b></td>
8963
                            <td>'.get_lang('max. 20 characters, e.g. <i>INNOV21</i>').' '.$threadhold2.'</td>
8964
                            <td>
8965
                                <div style="color:'.$excess_color.'">
8966
                                    '.$final_excess < 0 ? 0 : (int) $final_excess.'
8967
                                </div>
8968
                            </td>
8969
                        </tr>
8970
                        <tr class="row_even">
8971
                            <td><b>'.get_lang('Missing area area').'</b></td>
8972
                            <td>'.get_lang('max. 20 characters, e.g. <i>INNOV21</i>').' '.$threadhold3.'</td>
8973
                            <td>
8974
                                <div style="color:'.$missing_color.'">
8975
                                    '.$final_missing < 0 ? 0 : (int) $final_missing.'
8976
                                </div>
8977
                            </td>
8978
                        </tr>
8979
                    </table>
8980
                ';
8981
8982
            $answerType = $objQuestionTmp->selectType();
8983
            if (HOT_SPOT_DELINEATION != $answerType) {
8984
                $item_list = explode('@@', $destination);
8985
                $try = $item_list[0];
8986
                $lp = $item_list[1];
8987
                $destinationid = $item_list[2];
8988
                $url = $item_list[3];
8989
                $table_resume = '';
8990
            } else {
8991
                if (0 == $next) {
8992
                    $try = $try_hotspot;
8993
                    $lp = $lp_hotspot;
8994
                    $destinationid = $select_question_hotspot;
8995
                    $url = $url_hotspot;
8996
                } else {
8997
                    //show if no error
8998
                    $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
8999
                    $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
9000
                }
9001
            }
9002
9003
            echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>';
9004
            if (HOT_SPOT_DELINEATION == $answerType) {
9005
                if ($organs_at_risk_hit > 0) {
9006
                    $message = '<br />'.get_lang('Your result is :').' <b>'.$result_comment.'</b><br />';
9007
                    $message .= '<p style="color:#DC0A0A;"><b>'.get_lang('One (or more) area at risk has been hit').'</b></p>';
9008
                } else {
9009
                    $message = '<p>'.get_lang('Your delineation :').'</p>';
9010
                    $message .= $table_resume;
9011
                    $message .= '<br />'.get_lang('Your result is :').' <b>'.$result_comment.'</b><br />';
9012
                }
9013
                $message .= '<p>'.$comment.'</p>';
9014
                echo $message;
9015
            } else {
9016
                echo '<p>'.$comment.'</p>';
9017
            }
9018
9019
            // Showing the score
9020
            /*$queryfree = "SELECT marks FROM $TBL_TRACK_ATTEMPT
9021
                          WHERE exe_id = $id AND question_id =  $questionId";
9022
            $resfree = Database::query($queryfree);
9023
            $questionScore = Database::result($resfree, 0, 'marks');
9024
            $totalScore += $questionScore;*/
9025
            $relPath = api_get_path(REL_PATH);
9026
            echo '</table></td></tr>';
9027
            echo "
9028
                        <tr>
9029
                            <td colspan=\"2\">
9030
                                <div id=\"hotspot-solution\"></div>
9031
                                <script>
9032
                                    $(function() {
9033
                                        new HotspotQuestion({
9034
                                            questionId: $questionId,
9035
                                            exerciseId: {$this->id},
9036
                                            exeId: $id,
9037
                                            selector: '#hotspot-solution',
9038
                                            for: 'solution',
9039
                                            relPath: '$relPath'
9040
                                        });
9041
                                    });
9042
                                </script>
9043
                            </td>
9044
                        </tr>
9045
                    </table>
9046
                ";
9047
        }
9048
    }
9049
9050
    /**
9051
     * Clean exercise session variables.
9052
     */
9053
    public static function cleanSessionVariables()
9054
    {
9055
        Session::erase('objExercise');
9056
        Session::erase('exe_id');
9057
        Session::erase('calculatedAnswerId');
9058
        Session::erase('duration_time_previous');
9059
        Session::erase('duration_time');
9060
        Session::erase('objQuestion');
9061
        Session::erase('objAnswer');
9062
        Session::erase('questionList');
9063
        Session::erase('exerciseResult');
9064
        Session::erase('firstTime');
9065
9066
        Session::erase('exerciseResultCoordinates');
9067
        Session::erase('hotspot_coord');
9068
        Session::erase('hotspot_dest');
9069
        Session::erase('hotspot_delineation_result');
9070
    }
9071
9072
    /**
9073
     * Get the first LP found matching the session ID.
9074
     *
9075
     * @param int $sessionId
9076
     *
9077
     * @return array
9078
     */
9079
    public function getLpBySession($sessionId)
9080
    {
9081
        if (!empty($this->lpList)) {
9082
            $sessionId = (int) $sessionId;
9083
9084
            foreach ($this->lpList as $lp) {
9085
                if ((int) $lp['session_id'] == $sessionId) {
9086
                    return $lp;
9087
                }
9088
            }
9089
9090
            return current($this->lpList);
9091
        }
9092
9093
        return [
9094
            'lp_id' => 0,
9095
            'max_score' => 0,
9096
            'session_id' => 0,
9097
        ];
9098
    }
9099
9100
    public static function saveExerciseInLp($safe_item_id, $safe_exe_id)
9101
    {
9102
        $lp = Session::read('oLP');
9103
9104
        $safe_exe_id = (int) $safe_exe_id;
9105
        $safe_item_id = (int) $safe_item_id;
9106
9107
        if (empty($lp) || empty($safe_exe_id) || empty($safe_item_id)) {
9108
            return false;
9109
        }
9110
9111
        $viewId = $lp->get_view_id();
9112
        $course_id = api_get_course_int_id();
9113
        $userId = (int) api_get_user_id();
9114
        $viewId = (int) $viewId;
9115
9116
        $TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
9117
        $TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
9118
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
9119
9120
        $sql = "SELECT start_date, exe_date, exe_result, exe_weighting, exe_exo_id, exe_duration
9121
                FROM $TBL_TRACK_EXERCICES
9122
                WHERE exe_id = $safe_exe_id AND exe_user_id = $userId";
9123
        $res = Database::query($sql);
9124
        $row_dates = Database::fetch_array($res);
9125
9126
        if (empty($row_dates)) {
9127
            return false;
9128
        }
9129
9130
        $duration = (int) $row_dates['exe_duration'];
9131
        $score = (float) $row_dates['exe_result'];
9132
        $max_score = (float) $row_dates['exe_weighting'];
9133
9134
        $sql = "UPDATE $TBL_LP_ITEM SET
9135
                    max_score = '$max_score'
9136
                WHERE iid = $safe_item_id";
9137
        Database::query($sql);
9138
9139
        $sql = "SELECT id FROM $TBL_LP_ITEM_VIEW
9140
                WHERE
9141
                    c_id = $course_id AND
9142
                    lp_item_id = $safe_item_id AND
9143
                    lp_view_id = $viewId
9144
                ORDER BY id DESC
9145
                LIMIT 1";
9146
        $res_last_attempt = Database::query($sql);
9147
9148
        if (Database::num_rows($res_last_attempt) && !api_is_invitee()) {
9149
            $row_last_attempt = Database::fetch_row($res_last_attempt);
9150
            $lp_item_view_id = $row_last_attempt[0];
9151
9152
            $exercise = new Exercise($course_id);
9153
            $exercise->read($row_dates['exe_exo_id']);
9154
            $status = 'completed';
9155
9156
            if (!empty($exercise->pass_percentage)) {
9157
                $status = 'failed';
9158
                $success = ExerciseLib::isSuccessExerciseResult(
9159
                    $score,
9160
                    $max_score,
9161
                    $exercise->pass_percentage
9162
                );
9163
                if ($success) {
9164
                    $status = 'passed';
9165
                }
9166
            }
9167
9168
            $sql = "UPDATE $TBL_LP_ITEM_VIEW SET
9169
                        status = '$status',
9170
                        score = $score,
9171
                        total_time = $duration
9172
                    WHERE iid = $lp_item_view_id";
9173
            Database::query($sql);
9174
9175
            $sql = "UPDATE $TBL_TRACK_EXERCICES SET
9176
                        orig_lp_item_view_id = $lp_item_view_id
9177
                    WHERE exe_id = ".$safe_exe_id;
9178
            Database::query($sql);
9179
        }
9180
    }
9181
9182
    /**
9183
     * Get the user answers saved in exercise.
9184
     *
9185
     * @param int $attemptId
9186
     *
9187
     * @return array
9188
     */
9189
    public function getUserAnswersSavedInExercise($attemptId)
9190
    {
9191
        $exerciseResult = [];
9192
9193
        $attemptList = Event::getAllExerciseEventByExeId($attemptId);
9194
9195
        foreach ($attemptList as $questionId => $options) {
9196
            foreach ($options as $option) {
9197
                $question = Question::read($option['question_id']);
9198
9199
                switch ($question->type) {
9200
                    case FILL_IN_BLANKS:
9201
                        $option['answer'] = $this->fill_in_blank_answer_to_string($option['answer']);
9202
                        break;
9203
                }
9204
9205
                if (!empty($option['answer'])) {
9206
                    $exerciseResult[] = $questionId;
9207
9208
                    break;
9209
                }
9210
            }
9211
        }
9212
9213
        return $exerciseResult;
9214
    }
9215
9216
    /**
9217
     * Get the number of user answers saved in exercise.
9218
     *
9219
     * @param int $attemptId
9220
     *
9221
     * @return int
9222
     */
9223
    public function countUserAnswersSavedInExercise($attemptId)
9224
    {
9225
        $answers = $this->getUserAnswersSavedInExercise($attemptId);
9226
9227
        return count($answers);
9228
    }
9229
9230
    public static function allowAction($action)
9231
    {
9232
        if (api_is_platform_admin()) {
9233
            return true;
9234
        }
9235
9236
        $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
9237
        $disableClean = api_get_configuration_value('disable_clean_exercise_results_for_teachers');
9238
9239
        switch ($action) {
9240
            case 'delete':
9241
                if (api_is_allowed_to_edit(null, true)) {
9242
                    if ($limitTeacherAccess) {
9243
                        return false;
9244
                    }
9245
9246
                    return true;
9247
                }
9248
                break;
9249
            case 'clean_results':
9250
                if (api_is_allowed_to_edit(null, true)) {
9251
                    if ($limitTeacherAccess) {
9252
                        return false;
9253
                    }
9254
9255
                    if ($disableClean) {
9256
                        return false;
9257
                    }
9258
9259
                    return true;
9260
                }
9261
9262
                break;
9263
        }
9264
9265
        return false;
9266
    }
9267
9268
    public static function getLpListFromExercise($exerciseId, $courseId)
9269
    {
9270
        $tableLpItem = Database::get_course_table(TABLE_LP_ITEM);
9271
        $tblLp = Database::get_course_table(TABLE_LP_MAIN);
9272
9273
        $exerciseId = (int) $exerciseId;
9274
        $courseId = (int) $courseId;
9275
9276
        $sql = "SELECT
9277
                    lp.name,
9278
                    lpi.lp_id,
9279
                    lpi.max_score,
9280
                    lp.session_id
9281
                FROM $tableLpItem lpi
9282
                INNER JOIN $tblLp lp
9283
                ON (lpi.lp_id = lp.iid AND lpi.c_id = lp.c_id)
9284
                WHERE
9285
                    lpi.c_id = $courseId AND
9286
                    lpi.item_type = '".TOOL_QUIZ."' AND
9287
                    lpi.path = '$exerciseId'";
9288
        $result = Database::query($sql);
9289
        $lpList = [];
9290
        if (Database::num_rows($result) > 0) {
9291
            $lpList = Database::store_result($result, 'ASSOC');
9292
        }
9293
9294
        return $lpList;
9295
    }
9296
9297
    /**
9298
     * Get number of questions in exercise by user attempt.
9299
     *
9300
     * @return int
9301
     */
9302
    private function countQuestionsInExercise()
9303
    {
9304
        $lpId = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0;
9305
        $lpItemId = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0;
9306
        $lpItemViewId = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : 0;
9307
9308
        $trackInfo = $this->get_stat_track_exercise_info($lpId, $lpItemId, $lpItemViewId);
9309
9310
        if (!empty($trackInfo)) {
9311
            $questionIds = explode(',', $trackInfo['data_tracking']);
9312
9313
            return count($questionIds);
9314
        }
9315
9316
        return $this->getQuestionCount();
9317
    }
9318
9319
    /**
9320
     * Gets the question list ordered by the question_order setting (drag and drop).
9321
     *
9322
     * @param bool $adminView Optional.
9323
     *
9324
     * @return array
9325
     */
9326
    private function getQuestionOrderedList($adminView = false)
9327
    {
9328
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
9329
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
9330
9331
        // Getting question_order to verify that the question
9332
        // list is correct and all question_order's were set
9333
        $sql = "SELECT DISTINCT count(e.question_order) as count
9334
                FROM $TBL_EXERCICE_QUESTION e
9335
                INNER JOIN $TBL_QUESTIONS q
9336
                ON (e.question_id = q.id AND e.c_id = q.c_id)
9337
                WHERE
9338
                  e.c_id = {$this->course_id} AND
9339
                  e.exercice_id	= ".$this->id;
9340
9341
        $result = Database::query($sql);
9342
        $row = Database::fetch_array($result);
9343
        $count_question_orders = $row['count'];
9344
9345
        // Getting question list from the order (question list drag n drop interface).
9346
        $sql = "SELECT DISTINCT e.question_id, e.question_order
9347
                FROM $TBL_EXERCICE_QUESTION e
9348
                INNER JOIN $TBL_QUESTIONS q
9349
                ON (e.question_id = q.id AND e.c_id = q.c_id)
9350
                WHERE
9351
                    e.c_id = {$this->course_id} AND
9352
                    e.exercice_id = '".$this->id."'
9353
                ORDER BY question_order";
9354
        $result = Database::query($sql);
9355
9356
        // Fills the array with the question ID for this exercise
9357
        // the key of the array is the question position
9358
        $temp_question_list = [];
9359
        $counter = 1;
9360
        $questionList = [];
9361
        while ($new_object = Database::fetch_object($result)) {
9362
            if (!$adminView) {
9363
                // Correct order.
9364
                $questionList[$new_object->question_order] = $new_object->question_id;
9365
            } else {
9366
                $questionList[$counter] = $new_object->question_id;
9367
            }
9368
9369
            // Just in case we save the order in other array
9370
            $temp_question_list[$counter] = $new_object->question_id;
9371
            $counter++;
9372
        }
9373
9374
        if (!empty($temp_question_list)) {
9375
            /* If both array don't match it means that question_order was not correctly set
9376
               for all questions using the default mysql order */
9377
            if (count($temp_question_list) != $count_question_orders) {
9378
                $questionList = $temp_question_list;
9379
            }
9380
        }
9381
9382
        return $questionList;
9383
    }
9384
9385
    /**
9386
     * Select N values from the questions per category array.
9387
     *
9388
     * @param array $categoriesAddedInExercise
9389
     * @param array $question_list
9390
     * @param array $questions_by_category     per category
9391
     * @param bool  $flatResult
9392
     * @param bool  $randomizeQuestions
9393
     *
9394
     * @return array
9395
     */
9396
    private function pickQuestionsPerCategory(
9397
        $categoriesAddedInExercise,
9398
        $question_list,
9399
        &$questions_by_category,
9400
        $flatResult = true,
9401
        $randomizeQuestions = false
9402
    ) {
9403
        $addAll = true;
9404
        $categoryCountArray = [];
9405
9406
        // Getting how many questions will be selected per category.
9407
        if (!empty($categoriesAddedInExercise)) {
9408
            $addAll = false;
9409
            // Parsing question according the category rel exercise settings
9410
            foreach ($categoriesAddedInExercise as $category_info) {
9411
                $category_id = $category_info['category_id'];
9412
                if (isset($questions_by_category[$category_id])) {
9413
                    // How many question will be picked from this category.
9414
                    $count = $category_info['count_questions'];
9415
                    // -1 means all questions
9416
                    $categoryCountArray[$category_id] = $count;
9417
                    if (-1 == $count) {
9418
                        $categoryCountArray[$category_id] = 999;
9419
                    }
9420
                }
9421
            }
9422
        }
9423
9424
        if (!empty($questions_by_category)) {
9425
            $temp_question_list = [];
9426
            foreach ($questions_by_category as $category_id => &$categoryQuestionList) {
9427
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
9428
                    $numberOfQuestions = 0;
9429
                    if (isset($categoryCountArray[$category_id])) {
9430
                        $numberOfQuestions = $categoryCountArray[$category_id];
9431
                    }
9432
                }
9433
9434
                if ($addAll) {
9435
                    $numberOfQuestions = 999;
9436
                }
9437
9438
                if (!empty($numberOfQuestions)) {
9439
                    $elements = TestCategory::getNElementsFromArray(
9440
                        $categoryQuestionList,
9441
                        $numberOfQuestions,
9442
                        $randomizeQuestions
9443
                    );
9444
9445
                    if (!empty($elements)) {
9446
                        $temp_question_list[$category_id] = $elements;
9447
                        $categoryQuestionList = $elements;
9448
                    }
9449
                }
9450
            }
9451
9452
            if (!empty($temp_question_list)) {
9453
                if ($flatResult) {
9454
                    $temp_question_list = array_flatten($temp_question_list);
9455
                }
9456
                $question_list = $temp_question_list;
9457
            }
9458
        }
9459
9460
        return $question_list;
9461
    }
9462
9463
    /**
9464
     * Changes the exercise id.
9465
     *
9466
     * @param int $id - exercise id
9467
     */
9468
    private function updateId($id)
9469
    {
9470
        $this->id = $id;
9471
    }
9472
9473
    /**
9474
     * Sends a notification when a user ends an examn.
9475
     *
9476
     * @param array  $question_list_answers
9477
     * @param string $origin
9478
     * @param array  $user_info
9479
     * @param string $url_email
9480
     * @param array  $teachers
9481
     */
9482
    private function sendNotificationForOpenQuestions(
9483
        $question_list_answers,
9484
        $origin,
9485
        $user_info,
9486
        $url_email,
9487
        $teachers
9488
    ) {
9489
        // Email configuration settings
9490
        $courseCode = api_get_course_id();
9491
        $courseInfo = api_get_course_info($courseCode);
9492
        $sessionId = api_get_session_id();
9493
        $sessionData = '';
9494
        if (!empty($sessionId)) {
9495
            $sessionInfo = api_get_session_info($sessionId);
9496
            if (!empty($sessionInfo)) {
9497
                $sessionData = '<tr>'
9498
                    .'<td><em>'.get_lang('Session name').'</em></td>'
9499
                    .'<td>&nbsp;<b>'.$sessionInfo['name'].'</b></td>'
9500
                    .'</tr>';
9501
            }
9502
        }
9503
9504
        $msg = get_lang('A learner has answered an open question').'<br /><br />'
9505
            .get_lang('Attempt details').' : <br /><br />'
9506
            .'<table>'
9507
            .'<tr>'
9508
            .'<td><em>'.get_lang('Course name').'</em></td>'
9509
            .'<td>&nbsp;<b>#course#</b></td>'
9510
            .'</tr>'
9511
            .$sessionData
9512
            .'<tr>'
9513
            .'<td>'.get_lang('Test attempted').'</td>'
9514
            .'<td>&nbsp;#exercise#</td>'
9515
            .'</tr>'
9516
            .'<tr>'
9517
            .'<td>'.get_lang('Learner name').'</td>'
9518
            .'<td>&nbsp;#firstName# #lastName#</td>'
9519
            .'</tr>'
9520
            .'<tr>'
9521
            .'<td>'.get_lang('Learner e-mail').'</td>'
9522
            .'<td>&nbsp;#mail#</td>'
9523
            .'</tr>'
9524
            .'</table>';
9525
9526
        $open_question_list = null;
9527
        foreach ($question_list_answers as $item) {
9528
            $question = $item['question'];
9529
            $answer = $item['answer'];
9530
            $answer_type = $item['answer_type'];
9531
9532
            if (!empty($question) && !empty($answer) && FREE_ANSWER == $answer_type) {
9533
                $open_question_list .=
9534
                    '<tr>'
9535
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
9536
                    .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
9537
                    .'</tr>'
9538
                    .'<tr>'
9539
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
9540
                    .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
9541
                    .'</tr>';
9542
            }
9543
        }
9544
9545
        if (!empty($open_question_list)) {
9546
            $msg .= '<p><br />'.get_lang('A learner has answered an open questionAre').' :</p>'.
9547
                '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
9548
            $msg .= $open_question_list;
9549
            $msg .= '</table><br />';
9550
9551
            $msg = str_replace('#exercise#', $this->exercise, $msg);
9552
            $msg = str_replace('#firstName#', $user_info['firstname'], $msg);
9553
            $msg = str_replace('#lastName#', $user_info['lastname'], $msg);
9554
            $msg = str_replace('#mail#', $user_info['email'], $msg);
9555
            $msg = str_replace(
9556
                '#course#',
9557
                Display::url($courseInfo['title'], $courseInfo['course_public_url'].'?sid='.$sessionId),
9558
                $msg
9559
            );
9560
9561
            if ('learnpath' != $origin) {
9562
                $msg .= '<br /><a href="#url#">'.get_lang('Click this link to check the answer and/or give feedback').'</a>';
9563
            }
9564
            $msg = str_replace('#url#', $url_email, $msg);
9565
            $subject = get_lang('A learner has answered an open question');
9566
9567
            if (!empty($teachers)) {
9568
                foreach ($teachers as $user_id => $teacher_data) {
9569
                    MessageManager::send_message_simple(
9570
                        $user_id,
9571
                        $subject,
9572
                        $msg
9573
                    );
9574
                }
9575
            }
9576
        }
9577
    }
9578
9579
    /**
9580
     * Send notification for oral questions.
9581
     *
9582
     * @param array  $question_list_answers
9583
     * @param string $origin
9584
     * @param int    $exe_id
9585
     * @param array  $user_info
9586
     * @param string $url_email
9587
     * @param array  $teachers
9588
     */
9589
    private function sendNotificationForOralQuestions(
9590
        $question_list_answers,
9591
        $origin,
9592
        $exe_id,
9593
        $user_info,
9594
        $url_email,
9595
        $teachers
9596
    ) {
9597
        // Email configuration settings
9598
        $courseCode = api_get_course_id();
9599
        $courseInfo = api_get_course_info($courseCode);
9600
        $oral_question_list = null;
9601
        foreach ($question_list_answers as $item) {
9602
            $question = $item['question'];
9603
            $file = $item['generated_oral_file'];
9604
            $answer = $item['answer'];
9605
            if (0 == $answer) {
9606
                $answer = '';
9607
            }
9608
            $answer_type = $item['answer_type'];
9609
            if (!empty($question) && (!empty($answer) || !empty($file)) && ORAL_EXPRESSION == $answer_type) {
9610
                if (!empty($file)) {
9611
                    $file = Display::url($file, $file);
9612
                }
9613
                $oral_question_list .= '<br />
9614
                    <table width="730" height="136" border="0" cellpadding="3" cellspacing="3">
9615
                    <tr>
9616
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>
9617
                        <td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>
9618
                    </tr>
9619
                    <tr>
9620
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>
9621
                        <td valign="top" bgcolor="#F3F3F3">'.$answer.$file.'</td>
9622
                    </tr></table>';
9623
            }
9624
        }
9625
9626
        if (!empty($oral_question_list)) {
9627
            $msg = get_lang('A learner has attempted one or more oral question').'<br /><br />
9628
                    '.get_lang('Attempt details').' : <br /><br />
9629
                    <table>
9630
                        <tr>
9631
                            <td><em>'.get_lang('Course name').'</em></td>
9632
                            <td>&nbsp;<b>#course#</b></td>
9633
                        </tr>
9634
                        <tr>
9635
                            <td>'.get_lang('Test attempted').'</td>
9636
                            <td>&nbsp;#exercise#</td>
9637
                        </tr>
9638
                        <tr>
9639
                            <td>'.get_lang('Learner name').'</td>
9640
                            <td>&nbsp;#firstName# #lastName#</td>
9641
                        </tr>
9642
                        <tr>
9643
                            <td>'.get_lang('Learner e-mail').'</td>
9644
                            <td>&nbsp;#mail#</td>
9645
                        </tr>
9646
                    </table>';
9647
            $msg .= '<br />'.sprintf(get_lang('A learner has attempted one or more oral questionAreX'), $oral_question_list).'<br />';
9648
            $msg1 = str_replace('#exercise#', $this->exercise, $msg);
9649
            $msg = str_replace('#firstName#', $user_info['firstname'], $msg1);
9650
            $msg1 = str_replace('#lastName#', $user_info['lastname'], $msg);
9651
            $msg = str_replace('#mail#', $user_info['email'], $msg1);
9652
            $msg = str_replace('#course#', $courseInfo['name'], $msg1);
9653
9654
            if (!in_array($origin, ['learnpath', 'embeddable'])) {
9655
                $msg .= '<br /><a href="#url#">'.get_lang('Click this link to check the answer and/or give feedback').'</a>';
9656
            }
9657
            $msg1 = str_replace('#url#', $url_email, $msg);
9658
            $mail_content = $msg1;
9659
            $subject = get_lang('A learner has attempted one or more oral question');
9660
9661
            if (!empty($teachers)) {
9662
                foreach ($teachers as $user_id => $teacher_data) {
9663
                    MessageManager::send_message_simple(
9664
                        $user_id,
9665
                        $subject,
9666
                        $mail_content
9667
                    );
9668
                }
9669
            }
9670
        }
9671
    }
9672
9673
    /**
9674
     * Returns an array with the media list.
9675
     *
9676
     * @param array $questionList question list
9677
     *
9678
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
9679
     * <code>
9680
     * array (size=2)
9681
     *  999 =>
9682
     *    array (size=3)
9683
     *      0 => int 7
9684
     *      1 => int 6
9685
     *      2 => int 3254
9686
     *  100 =>
9687
     *   array (size=1)
9688
     *      0 => int 5
9689
     *  </code>
9690
     */
9691
    private function setMediaList($questionList)
9692
    {
9693
        $mediaList = [];
9694
        /*
9695
         * Media feature is not activated in 1.11.x
9696
        if (!empty($questionList)) {
9697
            foreach ($questionList as $questionId) {
9698
                $objQuestionTmp = Question::read($questionId, $this->course_id);
9699
                // If a media question exists
9700
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
9701
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
9702
                } else {
9703
                    // Always the last item
9704
                    $mediaList[999][] = $objQuestionTmp->id;
9705
                }
9706
            }
9707
        }*/
9708
9709
        $this->mediaList = $mediaList;
9710
    }
9711
9712
    /**
9713
     * @return HTML_QuickForm_group
9714
     */
9715
    private function setResultDisabledGroup(FormValidator $form)
9716
    {
9717
        $resultDisabledGroup = [];
9718
9719
        $resultDisabledGroup[] = $form->createElement(
9720
            'radio',
9721
            'results_disabled',
9722
            null,
9723
            get_lang('Auto-evaluation mode: show score and expected answers'),
9724
            '0',
9725
            ['id' => 'result_disabled_0']
9726
        );
9727
9728
        $resultDisabledGroup[] = $form->createElement(
9729
            'radio',
9730
            'results_disabled',
9731
            null,
9732
            get_lang('Exam mode: Do not show score nor answers'),
9733
            '1',
9734
            ['id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()']
9735
        );
9736
9737
        $resultDisabledGroup[] = $form->createElement(
9738
            'radio',
9739
            'results_disabled',
9740
            null,
9741
            get_lang('Practice mode: Show score only, by category if at least one is used'),
9742
            '2',
9743
            ['id' => 'result_disabled_2', 'onclick' => 'check_results_disabled()']
9744
        );
9745
9746
        if (in_array($this->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
9747
            return $form->addGroup(
9748
                $resultDisabledGroup,
9749
                null,
9750
                get_lang('ShowResults and feedback and feedback and feedback and feedback and feedback and feedbackToStudents')
9751
            );
9752
        }
9753
9754
        $resultDisabledGroup[] = $form->createElement(
9755
            'radio',
9756
            'results_disabled',
9757
            null,
9758
            get_lang('Show score on every attempt, show correct answers only on last attempt (only works with an attempts limit)'),
9759
            '4',
9760
            ['id' => 'result_disabled_4']
9761
        );
9762
9763
        $resultDisabledGroup[] = $form->createElement(
9764
            'radio',
9765
            'results_disabled',
9766
            null,
9767
            get_lang('Do not show the score (only when user finishes all attempts) but show feedback for each attempt.'),
9768
            '5',
9769
            ['id' => 'result_disabled_5', 'onclick' => 'check_results_disabled()']
9770
        );
9771
9772
        $resultDisabledGroup[] = $form->createElement(
9773
            'radio',
9774
            'results_disabled',
9775
            null,
9776
            get_lang('TestRankingMode'),
9777
            RESULT_DISABLE_RANKING,
9778
            ['id' => 'result_disabled_6']
9779
        );
9780
9781
        $resultDisabledGroup[] = $form->createElement(
9782
            'radio',
9783
            'results_disabled',
9784
            null,
9785
            get_lang('TestShowOnlyGlobalScoreAndCorrect answers'),
9786
            RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
9787
            ['id' => 'result_disabled_7']
9788
        );
9789
9790
        $resultDisabledGroup[] = $form->createElement(
9791
            'radio',
9792
            'results_disabled',
9793
            null,
9794
            get_lang('TestAutoEvaluationAndRankingMode'),
9795
            RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
9796
            ['id' => 'result_disabled_8']
9797
        );
9798
9799
        return $form->addGroup(
9800
            $resultDisabledGroup,
9801
            null,
9802
            get_lang('ShowResults and feedback and feedback and feedback and feedback and feedback and feedbackToStudents')
9803
        );
9804
    }
9805
}
9806