Passed
Push — 1.11.x ( 659998...c58798 )
by Angel Fernando Quiroz
10:01
created

Exercise::remedialCourseList()   D

Complexity

Conditions 29

Size

Total Lines 137
Code Lines 93

Duplication

Lines 0
Ratio 0 %

Importance

Changes 16
Bugs 0 Features 1
Metric Value
cc 29
eloc 93
c 16
b 0
f 1
nop 3
dl 0
loc 137
rs 4.1666

2 Methods

Rating   Name   Duplication   Size   Complexity  
A Exercise::countQuestionsInExercise() 0 15 5
A Exercise::getQuestionOrderedList() 0 57 5

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
6
use Chamilo\CoreBundle\Entity\GradebookLink;
7
use Chamilo\CoreBundle\Entity\TrackEExerciseConfirmation;
8
use Chamilo\CoreBundle\Entity\TrackEHotspot;
9
use Chamilo\CourseBundle\Entity\CExerciseCategory;
10
use ChamiloSession as Session;
11
use Doctrine\DBAL\Types\Type;
12
13
/**
14
 * Class Exercise.
15
 *
16
 * Allows to instantiate an object of type Exercise
17
 *
18
 * @todo use getters and setters correctly
19
 *
20
 * @author Olivier Brouckaert
21
 * @author Julio Montoya Cleaning exercises
22
 * Modified by Hubert Borderiou #294
23
 */
24
class Exercise
25
{
26
    public const PAGINATION_ITEMS_PER_PAGE = 20;
27
    public $iId;
28
    public $id;
29
    public $name;
30
    public $title;
31
    public $exercise;
32
    public $description;
33
    public $sound;
34
    public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
35
    public $random;
36
    public $random_answers;
37
    public $active;
38
    public $timeLimit;
39
    public $attempts;
40
    public $feedback_type;
41
    public $end_time;
42
    public $start_time;
43
    public $questionList; // array with the list of this exercise's questions
44
    /* including question list of the media */
45
    public $questionListUncompressed;
46
    public $results_disabled;
47
    public $expired_time;
48
    public $course;
49
    public $course_id;
50
    public $propagate_neg;
51
    public $saveCorrectAnswers;
52
    public $review_answers;
53
    public $randomByCat;
54
    public $text_when_finished;
55
    public $display_category_name;
56
    public $pass_percentage;
57
    public $edit_exercise_in_lp = false;
58
    public $is_gradebook_locked = false;
59
    public $exercise_was_added_in_lp = false;
60
    public $lpList = [];
61
    public $force_edit_exercise_in_lp = false;
62
    public $categories;
63
    public $categories_grouping = true;
64
    public $endButton = 0;
65
    public $categoryWithQuestionList;
66
    public $mediaList;
67
    public $loadQuestionAJAX = false;
68
    // Notification send to the teacher.
69
    public $emailNotificationTemplate = null;
70
    // Notification send to the student.
71
    public $emailNotificationTemplateToUser = null;
72
    public $countQuestions = 0;
73
    public $fastEdition = false;
74
    public $modelType = 1;
75
    public $questionSelectionType = EX_Q_SELECTION_ORDERED;
76
    public $hideQuestionTitle = 0;
77
    public $scoreTypeModel = 0;
78
    public $categoryMinusOne = true; // Shows the category -1: See BT#6540
79
    public $globalCategoryId = null;
80
    public $onSuccessMessage = null;
81
    public $onFailedMessage = null;
82
    public $emailAlert;
83
    public $notifyUserByEmail = '';
84
    public $sessionId = 0;
85
    public $questionFeedbackEnabled = false;
86
    public $questionTypeWithFeedback;
87
    public $showPreviousButton;
88
    public $notifications;
89
    public $export = false;
90
    public $autolaunch;
91
    public $exerciseCategoryId;
92
    public $pageResultConfiguration;
93
    public $preventBackwards;
94
    public $currentQuestion;
95
    public $hideComment;
96
    public $hideNoAnswer;
97
    public $hideExpectedAnswer;
98
    public $forceShowExpectedChoiceColumn;
99
    public $disableHideCorrectAnsweredQuestions;
100
101
    /**
102
     * Constructor of the class.
103
     *
104
     * @param int $courseId
105
     *
106
     * @author Olivier Brouckaert
107
     */
108
    public function __construct($courseId = 0)
109
    {
110
        $this->iId = 0;
111
        $this->id = 0;
112
        $this->exercise = '';
113
        $this->description = '';
114
        $this->sound = '';
115
        $this->type = ALL_ON_ONE_PAGE;
116
        $this->random = 0;
117
        $this->random_answers = 0;
118
        $this->active = 1;
119
        $this->questionList = [];
120
        $this->timeLimit = 0;
121
        $this->end_time = '';
122
        $this->start_time = '';
123
        $this->results_disabled = 1;
124
        $this->expired_time = 0;
125
        $this->propagate_neg = 0;
126
        $this->saveCorrectAnswers = 0;
127
        $this->review_answers = false;
128
        $this->randomByCat = 0;
129
        $this->text_when_finished = '';
130
        $this->display_category_name = 0;
131
        $this->pass_percentage = 0;
132
        $this->modelType = 1;
133
        $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
134
        $this->endButton = 0;
135
        $this->scoreTypeModel = 0;
136
        $this->globalCategoryId = null;
137
        $this->notifications = [];
138
        $this->exerciseCategoryId = null;
139
        $this->pageResultConfiguration;
140
        $this->preventBackwards = 0;
141
        $this->hideComment = false;
142
        $this->hideNoAnswer = false;
143
        $this->hideExpectedAnswer = false;
144
        $this->disableHideCorrectAnsweredQuestions = false;
145
146
        if (!empty($courseId)) {
147
            $courseInfo = api_get_course_info_by_id($courseId);
148
        } else {
149
            $courseInfo = api_get_course_info();
150
        }
151
        $this->course_id = $courseInfo['real_id'];
152
        $this->course = $courseInfo;
153
        $this->sessionId = api_get_session_id();
154
155
        // ALTER TABLE c_quiz_question ADD COLUMN feedback text;
156
        $this->questionFeedbackEnabled = api_get_configuration_value('allow_quiz_question_feedback');
157
        $this->showPreviousButton = true;
158
    }
159
160
    /**
161
     * Reads exercise information from the data base.
162
     *
163
     * @author Olivier Brouckaert
164
     *
165
     * @param int  $id                - exercise Id
166
     * @param bool $parseQuestionList
167
     *
168
     * @return bool - true if exercise exists, otherwise false
169
     */
170
    public function read($id, $parseQuestionList = true)
171
    {
172
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
173
174
        $id = (int) $id;
175
        if (empty($this->course_id)) {
176
            return false;
177
        }
178
179
        $sql = "SELECT * FROM $table
180
                WHERE c_id = ".$this->course_id." AND id = ".$id;
181
        $result = Database::query($sql);
182
183
        // if the exercise has been found
184
        if ($object = Database::fetch_object($result)) {
185
            $this->iId = $object->iid;
186
            $this->id = $id;
187
            $this->exercise = $object->title;
188
            $this->name = $object->title;
189
            $this->title = $object->title;
190
            $this->description = $object->description;
191
            $this->sound = $object->sound;
192
            $this->type = $object->type;
193
            if (empty($this->type)) {
194
                $this->type = ONE_PER_PAGE;
195
            }
196
            $this->random = $object->random;
197
            $this->random_answers = $object->random_answers;
198
            $this->active = $object->active;
199
            $this->results_disabled = $object->results_disabled;
200
            $this->attempts = $object->max_attempt;
201
            $this->feedback_type = $object->feedback_type;
202
            $this->sessionId = $object->session_id;
203
            $this->propagate_neg = $object->propagate_neg;
204
            $this->saveCorrectAnswers = $object->save_correct_answers;
205
            $this->randomByCat = $object->random_by_category;
206
            $this->text_when_finished = $object->text_when_finished;
207
            $this->display_category_name = $object->display_category_name;
208
            $this->pass_percentage = $object->pass_percentage;
209
            $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
210
            $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
211
            $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
212
            $this->questionSelectionType = isset($object->question_selection_type) ? (int) $object->question_selection_type : null;
213
            $this->hideQuestionTitle = isset($object->hide_question_title) ? (int) $object->hide_question_title : 0;
214
            $this->autolaunch = isset($object->autolaunch) ? (int) $object->autolaunch : 0;
215
            $this->exerciseCategoryId = isset($object->exercise_category_id) ? (int) $object->exercise_category_id : null;
216
            $this->preventBackwards = isset($object->prevent_backwards) ? (int) $object->prevent_backwards : 0;
217
            $this->exercise_was_added_in_lp = false;
218
            $this->lpList = [];
219
            $this->notifications = [];
220
            if (!empty($object->notifications)) {
221
                $this->notifications = explode(',', $object->notifications);
222
            }
223
224
            if (!empty($object->page_result_configuration)) {
225
                $this->pageResultConfiguration = $object->page_result_configuration;
226
            }
227
228
            if (isset($object->show_previous_button)) {
229
                $this->showPreviousButton = $object->show_previous_button == 1 ? true : false;
230
            }
231
232
            $list = self::getLpListFromExercise($id, $this->course_id);
233
            if (!empty($list)) {
234
                $this->exercise_was_added_in_lp = true;
235
                $this->lpList = $list;
236
            }
237
238
            $this->force_edit_exercise_in_lp = api_get_configuration_value('force_edit_exercise_in_lp');
239
            $this->edit_exercise_in_lp = true;
240
            if ($this->exercise_was_added_in_lp) {
241
                $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
242
            }
243
244
            if (!empty($object->end_time)) {
245
                $this->end_time = $object->end_time;
246
            }
247
            if (!empty($object->start_time)) {
248
                $this->start_time = $object->start_time;
249
            }
250
251
            // Control time
252
            $this->expired_time = $object->expired_time;
253
254
            // Checking if question_order is correctly set
255
            if ($parseQuestionList) {
256
                $this->setQuestionList(true);
257
            }
258
259
            //overload questions list with recorded questions list
260
            //load questions only for exercises of type 'one question per page'
261
            //this is needed only is there is no questions
262
263
            // @todo not sure were in the code this is used somebody mess with the exercise tool
264
            // @todo don't know who add that config and why $_configuration['live_exercise_tracking']
265
            /*global $_configuration, $questionList;
266
            if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST'
267
                && defined('QUESTION_LIST_ALREADY_LOGGED') &&
268
                isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']
269
            ) {
270
                $this->questionList = $questionList;
271
            }*/
272
            return true;
273
        }
274
275
        return false;
276
    }
277
278
    /**
279
     * @return string
280
     */
281
    public function getCutTitle()
282
    {
283
        $title = $this->getUnformattedTitle();
284
285
        return cut($title, EXERCISE_MAX_NAME_SIZE);
286
    }
287
288
    /**
289
     * returns the exercise ID.
290
     *
291
     * @author Olivier Brouckaert
292
     *
293
     * @return int - exercise ID
294
     */
295
    public function selectId()
296
    {
297
        return $this->id;
298
    }
299
300
    /**
301
     * returns the exercise title.
302
     *
303
     * @author Olivier Brouckaert
304
     *
305
     * @param bool $unformattedText Optional. Get the title without HTML tags
306
     *
307
     * @return string - exercise title
308
     */
309
    public function selectTitle($unformattedText = false)
310
    {
311
        if ($unformattedText) {
312
            return $this->getUnformattedTitle();
313
        }
314
315
        return $this->exercise;
316
    }
317
318
    /**
319
     * returns the number of attempts setted.
320
     *
321
     * @return int - exercise attempts
322
     */
323
    public function selectAttempts()
324
    {
325
        return $this->attempts;
326
    }
327
328
    /**
329
     * Returns the number of FeedbackType
330
     *  0: Feedback , 1: DirectFeedback, 2: NoFeedback.
331
     *
332
     * @return int - exercise attempts
333
     */
334
    public function getFeedbackType()
335
    {
336
        return (int) $this->feedback_type;
337
    }
338
339
    /**
340
     * returns the time limit.
341
     *
342
     * @return int
343
     */
344
    public function selectTimeLimit()
345
    {
346
        return $this->timeLimit;
347
    }
348
349
    /**
350
     * returns the exercise description.
351
     *
352
     * @author Olivier Brouckaert
353
     *
354
     * @return string - exercise description
355
     */
356
    public function selectDescription()
357
    {
358
        return $this->description;
359
    }
360
361
    /**
362
     * returns the exercise sound file.
363
     *
364
     * @author Olivier Brouckaert
365
     *
366
     * @return string - exercise description
367
     */
368
    public function selectSound()
369
    {
370
        return $this->sound;
371
    }
372
373
    /**
374
     * returns the exercise type.
375
     *
376
     * @author Olivier Brouckaert
377
     *
378
     * @return int - exercise type
379
     */
380
    public function selectType()
381
    {
382
        return $this->type;
383
    }
384
385
    /**
386
     * @return int
387
     */
388
    public function getModelType()
389
    {
390
        return $this->modelType;
391
    }
392
393
    /**
394
     * @return int
395
     */
396
    public function selectEndButton()
397
    {
398
        return $this->endButton;
399
    }
400
401
    /**
402
     * @author hubert borderiou 30-11-11
403
     *
404
     * @return int : do we display the question category name for students
405
     */
406
    public function selectDisplayCategoryName()
407
    {
408
        return $this->display_category_name;
409
    }
410
411
    /**
412
     * @return int
413
     */
414
    public function selectPassPercentage()
415
    {
416
        return $this->pass_percentage;
417
    }
418
419
    /**
420
     * Modify object to update the switch display_category_name.
421
     *
422
     * @author hubert borderiou 30-11-11
423
     *
424
     * @param int $value is an integer 0 or 1
425
     */
426
    public function updateDisplayCategoryName($value)
427
    {
428
        $this->display_category_name = $value;
429
    }
430
431
    /**
432
     * @author hubert borderiou 28-11-11
433
     *
434
     * @return string html text : the text to display ay the end of the test
435
     */
436
    public function getTextWhenFinished()
437
    {
438
        return $this->text_when_finished;
439
    }
440
441
    /**
442
     * @param string $text
443
     *
444
     * @author hubert borderiou 28-11-11
445
     */
446
    public function updateTextWhenFinished($text)
447
    {
448
        $this->text_when_finished = $text;
449
    }
450
451
    /**
452
     * return 1 or 2 if randomByCat.
453
     *
454
     * @author hubert borderiou
455
     *
456
     * @return int - quiz random by category
457
     */
458
    public function getRandomByCategory()
459
    {
460
        return $this->randomByCat;
461
    }
462
463
    /**
464
     * return 0 if no random by cat
465
     * return 1 if random by cat, categories shuffled
466
     * return 2 if random by cat, categories sorted by alphabetic order.
467
     *
468
     * @author hubert borderiou
469
     *
470
     * @return int - quiz random by category
471
     */
472
    public function isRandomByCat()
473
    {
474
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
475
        if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
476
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
477
        } elseif ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
478
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
479
        }
480
481
        return $res;
482
    }
483
484
    /**
485
     * return nothing
486
     * update randomByCat value for object.
487
     *
488
     * @param int $random
489
     *
490
     * @author hubert borderiou
491
     */
492
    public function updateRandomByCat($random)
493
    {
494
        $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
495
        if (in_array(
496
            $random,
497
            [
498
                EXERCISE_CATEGORY_RANDOM_SHUFFLED,
499
                EXERCISE_CATEGORY_RANDOM_ORDERED,
500
                EXERCISE_CATEGORY_RANDOM_DISABLED,
501
            ]
502
        )) {
503
            $this->randomByCat = $random;
504
        }
505
    }
506
507
    /**
508
     * Tells if questions are selected randomly, and if so returns the draws.
509
     *
510
     * @author Carlos Vargas
511
     *
512
     * @return int - results disabled exercise
513
     */
514
    public function selectResultsDisabled()
515
    {
516
        return $this->results_disabled;
517
    }
518
519
    /**
520
     * tells if questions are selected randomly, and if so returns the draws.
521
     *
522
     * @author Olivier Brouckaert
523
     *
524
     * @return bool
525
     */
526
    public function isRandom()
527
    {
528
        $isRandom = false;
529
        // "-1" means all questions will be random
530
        if ($this->random > 0 || $this->random == -1) {
531
            $isRandom = true;
532
        }
533
534
        return $isRandom;
535
    }
536
537
    /**
538
     * returns random answers status.
539
     *
540
     * @author Juan Carlos Rana
541
     */
542
    public function getRandomAnswers()
543
    {
544
        return $this->random_answers;
545
    }
546
547
    /**
548
     * Same as isRandom() but has a name applied to values different than 0 or 1.
549
     *
550
     * @return int
551
     */
552
    public function getShuffle()
553
    {
554
        return $this->random;
555
    }
556
557
    /**
558
     * returns the exercise status (1 = enabled ; 0 = disabled).
559
     *
560
     * @author Olivier Brouckaert
561
     *
562
     * @return int - 1 if enabled, otherwise 0
563
     */
564
    public function selectStatus()
565
    {
566
        return $this->active;
567
    }
568
569
    /**
570
     * If false the question list will be managed as always if true
571
     * the question will be filtered
572
     * depending of the exercise settings (table c_quiz_rel_category).
573
     *
574
     * @param bool $status active or inactive grouping
575
     */
576
    public function setCategoriesGrouping($status)
577
    {
578
        $this->categories_grouping = (bool) $status;
579
    }
580
581
    /**
582
     * @return int
583
     */
584
    public function getHideQuestionTitle()
585
    {
586
        return $this->hideQuestionTitle;
587
    }
588
589
    /**
590
     * @param $value
591
     */
592
    public function setHideQuestionTitle($value)
593
    {
594
        $this->hideQuestionTitle = (int) $value;
595
    }
596
597
    /**
598
     * @return int
599
     */
600
    public function getScoreTypeModel()
601
    {
602
        return $this->scoreTypeModel;
603
    }
604
605
    /**
606
     * @param int $value
607
     */
608
    public function setScoreTypeModel($value)
609
    {
610
        $this->scoreTypeModel = (int) $value;
611
    }
612
613
    /**
614
     * @return int
615
     */
616
    public function getGlobalCategoryId()
617
    {
618
        return $this->globalCategoryId;
619
    }
620
621
    /**
622
     * @param int $value
623
     */
624
    public function setGlobalCategoryId($value)
625
    {
626
        if (is_array($value) && isset($value[0])) {
627
            $value = $value[0];
628
        }
629
        $this->globalCategoryId = (int) $value;
630
    }
631
632
    /**
633
     * @param int    $start
634
     * @param int    $limit
635
     * @param int    $sidx
636
     * @param string $sord
637
     * @param array  $whereCondition
638
     * @param array  $extraFields
639
     *
640
     * @return array
641
     */
642
    public function getQuestionListPagination(
643
        $start,
644
        $limit,
645
        $sidx,
646
        $sord,
647
        $whereCondition = [],
648
        $extraFields = []
649
    ) {
650
        if (!empty($this->id)) {
651
            $category_list = TestCategory::getListOfCategoriesNameForTest(
652
                $this->id,
653
                false
654
            );
655
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
656
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
657
658
            $sql = "SELECT q.iid
659
                    FROM $TBL_EXERCICE_QUESTION e
660
                    INNER JOIN $TBL_QUESTIONS  q
661
                    ON (e.question_id = q.id AND e.c_id = ".$this->course_id." )
662
					WHERE e.exercice_id	= '".$this->id."' ";
663
664
            $orderCondition = ' ORDER BY question_order ';
665
666
            if (!empty($sidx) && !empty($sord)) {
667
                if ('question' === $sidx) {
668
                    if (in_array(strtolower($sord), ['desc', 'asc'])) {
669
                        $orderCondition = " ORDER BY `q.$sidx` $sord";
670
                    }
671
                }
672
            }
673
674
            $sql .= $orderCondition;
675
            $limitCondition = null;
676
            if (isset($start) && isset($limit)) {
677
                $start = (int) $start;
678
                $limit = (int) $limit;
679
                $limitCondition = " LIMIT $start, $limit";
680
            }
681
            $sql .= $limitCondition;
682
            $result = Database::query($sql);
683
            $questions = [];
684
            if (Database::num_rows($result)) {
685
                if (!empty($extraFields)) {
686
                    $extraFieldValue = new ExtraFieldValue('question');
687
                }
688
                while ($question = Database::fetch_array($result, 'ASSOC')) {
689
                    /** @var Question $objQuestionTmp */
690
                    $objQuestionTmp = Question::read($question['iid']);
691
                    $category_labels = TestCategory::return_category_labels(
692
                        $objQuestionTmp->category_list,
693
                        $category_list
694
                    );
695
696
                    if (empty($category_labels)) {
697
                        $category_labels = '-';
698
                    }
699
700
                    // Question type
701
                    $typeImg = $objQuestionTmp->getTypePicture();
702
                    $typeExpl = $objQuestionTmp->getExplanation();
703
704
                    $question_media = null;
705
                    if (!empty($objQuestionTmp->parent_id)) {
706
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
707
                        $question_media = Question::getMediaLabel($objQuestionMedia->question);
708
                    }
709
710
                    $questionType = Display::tag(
711
                        'div',
712
                        Display::return_icon($typeImg, $typeExpl, [], ICON_SIZE_MEDIUM).$question_media
713
                    );
714
715
                    $question = [
716
                        'id' => $question['iid'],
717
                        'question' => $objQuestionTmp->selectTitle(),
718
                        'type' => $questionType,
719
                        'category' => Display::tag(
720
                            'div',
721
                            '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'
722
                        ),
723
                        'score' => $objQuestionTmp->selectWeighting(),
724
                        'level' => $objQuestionTmp->level,
725
                    ];
726
727
                    if (!empty($extraFields)) {
728
                        foreach ($extraFields as $extraField) {
729
                            $value = $extraFieldValue->get_values_by_handler_and_field_id(
730
                                $question['id'],
731
                                $extraField['id']
732
                            );
733
                            $stringValue = null;
734
                            if ($value) {
735
                                $stringValue = $value['field_value'];
736
                            }
737
                            $question[$extraField['field_variable']] = $stringValue;
738
                        }
739
                    }
740
                    $questions[] = $question;
741
                }
742
            }
743
744
            return $questions;
745
        }
746
    }
747
748
    /**
749
     * Get question count per exercise from DB (any special treatment).
750
     *
751
     * @return int
752
     */
753
    public function getQuestionCount()
754
    {
755
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
756
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
757
        $sql = "SELECT count(q.id) as count
758
                FROM $TBL_EXERCICE_QUESTION e
759
                INNER JOIN $TBL_QUESTIONS q
760
                ON (e.question_id = q.id AND e.c_id = q.c_id)
761
                WHERE
762
                    e.c_id = {$this->course_id} AND
763
                    e.exercice_id = ".$this->id;
764
        $result = Database::query($sql);
765
766
        $count = 0;
767
        if (Database::num_rows($result)) {
768
            $row = Database::fetch_array($result);
769
            $count = (int) $row['count'];
770
        }
771
772
        return $count;
773
    }
774
775
    /**
776
     * @return array
777
     */
778
    public function getQuestionOrderedListByName()
779
    {
780
        if (empty($this->course_id) || empty($this->id)) {
781
            return [];
782
        }
783
784
        $exerciseQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
785
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
786
787
        // Getting question list from the order (question list drag n drop interface ).
788
        $sql = "SELECT e.question_id
789
                FROM $exerciseQuestionTable e
790
                INNER JOIN $questionTable q
791
                ON (e.question_id= q.id AND e.c_id = q.c_id)
792
                WHERE
793
                    e.c_id = {$this->course_id} AND
794
                    e.exercice_id = '".$this->id."'
795
                ORDER BY q.question";
796
        $result = Database::query($sql);
797
        $list = [];
798
        if (Database::num_rows($result)) {
799
            $list = Database::store_result($result, 'ASSOC');
800
        }
801
802
        return $list;
803
    }
804
805
    /**
806
     * Selecting question list depending in the exercise-category
807
     * relationship (category table in exercise settings).
808
     *
809
     * @param array $question_list
810
     * @param int   $questionSelectionType
811
     *
812
     * @return array
813
     */
814
    public function getQuestionListWithCategoryListFilteredByCategorySettings(
815
        $question_list,
816
        $questionSelectionType
817
    ) {
818
        $result = [
819
            'question_list' => [],
820
            'category_with_questions_list' => [],
821
        ];
822
823
        // Order/random categories
824
        $cat = new TestCategory();
825
        // Setting category order.
826
        switch ($questionSelectionType) {
827
            case EX_Q_SELECTION_ORDERED: // 1
828
            case EX_Q_SELECTION_RANDOM:  // 2
829
                // This options are not allowed here.
830
                break;
831
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
832
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
833
                    $this,
834
                    $this->course['real_id'],
835
                    'title ASC',
836
                    false,
837
                    true
838
                );
839
840
                $questions_by_category = TestCategory::getQuestionsByCat(
841
                    $this->id,
842
                    $question_list,
843
                    $categoriesAddedInExercise
844
                );
845
846
                $question_list = $this->pickQuestionsPerCategory(
847
                    $categoriesAddedInExercise,
848
                    $question_list,
849
                    $questions_by_category,
850
                    true,
851
                    false
852
                );
853
                break;
854
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
855
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
856
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
857
                    $this,
858
                    $this->course['real_id'],
859
                    null,
860
                    true,
861
                    true
862
                );
863
                $questions_by_category = TestCategory::getQuestionsByCat(
864
                    $this->id,
865
                    $question_list,
866
                    $categoriesAddedInExercise
867
                );
868
                $question_list = $this->pickQuestionsPerCategory(
869
                    $categoriesAddedInExercise,
870
                    $question_list,
871
                    $questions_by_category,
872
                    true,
873
                    false
874
                );
875
                break;
876
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
877
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
878
                    $this,
879
                    $this->course['real_id'],
880
                    'title ASC',
881
                    false,
882
                    true
883
                );
884
                $questions_by_category = TestCategory::getQuestionsByCat(
885
                    $this->id,
886
                    $question_list,
887
                    $categoriesAddedInExercise
888
                );
889
890
                $questionsByCategoryMandatory = [];
891
                if (EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM == $this->getQuestionSelectionType() &&
892
                    api_get_configuration_value('allow_mandatory_question_in_category')
893
                ) {
894
                    $questionsByCategoryMandatory = TestCategory::getQuestionsByCat(
895
                        $this->id,
896
                        $question_list,
897
                        $categoriesAddedInExercise,
898
                        true
899
                    );
900
                }
901
902
                $question_list = $this->pickQuestionsPerCategory(
903
                    $categoriesAddedInExercise,
904
                    $question_list,
905
                    $questions_by_category,
906
                    true,
907
                    true,
908
                    $questionsByCategoryMandatory
909
                );
910
                break;
911
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
912
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
913
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
914
                    $this,
915
                    $this->course['real_id'],
916
                    null,
917
                    true,
918
                    true
919
                );
920
921
                $questions_by_category = TestCategory::getQuestionsByCat(
922
                    $this->id,
923
                    $question_list,
924
                    $categoriesAddedInExercise
925
                );
926
927
                $question_list = $this->pickQuestionsPerCategory(
928
                    $categoriesAddedInExercise,
929
                    $question_list,
930
                    $questions_by_category,
931
                    true,
932
                    true
933
                );
934
                break;
935
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
936
                break;
937
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
938
                break;
939
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
940
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
941
                    $this,
942
                    $this->course['real_id'],
943
                    'root ASC, lft ASC',
944
                    false,
945
                    true
946
                );
947
                $questions_by_category = TestCategory::getQuestionsByCat(
948
                    $this->id,
949
                    $question_list,
950
                    $categoriesAddedInExercise
951
                );
952
                $question_list = $this->pickQuestionsPerCategory(
953
                    $categoriesAddedInExercise,
954
                    $question_list,
955
                    $questions_by_category,
956
                    true,
957
                    false
958
                );
959
                break;
960
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
961
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
962
                    $this,
963
                    $this->course['real_id'],
964
                    'root, lft ASC',
965
                    false,
966
                    true
967
                );
968
                $questions_by_category = TestCategory::getQuestionsByCat(
969
                    $this->id,
970
                    $question_list,
971
                    $categoriesAddedInExercise
972
                );
973
                $question_list = $this->pickQuestionsPerCategory(
974
                    $categoriesAddedInExercise,
975
                    $question_list,
976
                    $questions_by_category,
977
                    true,
978
                    true
979
                );
980
                break;
981
        }
982
983
        $result['question_list'] = isset($question_list) ? $question_list : [];
984
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : [];
985
        $parentsLoaded = [];
986
        // Adding category info in the category list with question list:
987
        if (!empty($questions_by_category)) {
988
            $newCategoryList = [];
989
            $em = Database::getManager();
990
            $repo = $em->getRepository('ChamiloCourseBundle:CQuizCategory');
991
992
            foreach ($questions_by_category as $categoryId => $questionList) {
993
                $cat = new TestCategory();
994
                $cat = $cat->getCategory($categoryId);
995
                if ($cat) {
996
                    $cat = (array) $cat;
997
                    $cat['iid'] = $cat['id'];
998
                }
999
1000
                $categoryParentInfo = null;
1001
                // Parent is not set no loop here
1002
                if (isset($cat['parent_id']) && !empty($cat['parent_id'])) {
1003
                    /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryEntity */
1004
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
1005
                        $categoryEntity = $em->find('ChamiloCourseBundle:CQuizCategory', $cat['parent_id']);
1006
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
1007
                    } else {
1008
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
1009
                    }
1010
                    $path = $repo->getPath($categoryEntity);
1011
1012
                    $index = 0;
1013
                    if ($this->categoryMinusOne) {
1014
                        //$index = 1;
1015
                    }
1016
1017
                    /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryParent */
1018
                    foreach ($path as $categoryParent) {
1019
                        $visibility = $categoryParent->getVisibility();
1020
                        if (0 == $visibility) {
1021
                            $categoryParentId = $categoryId;
1022
                            $categoryTitle = $cat['title'];
1023
                            if (count($path) > 1) {
1024
                                continue;
1025
                            }
1026
                        } else {
1027
                            $categoryParentId = $categoryParent->getIid();
1028
                            $categoryTitle = $categoryParent->getTitle();
1029
                        }
1030
1031
                        $categoryParentInfo['id'] = $categoryParentId;
1032
                        $categoryParentInfo['iid'] = $categoryParentId;
1033
                        $categoryParentInfo['parent_path'] = null;
1034
                        $categoryParentInfo['title'] = $categoryTitle;
1035
                        $categoryParentInfo['name'] = $categoryTitle;
1036
                        $categoryParentInfo['parent_id'] = null;
1037
                        break;
1038
                    }
1039
                }
1040
                $cat['parent_info'] = $categoryParentInfo;
1041
                $newCategoryList[$categoryId] = [
1042
                    'category' => $cat,
1043
                    'question_list' => $questionList,
1044
                ];
1045
            }
1046
1047
            $result['category_with_questions_list'] = $newCategoryList;
1048
        }
1049
1050
        return $result;
1051
    }
1052
1053
    /**
1054
     * returns the array with the question ID list.
1055
     *
1056
     * @param bool $fromDatabase Whether the results should be fetched in the database or just from memory
1057
     * @param bool $adminView    Whether we should return all questions (admin view) or
1058
     *                           just a list limited by the max number of random questions
1059
     *
1060
     * @author Olivier Brouckaert
1061
     *
1062
     * @return array - question ID list
1063
     */
1064
    public function selectQuestionList($fromDatabase = false, $adminView = false)
1065
    {
1066
        if ($fromDatabase && !empty($this->id)) {
1067
            $nbQuestions = $this->getQuestionCount();
1068
            $questionSelectionType = $this->getQuestionSelectionType();
1069
1070
            switch ($questionSelectionType) {
1071
                case EX_Q_SELECTION_ORDERED:
1072
                    $questionList = $this->getQuestionOrderedList($adminView);
1073
                    break;
1074
                case EX_Q_SELECTION_RANDOM:
1075
                    // Not a random exercise, or if there are not at least 2 questions
1076
                    if ($this->random == 0 || $nbQuestions < 2) {
1077
                        $questionList = $this->getQuestionOrderedList($adminView);
1078
                    } else {
1079
                        $questionList = $this->getRandomList($adminView);
1080
                    }
1081
                    break;
1082
                default:
1083
                    $questionList = $this->getQuestionOrderedList($adminView);
1084
                    $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1085
                        $questionList,
1086
                        $questionSelectionType
1087
                    );
1088
                    $this->categoryWithQuestionList = $result['category_with_questions_list'];
1089
                    $questionList = $result['question_list'];
1090
                    break;
1091
            }
1092
1093
            return $questionList;
1094
        }
1095
1096
        return $this->questionList;
1097
    }
1098
1099
    /**
1100
     * returns the number of questions in this exercise.
1101
     *
1102
     * @author Olivier Brouckaert
1103
     *
1104
     * @return int - number of questions
1105
     */
1106
    public function selectNbrQuestions()
1107
    {
1108
        return count($this->questionList);
1109
    }
1110
1111
    /**
1112
     * @return int
1113
     */
1114
    public function selectPropagateNeg()
1115
    {
1116
        return $this->propagate_neg;
1117
    }
1118
1119
    /**
1120
     * @return int
1121
     */
1122
    public function getSaveCorrectAnswers()
1123
    {
1124
        return $this->saveCorrectAnswers;
1125
    }
1126
1127
    /**
1128
     * Selects questions randomly in the question list.
1129
     *
1130
     * @author Olivier Brouckaert
1131
     * @author Hubert Borderiou 15 nov 2011
1132
     *
1133
     * @param bool $adminView Whether we should return all
1134
     *                        questions (admin view) or just a list limited by the max number of random questions
1135
     *
1136
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1137
     *               without randomizing, otherwise, returns the list with questions selected randomly
1138
     */
1139
    public function getRandomList($adminView = false)
1140
    {
1141
        $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1142
        $question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1143
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1144
1145
        // Random with limit
1146
        $randomLimit = " ORDER BY RAND() LIMIT $random";
1147
1148
        // Random with no limit
1149
        if (-1 == $random) {
1150
            $randomLimit = ' ORDER BY RAND() ';
1151
        }
1152
1153
        // Admin see the list in default order
1154
        if (true === $adminView) {
1155
            // If viewing it as admin for edition, don't show it randomly, use title + id
1156
            $randomLimit = 'ORDER BY e.question_order';
1157
        }
1158
1159
        $sql = "SELECT e.question_id
1160
                FROM $quizRelQuestion e
1161
                INNER JOIN $question q
1162
                ON (e.question_id= q.id AND e.c_id = q.c_id)
1163
                WHERE
1164
                    e.c_id = {$this->course_id} AND
1165
                    e.exercice_id = '".Database::escape_string($this->id)."'
1166
                    $randomLimit ";
1167
        $result = Database::query($sql);
1168
        $questionList = [];
1169
        while ($row = Database::fetch_object($result)) {
1170
            $questionList[] = $row->question_id;
1171
        }
1172
1173
        return $questionList;
1174
    }
1175
1176
    /**
1177
     * returns 'true' if the question ID is in the question list.
1178
     *
1179
     * @author Olivier Brouckaert
1180
     *
1181
     * @param int $questionId - question ID
1182
     *
1183
     * @return bool - true if in the list, otherwise false
1184
     */
1185
    public function isInList($questionId)
1186
    {
1187
        $inList = false;
1188
        if (is_array($this->questionList)) {
1189
            $inList = in_array($questionId, $this->questionList);
1190
        }
1191
1192
        return $inList;
1193
    }
1194
1195
    /**
1196
     * If current exercise has a question.
1197
     *
1198
     * @param int $questionId
1199
     *
1200
     * @return int
1201
     */
1202
    public function hasQuestion($questionId)
1203
    {
1204
        $questionId = (int) $questionId;
1205
1206
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1207
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1208
        $sql = "SELECT q.id
1209
                FROM $TBL_EXERCICE_QUESTION e
1210
                INNER JOIN $TBL_QUESTIONS q
1211
                ON (e.question_id = q.id AND e.c_id = q.c_id)
1212
                WHERE
1213
                    q.id = $questionId AND
1214
                    e.c_id = {$this->course_id} AND
1215
                    e.exercice_id = ".$this->id;
1216
1217
        $result = Database::query($sql);
1218
1219
        return Database::num_rows($result) > 0;
1220
    }
1221
1222
    public function hasQuestionWithType($type)
1223
    {
1224
        $type = (int) $type;
1225
1226
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1227
        $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
1228
        $sql = "SELECT q.id
1229
                FROM $table e
1230
                INNER JOIN $tableQuestion q
1231
                ON (e.question_id = q.id AND e.c_id = q.c_id)
1232
                WHERE
1233
                    q.type = $type AND
1234
                    e.c_id = {$this->course_id} AND
1235
                    e.exercice_id = ".$this->id;
1236
1237
        $result = Database::query($sql);
1238
1239
        return Database::num_rows($result) > 0;
1240
    }
1241
1242
    public function hasQuestionWithTypeNotInList(array $questionTypeList)
1243
    {
1244
        if (empty($questionTypeList)) {
1245
            return false;
1246
        }
1247
1248
        $questionTypeToString = implode("','", array_map('intval', $questionTypeList));
1249
1250
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1251
        $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
1252
        $sql = "SELECT q.id
1253
                FROM $table e
1254
                INNER JOIN $tableQuestion q
1255
                ON (e.question_id = q.id AND e.c_id = q.c_id)
1256
                WHERE
1257
                    q.type NOT IN ('$questionTypeToString')  AND
1258
                    e.c_id = {$this->course_id} AND
1259
                    e.exercice_id = ".$this->id;
1260
1261
        $result = Database::query($sql);
1262
1263
        return Database::num_rows($result) > 0;
1264
    }
1265
1266
    /**
1267
     * changes the exercise title.
1268
     *
1269
     * @author Olivier Brouckaert
1270
     *
1271
     * @param string $title - exercise title
1272
     */
1273
    public function updateTitle($title)
1274
    {
1275
        $this->title = $this->exercise = $title;
1276
    }
1277
1278
    /**
1279
     * changes the exercise max attempts.
1280
     *
1281
     * @param int $attempts - exercise max attempts
1282
     */
1283
    public function updateAttempts($attempts)
1284
    {
1285
        $this->attempts = $attempts;
1286
    }
1287
1288
    /**
1289
     * changes the exercise feedback type.
1290
     *
1291
     * @param int $feedback_type
1292
     */
1293
    public function updateFeedbackType($feedback_type)
1294
    {
1295
        $this->feedback_type = $feedback_type;
1296
    }
1297
1298
    /**
1299
     * changes the exercise description.
1300
     *
1301
     * @author Olivier Brouckaert
1302
     *
1303
     * @param string $description - exercise description
1304
     */
1305
    public function updateDescription($description)
1306
    {
1307
        $this->description = $description;
1308
    }
1309
1310
    /**
1311
     * changes the exercise expired_time.
1312
     *
1313
     * @author Isaac flores
1314
     *
1315
     * @param int $expired_time The expired time of the quiz
1316
     */
1317
    public function updateExpiredTime($expired_time)
1318
    {
1319
        $this->expired_time = $expired_time;
1320
    }
1321
1322
    /**
1323
     * @param $value
1324
     */
1325
    public function updatePropagateNegative($value)
1326
    {
1327
        $this->propagate_neg = $value;
1328
    }
1329
1330
    /**
1331
     * @param int $value
1332
     */
1333
    public function updateSaveCorrectAnswers($value)
1334
    {
1335
        $this->saveCorrectAnswers = (int) $value;
1336
    }
1337
1338
    /**
1339
     * @param $value
1340
     */
1341
    public function updateReviewAnswers($value)
1342
    {
1343
        $this->review_answers = isset($value) && $value ? true : false;
1344
    }
1345
1346
    /**
1347
     * @param $value
1348
     */
1349
    public function updatePassPercentage($value)
1350
    {
1351
        $this->pass_percentage = $value;
1352
    }
1353
1354
    /**
1355
     * @param string $text
1356
     */
1357
    public function updateEmailNotificationTemplate($text)
1358
    {
1359
        $this->emailNotificationTemplate = $text;
1360
    }
1361
1362
    /**
1363
     * @param string $text
1364
     */
1365
    public function setEmailNotificationTemplateToUser($text)
1366
    {
1367
        $this->emailNotificationTemplateToUser = $text;
1368
    }
1369
1370
    /**
1371
     * @param string $value
1372
     */
1373
    public function setNotifyUserByEmail($value)
1374
    {
1375
        $this->notifyUserByEmail = $value;
1376
    }
1377
1378
    /**
1379
     * @param int $value
1380
     */
1381
    public function updateEndButton($value)
1382
    {
1383
        $this->endButton = (int) $value;
1384
    }
1385
1386
    /**
1387
     * @param string $value
1388
     */
1389
    public function setOnSuccessMessage($value)
1390
    {
1391
        $this->onSuccessMessage = $value;
1392
    }
1393
1394
    /**
1395
     * @param string $value
1396
     */
1397
    public function setOnFailedMessage($value)
1398
    {
1399
        $this->onFailedMessage = $value;
1400
    }
1401
1402
    /**
1403
     * @param $value
1404
     */
1405
    public function setModelType($value)
1406
    {
1407
        $this->modelType = (int) $value;
1408
    }
1409
1410
    /**
1411
     * @param int $value
1412
     */
1413
    public function setQuestionSelectionType($value)
1414
    {
1415
        $this->questionSelectionType = (int) $value;
1416
    }
1417
1418
    /**
1419
     * @return int
1420
     */
1421
    public function getQuestionSelectionType()
1422
    {
1423
        return (int) $this->questionSelectionType;
1424
    }
1425
1426
    /**
1427
     * @param array $categories
1428
     */
1429
    public function updateCategories($categories)
1430
    {
1431
        if (!empty($categories)) {
1432
            $categories = array_map('intval', $categories);
1433
            $this->categories = $categories;
1434
        }
1435
    }
1436
1437
    /**
1438
     * changes the exercise sound file.
1439
     *
1440
     * @author Olivier Brouckaert
1441
     *
1442
     * @param string $sound  - exercise sound file
1443
     * @param string $delete - ask to delete the file
1444
     */
1445
    public function updateSound($sound, $delete)
1446
    {
1447
        global $audioPath, $documentPath;
1448
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1449
1450
        if ($sound['size'] && (strstr($sound['type'], 'audio') || strstr($sound['type'], 'video'))) {
1451
            $this->sound = $sound['name'];
1452
1453
            if (@move_uploaded_file($sound['tmp_name'], $audioPath.'/'.$this->sound)) {
1454
                $sql = "SELECT 1 FROM $TBL_DOCUMENT
1455
                        WHERE
1456
                            c_id = ".$this->course_id." AND
1457
                            path = '".str_replace($documentPath, '', $audioPath).'/'.$this->sound."'";
1458
                $result = Database::query($sql);
1459
1460
                if (!Database::num_rows($result)) {
1461
                    $id = add_document(
1462
                        $this->course,
1463
                        str_replace($documentPath, '', $audioPath).'/'.$this->sound,
1464
                        'file',
1465
                        $sound['size'],
1466
                        $sound['name']
1467
                    );
1468
                    api_item_property_update(
1469
                        $this->course,
1470
                        TOOL_DOCUMENT,
1471
                        $id,
1472
                        'DocumentAdded',
1473
                        api_get_user_id()
1474
                    );
1475
                    item_property_update_on_folder(
1476
                        $this->course,
1477
                        str_replace($documentPath, '', $audioPath),
1478
                        api_get_user_id()
1479
                    );
1480
                }
1481
            }
1482
        } elseif ($delete && is_file($audioPath.'/'.$this->sound)) {
1483
            $this->sound = '';
1484
        }
1485
    }
1486
1487
    /**
1488
     * changes the exercise type.
1489
     *
1490
     * @author Olivier Brouckaert
1491
     *
1492
     * @param int $type - exercise type
1493
     */
1494
    public function updateType($type)
1495
    {
1496
        $this->type = $type;
1497
    }
1498
1499
    /**
1500
     * sets to 0 if questions are not selected randomly
1501
     * if questions are selected randomly, sets the draws.
1502
     *
1503
     * @author Olivier Brouckaert
1504
     *
1505
     * @param int $random - 0 if not random, otherwise the draws
1506
     */
1507
    public function setRandom($random)
1508
    {
1509
        $this->random = $random;
1510
    }
1511
1512
    /**
1513
     * sets to 0 if answers are not selected randomly
1514
     * if answers are selected randomly.
1515
     *
1516
     * @author Juan Carlos Rana
1517
     *
1518
     * @param int $random_answers - random answers
1519
     */
1520
    public function updateRandomAnswers($random_answers)
1521
    {
1522
        $this->random_answers = $random_answers;
1523
    }
1524
1525
    /**
1526
     * enables the exercise.
1527
     *
1528
     * @author Olivier Brouckaert
1529
     */
1530
    public function enable()
1531
    {
1532
        $this->active = 1;
1533
    }
1534
1535
    /**
1536
     * disables the exercise.
1537
     *
1538
     * @author Olivier Brouckaert
1539
     */
1540
    public function disable()
1541
    {
1542
        $this->active = 0;
1543
    }
1544
1545
    /**
1546
     * Set disable results.
1547
     */
1548
    public function disable_results()
1549
    {
1550
        $this->results_disabled = true;
1551
    }
1552
1553
    /**
1554
     * Enable results.
1555
     */
1556
    public function enable_results()
1557
    {
1558
        $this->results_disabled = false;
1559
    }
1560
1561
    /**
1562
     * @param int $results_disabled
1563
     */
1564
    public function updateResultsDisabled($results_disabled)
1565
    {
1566
        $this->results_disabled = (int) $results_disabled;
1567
    }
1568
1569
    /**
1570
     * updates the exercise in the data base.
1571
     *
1572
     * @param string $type_e
1573
     *
1574
     * @author Olivier Brouckaert
1575
     */
1576
    public function save($type_e = '')
1577
    {
1578
        $_course = $this->course;
1579
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1580
1581
        $id = $this->id;
1582
        $exercise = $this->exercise;
1583
        $description = $this->description;
1584
        $sound = $this->sound;
1585
        $type = $this->type;
1586
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1587
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1588
        $random = $this->random;
1589
        $random_answers = $this->random_answers;
1590
        $active = $this->active;
1591
        $propagate_neg = (int) $this->propagate_neg;
1592
        $saveCorrectAnswers = isset($this->saveCorrectAnswers) ? (int) $this->saveCorrectAnswers : 0;
1593
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1594
        $randomByCat = (int) $this->randomByCat;
1595
        $text_when_finished = $this->text_when_finished;
1596
        $display_category_name = (int) $this->display_category_name;
1597
        $pass_percentage = (int) $this->pass_percentage;
1598
        $session_id = $this->sessionId;
1599
1600
        // If direct we do not show results
1601
        $results_disabled = (int) $this->results_disabled;
1602
        if (in_array($feedback_type, [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
1603
            $results_disabled = 0;
1604
        }
1605
        $expired_time = (int) $this->expired_time;
1606
1607
        // Exercise already exists
1608
        if ($id) {
1609
            // we prepare date in the database using the api_get_utc_datetime() function
1610
            $start_time = null;
1611
            if (!empty($this->start_time)) {
1612
                $start_time = $this->start_time;
1613
            }
1614
1615
            $end_time = null;
1616
            if (!empty($this->end_time)) {
1617
                $end_time = $this->end_time;
1618
            }
1619
1620
            $params = [
1621
                'title' => $exercise,
1622
                'description' => $description,
1623
            ];
1624
1625
            $paramsExtra = [];
1626
            if ($type_e != 'simple') {
1627
                $paramsExtra = [
1628
                    'sound' => $sound,
1629
                    'type' => $type,
1630
                    'random' => $random,
1631
                    'random_answers' => $random_answers,
1632
                    'active' => $active,
1633
                    'feedback_type' => $feedback_type,
1634
                    'start_time' => $start_time,
1635
                    'end_time' => $end_time,
1636
                    'max_attempt' => $attempts,
1637
                    'expired_time' => $expired_time,
1638
                    'propagate_neg' => $propagate_neg,
1639
                    'save_correct_answers' => $saveCorrectAnswers,
1640
                    'review_answers' => $review_answers,
1641
                    'random_by_category' => $randomByCat,
1642
                    'text_when_finished' => $text_when_finished,
1643
                    'display_category_name' => $display_category_name,
1644
                    'pass_percentage' => $pass_percentage,
1645
                    'results_disabled' => $results_disabled,
1646
                    'question_selection_type' => $this->getQuestionSelectionType(),
1647
                    'hide_question_title' => $this->getHideQuestionTitle(),
1648
                ];
1649
1650
                $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1651
                if ($allow === true) {
1652
                    $paramsExtra['show_previous_button'] = $this->showPreviousButton();
1653
                }
1654
1655
                if (api_get_configuration_value('quiz_prevent_backwards_move')) {
1656
                    $paramsExtra['prevent_backwards'] = $this->getPreventBackwards();
1657
                }
1658
1659
                $allow = api_get_configuration_value('allow_exercise_categories');
1660
                if ($allow === true) {
1661
                    if (!empty($this->getExerciseCategoryId())) {
1662
                        $paramsExtra['exercise_category_id'] = $this->getExerciseCategoryId();
1663
                    }
1664
                }
1665
1666
                $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1667
                if ($allow === true) {
1668
                    $notifications = $this->getNotifications();
1669
                    $notifications = implode(',', $notifications);
1670
                    $paramsExtra['notifications'] = $notifications;
1671
                }
1672
1673
                $pageConfig = api_get_configuration_value('allow_quiz_results_page_config');
1674
                if ($pageConfig && !empty($this->pageResultConfiguration)) {
1675
                    $paramsExtra['page_result_configuration'] = $this->pageResultConfiguration;
1676
                }
1677
            }
1678
1679
            $params = array_merge($params, $paramsExtra);
1680
1681
            Database::update(
1682
                $TBL_EXERCISES,
1683
                $params,
1684
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1685
            );
1686
1687
            // update into the item_property table
1688
            api_item_property_update(
1689
                $_course,
1690
                TOOL_QUIZ,
1691
                $id,
1692
                'QuizUpdated',
1693
                api_get_user_id()
1694
            );
1695
1696
            if (api_get_setting('search_enabled') === 'true') {
1697
                $this->search_engine_edit();
1698
            }
1699
        } else {
1700
            // Creates a new exercise
1701
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1702
            // for date because, bellow, we call function api_set_default_visibility()
1703
            // In this function, api_set_default_visibility,
1704
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1705
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1706
            $start_time = null;
1707
            if (!empty($this->start_time)) {
1708
                $start_time = $this->start_time;
1709
            }
1710
1711
            $end_time = null;
1712
            if (!empty($this->end_time)) {
1713
                $end_time = $this->end_time;
1714
            }
1715
1716
            $params = [
1717
                'c_id' => $this->course_id,
1718
                'start_time' => $start_time,
1719
                'end_time' => $end_time,
1720
                'title' => $exercise,
1721
                'description' => $description,
1722
                'sound' => $sound,
1723
                'type' => $type,
1724
                'random' => $random,
1725
                'random_answers' => $random_answers,
1726
                'active' => $active,
1727
                'results_disabled' => $results_disabled,
1728
                'max_attempt' => $attempts,
1729
                'feedback_type' => $feedback_type,
1730
                'expired_time' => $expired_time,
1731
                'session_id' => $session_id,
1732
                'review_answers' => $review_answers,
1733
                'random_by_category' => $randomByCat,
1734
                'text_when_finished' => $text_when_finished,
1735
                'display_category_name' => $display_category_name,
1736
                'pass_percentage' => $pass_percentage,
1737
                'save_correct_answers' => $saveCorrectAnswers,
1738
                'propagate_neg' => $propagate_neg,
1739
                'hide_question_title' => $this->getHideQuestionTitle(),
1740
            ];
1741
1742
            $allow = api_get_configuration_value('allow_exercise_categories');
1743
            if (true === $allow) {
1744
                if (!empty($this->getExerciseCategoryId())) {
1745
                    $params['exercise_category_id'] = $this->getExerciseCategoryId();
1746
                }
1747
            }
1748
1749
            if (api_get_configuration_value('quiz_prevent_backwards_move')) {
1750
                $params['prevent_backwards'] = $this->getPreventBackwards();
1751
            }
1752
1753
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
1754
            if (true === $allow) {
1755
                $params['show_previous_button'] = $this->showPreviousButton();
1756
            }
1757
1758
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
1759
            if (true === $allow) {
1760
                $notifications = $this->getNotifications();
1761
                $params['notifications'] = '';
1762
                if (!empty($notifications)) {
1763
                    $notifications = implode(',', $notifications);
1764
                    $params['notifications'] = $notifications;
1765
                }
1766
            }
1767
1768
            $pageConfig = api_get_configuration_value('allow_quiz_results_page_config');
1769
            if ($pageConfig && !empty($this->pageResultConfiguration)) {
1770
                $params['page_result_configuration'] = $this->pageResultConfiguration;
1771
            }
1772
1773
            $this->id = $this->iId = Database::insert($TBL_EXERCISES, $params);
1774
1775
            if ($this->id) {
1776
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1777
                Database::query($sql);
1778
1779
                $sql = "UPDATE $TBL_EXERCISES
1780
                        SET question_selection_type= ".$this->getQuestionSelectionType()."
1781
                        WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1782
                Database::query($sql);
1783
1784
                // insert into the item_property table
1785
                api_item_property_update(
1786
                    $this->course,
1787
                    TOOL_QUIZ,
1788
                    $this->id,
1789
                    'QuizAdded',
1790
                    api_get_user_id()
1791
                );
1792
1793
                // This function save the quiz again, carefull about start_time
1794
                // and end_time if you remove this line (see above)
1795
                api_set_default_visibility(
1796
                    $this->id,
1797
                    TOOL_QUIZ,
1798
                    null,
1799
                    $this->course
1800
                );
1801
1802
                if (api_get_setting('search_enabled') === 'true' && extension_loaded('xapian')) {
1803
                    $this->search_engine_save();
1804
                }
1805
            }
1806
        }
1807
1808
        $this->save_categories_in_exercise($this->categories);
1809
1810
        return $this->iId;
1811
    }
1812
1813
    /**
1814
     * Updates question position.
1815
     *
1816
     * @return bool
1817
     */
1818
    public function update_question_positions()
1819
    {
1820
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1821
        // Fixes #3483 when updating order
1822
        $questionList = $this->selectQuestionList(true);
1823
1824
        $this->id = (int) $this->id;
1825
1826
        if (empty($this->id)) {
1827
            return false;
1828
        }
1829
1830
        if (!empty($questionList)) {
1831
            foreach ($questionList as $position => $questionId) {
1832
                $position = (int) $position;
1833
                $questionId = (int) $questionId;
1834
                $sql = "UPDATE $table SET
1835
                            question_order ='".$position."'
1836
                        WHERE
1837
                            c_id = ".$this->course_id." AND
1838
                            question_id = ".$questionId." AND
1839
                            exercice_id=".$this->id;
1840
                Database::query($sql);
1841
            }
1842
        }
1843
1844
        return true;
1845
    }
1846
1847
    /**
1848
     * Adds a question into the question list.
1849
     *
1850
     * @author Olivier Brouckaert
1851
     *
1852
     * @param int $questionId - question ID
1853
     *
1854
     * @return bool - true if the question has been added, otherwise false
1855
     */
1856
    public function addToList($questionId)
1857
    {
1858
        // checks if the question ID is not in the list
1859
        if (!$this->isInList($questionId)) {
1860
            // selects the max position
1861
            if (!$this->selectNbrQuestions()) {
1862
                $pos = 1;
1863
            } else {
1864
                if (is_array($this->questionList)) {
1865
                    $pos = max(array_keys($this->questionList)) + 1;
1866
                }
1867
            }
1868
            $this->questionList[$pos] = $questionId;
1869
1870
            return true;
1871
        }
1872
1873
        return false;
1874
    }
1875
1876
    /**
1877
     * removes a question from the question list.
1878
     *
1879
     * @author Olivier Brouckaert
1880
     *
1881
     * @param int $questionId - question ID
1882
     *
1883
     * @return bool - true if the question has been removed, otherwise false
1884
     */
1885
    public function removeFromList($questionId)
1886
    {
1887
        // searches the position of the question ID in the list
1888
        $pos = array_search($questionId, $this->questionList);
1889
        // question not found
1890
        if (false === $pos) {
1891
            return false;
1892
        } else {
1893
            // dont reduce the number of random question if we use random by category option, or if
1894
            // random all questions
1895
            if ($this->isRandom() && 0 == $this->isRandomByCat()) {
1896
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1897
                    $this->random--;
1898
                    $this->save();
1899
                }
1900
            }
1901
            // deletes the position from the array containing the wanted question ID
1902
            unset($this->questionList[$pos]);
1903
1904
            return true;
1905
        }
1906
    }
1907
1908
    /**
1909
     * deletes the exercise from the database
1910
     * Notice : leaves the question in the data base.
1911
     *
1912
     * @author Olivier Brouckaert
1913
     */
1914
    public function delete()
1915
    {
1916
        $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
1917
1918
        if ($limitTeacherAccess && !api_is_platform_admin()) {
1919
            return false;
1920
        }
1921
1922
        $locked = api_resource_is_locked_by_gradebook(
1923
            $this->id,
1924
            LINK_EXERCISE
1925
        );
1926
1927
        if ($locked) {
1928
            return false;
1929
        }
1930
1931
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
1932
        $sql = "UPDATE $table SET active='-1'
1933
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id);
1934
        Database::query($sql);
1935
1936
        api_item_property_update(
1937
            $this->course,
1938
            TOOL_QUIZ,
1939
            $this->id,
1940
            'QuizDeleted',
1941
            api_get_user_id()
1942
        );
1943
        api_item_property_update(
1944
            $this->course,
1945
            TOOL_QUIZ,
1946
            $this->id,
1947
            'delete',
1948
            api_get_user_id()
1949
        );
1950
1951
        Skill::deleteSkillsFromItem($this->iId, ITEM_TYPE_EXERCISE);
1952
1953
        if (api_get_setting('search_enabled') === 'true' &&
1954
            extension_loaded('xapian')
1955
        ) {
1956
            $this->search_engine_delete();
1957
        }
1958
1959
        $linkInfo = GradebookUtils::isResourceInCourseGradebook(
1960
            $this->course['code'],
1961
            LINK_EXERCISE,
1962
            $this->id,
1963
            $this->sessionId
1964
        );
1965
1966
        if ($linkInfo !== false) {
1967
            GradebookUtils::remove_resource_from_course_gradebook($linkInfo['id']);
1968
        }
1969
1970
        return true;
1971
    }
1972
1973
    /**
1974
     * Creates the form to create / edit an exercise.
1975
     *
1976
     * @param FormValidator $form
1977
     * @param string        $type
1978
     */
1979
    public function createForm($form, $type = 'full')
1980
    {
1981
        if (empty($type)) {
1982
            $type = 'full';
1983
        }
1984
1985
        // Form title
1986
        $form_title = get_lang('NewEx');
1987
        if (!empty($_GET['exerciseId'])) {
1988
            $form_title = get_lang('ModifyExercise');
1989
        }
1990
1991
        $form->addHeader($form_title);
1992
1993
        // Title.
1994
        if (api_get_configuration_value('save_titles_as_html')) {
1995
            $form->addHtmlEditor(
1996
                'exerciseTitle',
1997
                get_lang('ExerciseName'),
1998
                false,
1999
                false,
2000
                ['ToolbarSet' => 'TitleAsHtml']
2001
            );
2002
        } else {
2003
            $form->addElement(
2004
                'text',
2005
                'exerciseTitle',
2006
                get_lang('ExerciseName'),
2007
                ['id' => 'exercise_title']
2008
            );
2009
        }
2010
2011
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
2012
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
2013
2014
        if (api_get_configuration_value('allow_exercise_categories')) {
2015
            $categoryManager = new ExerciseCategoryManager();
2016
            $categories = $categoryManager->getCategories(api_get_course_int_id());
2017
            $options = [];
2018
            if (!empty($categories)) {
2019
                /** @var CExerciseCategory $category */
2020
                foreach ($categories as $category) {
2021
                    $options[$category->getId()] = $category->getName();
2022
                }
2023
            }
2024
2025
            $form->addSelect(
2026
                'exercise_category_id',
2027
                get_lang('Category'),
2028
                $options,
2029
                ['placeholder' => get_lang('SelectAnOption')]
2030
            );
2031
        }
2032
2033
        $editor_config = [
2034
            'ToolbarSet' => 'TestQuestionDescription',
2035
            'Width' => '100%',
2036
            'Height' => '150',
2037
        ];
2038
2039
        if (is_array($type)) {
2040
            $editor_config = array_merge($editor_config, $type);
2041
        }
2042
2043
        $form->addHtmlEditor(
2044
            'exerciseDescription',
2045
            get_lang('ExerciseDescription'),
2046
            false,
2047
            false,
2048
            $editor_config
2049
        );
2050
2051
        $skillList = [];
2052
        if ('full' === $type) {
2053
            // Can't modify a DirectFeedback question.
2054
            if (!in_array($this->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
2055
                $this->setResultFeedbackGroup($form);
2056
2057
                // Type of results display on the final page
2058
                $this->setResultDisabledGroup($form);
2059
2060
                // Type of questions disposition on page
2061
                $radios = [];
2062
                $radios[] = $form->createElement(
2063
                    'radio',
2064
                    'exerciseType',
2065
                    null,
2066
                    get_lang('SimpleExercise'),
2067
                    '1',
2068
                    [
2069
                        'onclick' => 'check_per_page_all()',
2070
                        'id' => 'option_page_all',
2071
                    ]
2072
                );
2073
                $radios[] = $form->createElement(
2074
                    'radio',
2075
                    'exerciseType',
2076
                    null,
2077
                    get_lang('SequentialExercise'),
2078
                    '2',
2079
                    [
2080
                        'onclick' => 'check_per_page_one()',
2081
                        'id' => 'option_page_one',
2082
                    ]
2083
                );
2084
2085
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
2086
            } else {
2087
                // if is Direct feedback but has not questions we can allow to modify the question type
2088
                if (empty($this->iId) || 0 === $this->getQuestionCount()) {
2089
                    $this->setResultFeedbackGroup($form);
2090
                    $this->setResultDisabledGroup($form);
2091
2092
                    // Type of questions disposition on page
2093
                    $radios = [];
2094
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1');
2095
                    $radios[] = $form->createElement(
2096
                        'radio',
2097
                        'exerciseType',
2098
                        null,
2099
                        get_lang('SequentialExercise'),
2100
                        '2'
2101
                    );
2102
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
2103
                } else {
2104
                    $this->setResultFeedbackGroup($form, true);
2105
                    $group = $this->setResultDisabledGroup($form);
2106
                    $group->freeze();
2107
2108
                    // we force the options to the DirectFeedback exercisetype
2109
                    //$form->addElement('hidden', 'exerciseFeedbackType', $this->getFeedbackType());
2110
                    //$form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
2111
2112
                    // Type of questions disposition on page
2113
                    $radios[] = $form->createElement(
2114
                        'radio',
2115
                        'exerciseType',
2116
                        null,
2117
                        get_lang('SimpleExercise'),
2118
                        '1',
2119
                        [
2120
                            'onclick' => 'check_per_page_all()',
2121
                            'id' => 'option_page_all',
2122
                        ]
2123
                    );
2124
                    $radios[] = $form->createElement(
2125
                        'radio',
2126
                        'exerciseType',
2127
                        null,
2128
                        get_lang('SequentialExercise'),
2129
                        '2',
2130
                        [
2131
                            'onclick' => 'check_per_page_one()',
2132
                            'id' => 'option_page_one',
2133
                        ]
2134
                    );
2135
2136
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'));
2137
                    $type_group->freeze();
2138
                }
2139
            }
2140
2141
            $option = [
2142
                EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2143
                //  Defined by user
2144
                EX_Q_SELECTION_RANDOM => get_lang('Random'),
2145
                // 1-10, All
2146
                'per_categories' => '--------'.get_lang('UsingCategories').'----------',
2147
                // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2148
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang('OrderedCategoriesAlphabeticallyWithQuestionsOrdered'),
2149
                // A 123 B 456 C 78 (0, 1, all)
2150
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang('RandomCategoriesWithQuestionsOrdered'),
2151
                // C 78 B 456 A 123
2152
                EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang('OrderedCategoriesAlphabeticallyWithRandomQuestions'),
2153
                // A 321 B 654 C 87
2154
                EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang('RandomCategoriesWithRandomQuestions'),
2155
                // C 87 B 654 A 321
2156
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2157
                /*    B 456 C 78 A 123
2158
                        456 78 123
2159
                        123 456 78
2160
                */
2161
                //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2162
                /*
2163
                    A 123 B 456 C 78
2164
                    B 456 C 78 A 123
2165
                    B 654 C 87 A 321
2166
                    654 87 321
2167
                    165 842 73
2168
                */
2169
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2170
                //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2171
            ];
2172
2173
            $form->addElement(
2174
                'select',
2175
                'question_selection_type',
2176
                [get_lang('QuestionSelection')],
2177
                $option,
2178
                [
2179
                    'id' => 'questionSelection',
2180
                    'onchange' => 'checkQuestionSelection()',
2181
                ]
2182
            );
2183
2184
            $pageConfig = api_get_configuration_value('allow_quiz_results_page_config');
2185
            if ($pageConfig) {
2186
                $group = [
2187
                    $form->createElement(
2188
                        'checkbox',
2189
                        'hide_expected_answer',
2190
                        null,
2191
                        get_lang('HideExpectedAnswer')
2192
                    ),
2193
                    $form->createElement(
2194
                        'checkbox',
2195
                        'hide_total_score',
2196
                        null,
2197
                        get_lang('HideTotalScore')
2198
                    ),
2199
                    $form->createElement(
2200
                        'checkbox',
2201
                        'hide_question_score',
2202
                        null,
2203
                        get_lang('HideQuestionScore')
2204
                    ),
2205
                    $form->createElement(
2206
                        'checkbox',
2207
                        'hide_category_table',
2208
                        null,
2209
                        get_lang('HideCategoryTable')
2210
                    ),
2211
                    $form->createElement(
2212
                        'checkbox',
2213
                        'hide_correct_answered_questions',
2214
                        null,
2215
                        get_lang('HideCorrectAnsweredQuestions')
2216
                    ),
2217
                ];
2218
                $form->addGroup($group, null, get_lang('ResultsConfigurationPage'));
2219
            }
2220
2221
            $displayMatrix = 'none';
2222
            $displayRandom = 'none';
2223
            $selectionType = $this->getQuestionSelectionType();
2224
            switch ($selectionType) {
2225
                case EX_Q_SELECTION_RANDOM:
2226
                    $displayRandom = 'block';
2227
                    break;
2228
                case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2229
                    $displayMatrix = 'block';
2230
                    break;
2231
            }
2232
2233
            $form->addHtml('<div id="hidden_random" style="display:'.$displayRandom.'">');
2234
            // Number of random question.
2235
            $max = ($this->id > 0) ? $this->getQuestionCount() : 10;
2236
            $option = range(0, $max);
2237
            $option[0] = get_lang('No');
2238
            $option[-1] = get_lang('AllQuestionsShort');
2239
            $form->addElement(
2240
                'select',
2241
                'randomQuestions',
2242
                [
2243
                    get_lang('RandomQuestions'),
2244
                    get_lang('RandomQuestionsHelp'),
2245
                ],
2246
                $option,
2247
                ['id' => 'randomQuestions']
2248
            );
2249
            $form->addHtml('</div>');
2250
            $form->addHtml('<div id="hidden_matrix" style="display:'.$displayMatrix.'">');
2251
2252
            // Category selection.
2253
            $cat = new TestCategory();
2254
            $cat_form = $cat->returnCategoryForm($this);
2255
            if (empty($cat_form)) {
2256
                $cat_form = '<span class="label label-warning">'.get_lang('NoCategoriesDefined').'</span>';
2257
            }
2258
            $form->addElement('label', null, $cat_form);
2259
            $form->addHtml('</div>');
2260
2261
            // Random answers.
2262
            $radios_random_answers = [
2263
                $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2264
                $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0'),
2265
            ];
2266
            $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'));
2267
2268
            // Category name.
2269
            $radio_display_cat_name = [
2270
                $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2271
                $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0'),
2272
            ];
2273
            $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'));
2274
2275
            // Hide question title.
2276
            $group = [
2277
                $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2278
                $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0'),
2279
            ];
2280
            $form->addGroup($group, null, get_lang('HideQuestionTitle'));
2281
2282
            $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
2283
            if (true === $allow) {
2284
                // Hide question title.
2285
                $group = [
2286
                    $form->createElement(
2287
                        'radio',
2288
                        'show_previous_button',
2289
                        null,
2290
                        get_lang('Yes'),
2291
                        '1'
2292
                    ),
2293
                    $form->createElement(
2294
                        'radio',
2295
                        'show_previous_button',
2296
                        null,
2297
                        get_lang('No'),
2298
                        '0'
2299
                    ),
2300
                ];
2301
                $form->addGroup($group, null, get_lang('ShowPreviousButton'));
2302
            }
2303
2304
            $form->addElement(
2305
                'number',
2306
                'exerciseAttempts',
2307
                get_lang('ExerciseAttempts'),
2308
                null,
2309
                ['id' => 'exerciseAttempts']
2310
            );
2311
2312
            // Exercise time limit
2313
            $form->addElement(
2314
                'checkbox',
2315
                'activate_start_date_check',
2316
                null,
2317
                get_lang('EnableStartTime'),
2318
                ['onclick' => 'activate_start_date()']
2319
            );
2320
2321
            if (!empty($this->start_time)) {
2322
                $form->addElement('html', '<div id="start_date_div" style="display:block;">');
2323
            } else {
2324
                $form->addElement('html', '<div id="start_date_div" style="display:none;">');
2325
            }
2326
2327
            $form->addElement('date_time_picker', 'start_time');
2328
            $form->addElement('html', '</div>');
2329
            $form->addElement(
2330
                'checkbox',
2331
                'activate_end_date_check',
2332
                null,
2333
                get_lang('EnableEndTime'),
2334
                ['onclick' => 'activate_end_date()']
2335
            );
2336
2337
            if (!empty($this->end_time)) {
2338
                $form->addHtml('<div id="end_date_div" style="display:block;">');
2339
            } else {
2340
                $form->addHtml('<div id="end_date_div" style="display:none;">');
2341
            }
2342
2343
            $form->addElement('date_time_picker', 'end_time');
2344
            $form->addElement('html', '</div>');
2345
2346
            $display = 'block';
2347
            $form->addElement(
2348
                'checkbox',
2349
                'propagate_neg',
2350
                null,
2351
                get_lang('PropagateNegativeResults')
2352
            );
2353
2354
            if (api_get_configuration_value('allow_quiz_save_correct_options')) {
2355
                $options = [
2356
                    '' => get_lang('SelectAnOption'),
2357
                    1 => get_lang('SaveTheCorrectAnswersForTheNextAttempt'),
2358
                    2 => get_lang('SaveAllAnswers'),
2359
                ];
2360
                $form->addSelect(
2361
                    'save_correct_answers',
2362
                    get_lang('SaveAnswers'),
2363
                    $options
2364
                );
2365
            } else {
2366
                $form->addCheckBox(
2367
                    'save_correct_answers',
2368
                    null,
2369
                    get_lang('SaveTheCorrectAnswersForTheNextAttempt')
2370
                );
2371
            }
2372
2373
            $form->addElement('html', '<div class="clear">&nbsp;</div>');
2374
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2375
            $form->addElement('html', '<div id="divtimecontrol"  style="display:'.$display.';">');
2376
2377
            // Timer control
2378
            $form->addElement(
2379
                'checkbox',
2380
                'enabletimercontrol',
2381
                null,
2382
                get_lang('EnableTimerControl'),
2383
                [
2384
                    'onclick' => 'option_time_expired()',
2385
                    'id' => 'enabletimercontrol',
2386
                    'onload' => 'check_load_time()',
2387
                ]
2388
            );
2389
2390
            $expired_date = (int) $this->selectExpiredTime();
2391
2392
            if (($expired_date != '0')) {
2393
                $form->addElement('html', '<div id="timercontrol" style="display:block;">');
2394
            } else {
2395
                $form->addElement('html', '<div id="timercontrol" style="display:none;">');
2396
            }
2397
            $form->addText(
2398
                'enabletimercontroltotalminutes',
2399
                get_lang('ExerciseTotalDurationInMinutes'),
2400
                false,
2401
                [
2402
                    'id' => 'enabletimercontroltotalminutes',
2403
                    'cols-size' => [2, 2, 8],
2404
                ]
2405
            );
2406
            $form->addElement('html', '</div>');
2407
2408
            if (api_get_configuration_value('quiz_prevent_backwards_move')) {
2409
                $form->addCheckBox(
2410
                    'prevent_backwards',
2411
                    null,
2412
                    get_lang('QuizPreventBackwards')
2413
                );
2414
            }
2415
2416
            $form->addElement(
2417
                'text',
2418
                'pass_percentage',
2419
                [get_lang('PassPercentage'), null, '%'],
2420
                ['id' => 'pass_percentage']
2421
            );
2422
2423
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2424
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2425
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2426
2427
            // add the text_when_finished textbox
2428
            $form->addHtmlEditor(
2429
                'text_when_finished',
2430
                get_lang('TextWhenFinished'),
2431
                false,
2432
                false,
2433
                $editor_config
2434
            );
2435
2436
            $allow = api_get_configuration_value('allow_notification_setting_per_exercise');
2437
            if ($allow === true) {
2438
                $settings = ExerciseLib::getNotificationSettings();
2439
                $group = [];
2440
                foreach ($settings as $itemId => $label) {
2441
                    $group[] = $form->createElement(
2442
                        'checkbox',
2443
                        'notifications[]',
2444
                        null,
2445
                        $label,
2446
                        ['value' => $itemId]
2447
                    );
2448
                }
2449
                $form->addGroup($group, '', [get_lang('EmailNotifications')]);
2450
            }
2451
2452
            $form->addCheckBox(
2453
                'update_title_in_lps',
2454
                null,
2455
                get_lang('UpdateTitleInLps')
2456
            );
2457
2458
            $defaults = [];
2459
            if (api_get_setting('search_enabled') === 'true') {
2460
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2461
                $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument'));
2462
                $form->addSelectLanguage('language', get_lang('SearchFeatureDocumentLanguage'));
2463
                $specific_fields = get_specific_field_list();
2464
2465
                foreach ($specific_fields as $specific_field) {
2466
                    $form->addElement('text', $specific_field['code'], $specific_field['name']);
2467
                    $filter = [
2468
                        'c_id' => api_get_course_int_id(),
2469
                        'field_id' => $specific_field['id'],
2470
                        'ref_id' => $this->id,
2471
                        'tool_id' => "'".TOOL_QUIZ."'",
2472
                    ];
2473
                    $values = get_specific_field_values_list($filter, ['value']);
2474
                    if (!empty($values)) {
2475
                        $arr_str_values = [];
2476
                        foreach ($values as $value) {
2477
                            $arr_str_values[] = $value['value'];
2478
                        }
2479
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2480
                    }
2481
                }
2482
            }
2483
2484
            $skillList = Skill::addSkillsToForm($form, ITEM_TYPE_EXERCISE, $this->iId);
2485
2486
            $extraField = new ExtraField('exercise');
2487
            $extraField->addElements(
2488
                $form,
2489
                $this->iId,
2490
                [
2491
                    'notifications',
2492
                    'remedialcourselist',
2493
                    'advancedcourselist',
2494
                ], //exclude
2495
                false, // filter
2496
                false, // tag as select
2497
                [], //show only fields
2498
                [], // order fields
2499
                [] // extra data
2500
            );
2501
2502
            // See BT#18165
2503
            $remedialList = [
2504
                'remedialcourselist' => 'RemedialCourses',
2505
                'advancedcourselist' => 'AdvancedCourses',
2506
            ];
2507
            $extraFieldExercice = new ExtraField('exercise');
2508
            $extraFieldExerciceValue = new ExtraFieldValue('exercise');
2509
            $pluginRemedial = api_get_plugin_setting('remedial_course', 'enabled') === 'true';
2510
            if ($pluginRemedial) {
2511
                $sessionId = api_get_session_id();
2512
                $userId = api_get_user_id();
2513
                foreach ($remedialList as $item => $label) {
2514
                    $remedialField = $extraFieldExercice->get_handler_field_info_by_field_variable($item);
2515
                    $optionRemedial = [];
2516
                    $defaults[$item] = [];
2517
                    $remedialExtraValue = $extraFieldExerciceValue->get_values_by_handler_and_field_id($this->iId, $remedialField['id']);
2518
                    $defaults[$item] = isset($remedialExtraValue['value']) ? explode(';', $remedialExtraValue['value']) : [];
2519
                    if ($sessionId != 0) {
2520
                        $courseList = SessionManager::getCoursesInSession($sessionId);
2521
                        foreach ($courseList as $course) {
2522
                            $courseSession = api_get_course_info_by_id($course);
2523
                            if (!empty($courseSession) && isset($courseSession['real_id'])) {
2524
                                $courseId = $courseSession['real_id'];
2525
                                if (api_get_course_int_id() != $courseId) {
2526
                                    $optionRemedial[$courseId] = $courseSession['title'];
2527
                                }
2528
                            }
2529
                        }
2530
                    } else {
2531
                        $courseList = CourseManager::get_course_list();
2532
                        foreach ($courseList as $course) {
2533
                            if (!empty($course) && isset($course['real_id'])) {
2534
                                $courseId = $course['real_id'];
2535
                                if (api_get_course_int_id() != $courseId) {
2536
                                    $optionRemedial[$courseId] = $course['title'];
2537
                                }
2538
                            }
2539
                        }
2540
                    }
2541
                    unset($optionRemedial[0]);
2542
                    $form->addSelect(
2543
                        "extra_".$item,
2544
                        get_plugin_lang($label, RemedialCoursePlugin::class),
2545
                        $optionRemedial,
2546
                        [
2547
                            'placeholder' => get_lang('SelectAnOption'),
2548
                            'multiple' => 'multiple',
2549
                        ]
2550
                    );
2551
                }
2552
            }
2553
2554
            $settings = api_get_configuration_value('exercise_finished_notification_settings');
2555
            if (!empty($settings)) {
2556
                $options = [];
2557
                foreach ($settings as $name => $data) {
2558
                    $options[$name] = $name;
2559
                }
2560
                $form->addSelect(
2561
                    'extra_notifications',
2562
                    get_lang('Notifications'),
2563
                    $options,
2564
                    ['placeholder' => get_lang('SelectAnOption')]
2565
                );
2566
            }
2567
            $form->addElement('html', '</div>'); //End advanced setting
2568
            $form->addElement('html', '</div>');
2569
        }
2570
2571
        // submit
2572
        if (isset($_GET['exerciseId'])) {
2573
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2574
        } else {
2575
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2576
        }
2577
2578
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2579
2580
        // defaults
2581
        if ($type == 'full') {
2582
            // rules
2583
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2584
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2585
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2586
2587
            if ($this->id > 0) {
2588
                $defaults['randomQuestions'] = $this->random;
2589
                $defaults['randomAnswers'] = $this->getRandomAnswers();
2590
                $defaults['exerciseType'] = $this->selectType();
2591
                $defaults['exerciseTitle'] = $this->get_formated_title();
2592
                $defaults['exerciseDescription'] = $this->selectDescription();
2593
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2594
                $defaults['exerciseFeedbackType'] = $this->getFeedbackType();
2595
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2596
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2597
                $defaults['save_correct_answers'] = $this->getSaveCorrectAnswers();
2598
                $defaults['review_answers'] = $this->review_answers;
2599
                $defaults['randomByCat'] = $this->getRandomByCategory();
2600
                $defaults['text_when_finished'] = $this->getTextWhenFinished();
2601
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2602
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2603
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2604
                $defaults['hide_question_title'] = $this->getHideQuestionTitle();
2605
                $defaults['show_previous_button'] = $this->showPreviousButton();
2606
                $defaults['exercise_category_id'] = $this->getExerciseCategoryId();
2607
                $defaults['prevent_backwards'] = $this->getPreventBackwards();
2608
2609
                if (!empty($this->start_time)) {
2610
                    $defaults['activate_start_date_check'] = 1;
2611
                }
2612
                if (!empty($this->end_time)) {
2613
                    $defaults['activate_end_date_check'] = 1;
2614
                }
2615
2616
                $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2617
                $defaults['end_time'] = !empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time() + 84600);
2618
2619
                // Get expired time
2620
                if ($this->expired_time != '0') {
2621
                    $defaults['enabletimercontrol'] = 1;
2622
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2623
                } else {
2624
                    $defaults['enabletimercontroltotalminutes'] = 0;
2625
                }
2626
                $defaults['skills'] = array_keys($skillList);
2627
                $defaults['notifications'] = $this->getNotifications();
2628
            } else {
2629
                $defaults['exerciseType'] = 2;
2630
                $defaults['exerciseAttempts'] = 0;
2631
                $defaults['randomQuestions'] = 0;
2632
                $defaults['randomAnswers'] = 0;
2633
                $defaults['exerciseDescription'] = '';
2634
                $defaults['exerciseFeedbackType'] = 0;
2635
                $defaults['results_disabled'] = 0;
2636
                $defaults['randomByCat'] = 0;
2637
                $defaults['text_when_finished'] = '';
2638
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2639
                $defaults['display_category_name'] = 1;
2640
                $defaults['end_time'] = date('Y-m-d 12:00:00', time() + 84600);
2641
                $defaults['pass_percentage'] = '';
2642
                $defaults['end_button'] = $this->selectEndButton();
2643
                $defaults['question_selection_type'] = 1;
2644
                $defaults['hide_question_title'] = 0;
2645
                $defaults['show_previous_button'] = 1;
2646
                $defaults['on_success_message'] = null;
2647
                $defaults['on_failed_message'] = null;
2648
            }
2649
        } else {
2650
            $defaults['exerciseTitle'] = $this->selectTitle();
2651
            $defaults['exerciseDescription'] = $this->selectDescription();
2652
        }
2653
2654
        if (api_get_setting('search_enabled') === 'true') {
2655
            $defaults['index_document'] = 'checked="checked"';
2656
        }
2657
2658
        $this->setPageResultConfigurationDefaults($defaults);
2659
        $form->setDefaults($defaults);
2660
2661
        // Freeze some elements.
2662
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2663
            $elementsToFreeze = [
2664
                'randomQuestions',
2665
                //'randomByCat',
2666
                'exerciseAttempts',
2667
                'propagate_neg',
2668
                'enabletimercontrol',
2669
                'review_answers',
2670
            ];
2671
2672
            foreach ($elementsToFreeze as $elementName) {
2673
                /** @var HTML_QuickForm_element $element */
2674
                $element = $form->getElement($elementName);
2675
                $element->freeze();
2676
            }
2677
        }
2678
    }
2679
2680
    public function setResultFeedbackGroup(FormValidator $form, $checkFreeze = true)
2681
    {
2682
        // Feedback type.
2683
        $feedback = [];
2684
        $warning = sprintf(
2685
            get_lang('TheSettingXWillChangeToX'),
2686
            get_lang('ShowResultsToStudents'),
2687
            get_lang('ShowScoreAndRightAnswer')
2688
        );
2689
        $endTest = $form->createElement(
2690
            'radio',
2691
            'exerciseFeedbackType',
2692
            null,
2693
            get_lang('ExerciseAtTheEndOfTheTest'),
2694
            EXERCISE_FEEDBACK_TYPE_END,
2695
            [
2696
                'id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_END,
2697
                //'onclick' => 'if confirm() check_feedback()',
2698
                'onclick' => 'javascript:if(confirm('."'".addslashes($warning)."'".')) { check_feedback(); } else { return false;} ',
2699
            ]
2700
        );
2701
2702
        $noFeedBack = $form->createElement(
2703
            'radio',
2704
            'exerciseFeedbackType',
2705
            null,
2706
            get_lang('NoFeedback'),
2707
            EXERCISE_FEEDBACK_TYPE_EXAM,
2708
            [
2709
                'id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_EXAM,
2710
            ]
2711
        );
2712
2713
        $feedback[] = $endTest;
2714
        $feedback[] = $noFeedBack;
2715
2716
        $scenarioEnabled = 'true' === api_get_setting('enable_quiz_scenario');
2717
        $freeze = true;
2718
        if ($scenarioEnabled) {
2719
            if ($this->getQuestionCount() > 0) {
2720
                $hasDifferentQuestion = $this->hasQuestionWithTypeNotInList([UNIQUE_ANSWER, HOT_SPOT_DELINEATION]);
2721
2722
                if (false === $hasDifferentQuestion) {
2723
                    $freeze = false;
2724
                }
2725
            } else {
2726
                $freeze = false;
2727
            }
2728
2729
            $direct = $form->createElement(
2730
                'radio',
2731
                'exerciseFeedbackType',
2732
                null,
2733
                get_lang('DirectFeedback'),
2734
                EXERCISE_FEEDBACK_TYPE_DIRECT,
2735
                [
2736
                    'id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_DIRECT,
2737
                    'onclick' => 'check_direct_feedback()',
2738
                ]
2739
            );
2740
2741
            $directPopUp = $form->createElement(
2742
                'radio',
2743
                'exerciseFeedbackType',
2744
                null,
2745
                get_lang('ExerciseDirectPopUp'),
2746
                EXERCISE_FEEDBACK_TYPE_POPUP,
2747
                ['id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_POPUP, 'onclick' => 'check_direct_feedback()']
2748
            );
2749
2750
            if ($freeze) {
2751
                $direct->freeze();
2752
                $directPopUp->freeze();
2753
            }
2754
2755
            // If has delineation freeze all.
2756
            $hasDelineation = $this->hasQuestionWithType(HOT_SPOT_DELINEATION);
2757
            if ($hasDelineation) {
2758
                $endTest->freeze();
2759
                $noFeedBack->freeze();
2760
                $direct->freeze();
2761
                $directPopUp->freeze();
2762
            }
2763
2764
            $feedback[] = $direct;
2765
            $feedback[] = $directPopUp;
2766
        }
2767
2768
        $form->addGroup(
2769
            $feedback,
2770
            null,
2771
            [
2772
                get_lang('FeedbackType'),
2773
                get_lang('FeedbackDisplayOptions'),
2774
            ]
2775
        );
2776
    }
2777
2778
    /**
2779
     * function which process the creation of exercises.
2780
     *
2781
     * @param FormValidator $form
2782
     * @param string
2783
     *
2784
     * @return int c_quiz.iid
2785
     */
2786
    public function processCreation($form, $type = '')
2787
    {
2788
        $this->updateTitle(self::format_title_variable($form->getSubmitValue('exerciseTitle')));
2789
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2790
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2791
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2792
        $this->updateType($form->getSubmitValue('exerciseType'));
2793
2794
        // If direct feedback then force to One per page
2795
        if (EXERCISE_FEEDBACK_TYPE_DIRECT == $form->getSubmitValue('exerciseFeedbackType')) {
2796
            $this->updateType(ONE_PER_PAGE);
2797
        }
2798
2799
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2800
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2801
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2802
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2803
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2804
        $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers'));
2805
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2806
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2807
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2808
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2809
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2810
        $this->updateCategories($form->getSubmitValue('category'));
2811
        $this->updateEndButton($form->getSubmitValue('end_button'));
2812
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2813
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2814
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2815
        $this->setEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2816
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2817
        $this->setModelType($form->getSubmitValue('model_type'));
2818
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2819
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2820
        $this->sessionId = api_get_session_id();
2821
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2822
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2823
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2824
        $this->setShowPreviousButton($form->getSubmitValue('show_previous_button'));
2825
        $this->setNotifications($form->getSubmitValue('notifications'));
2826
        $this->setExerciseCategoryId($form->getSubmitValue('exercise_category_id'));
2827
        $this->setPageResultConfiguration($form->getSubmitValues());
2828
        $this->preventBackwards = (int) $form->getSubmitValue('prevent_backwards');
2829
2830
        $this->start_time = null;
2831
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2832
            $start_time = $form->getSubmitValue('start_time');
2833
            $this->start_time = api_get_utc_datetime($start_time);
2834
        }
2835
2836
        $this->end_time = null;
2837
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2838
            $end_time = $form->getSubmitValue('end_time');
2839
            $this->end_time = api_get_utc_datetime($end_time);
2840
        }
2841
2842
        $this->expired_time = 0;
2843
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2844
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2845
            if ($this->expired_time == 0) {
2846
                $this->expired_time = $expired_total_time;
2847
            }
2848
        }
2849
2850
        $this->random_answers = 0;
2851
        if ($form->getSubmitValue('randomAnswers') == 1) {
2852
            $this->random_answers = 1;
2853
        }
2854
2855
        // Update title in all LPs that have this quiz added
2856
        if ($form->getSubmitValue('update_title_in_lps') == 1) {
2857
            $courseId = api_get_course_int_id();
2858
            $table = Database::get_course_table(TABLE_LP_ITEM);
2859
            $sql = "SELECT * FROM $table
2860
                    WHERE
2861
                        c_id = $courseId AND
2862
                        item_type = 'quiz' AND
2863
                        path = '".$this->id."'
2864
                    ";
2865
            $result = Database::query($sql);
2866
            $items = Database::store_result($result);
2867
            if (!empty($items)) {
2868
                foreach ($items as $item) {
2869
                    $itemId = $item['iid'];
2870
                    $sql = "UPDATE $table SET title = '".$this->title."'
2871
                            WHERE iid = $itemId AND c_id = $courseId ";
2872
                    Database::query($sql);
2873
                }
2874
            }
2875
        }
2876
2877
        $iId = $this->save($type);
2878
        if (!empty($iId)) {
2879
            $values = $form->getSubmitValues();
2880
            $values['item_id'] = $iId;
2881
            $extraFieldValue = new ExtraFieldValue('exercise');
2882
            $extraFieldValue->saveFieldValues($values);
2883
2884
            Skill::saveSkills($form, ITEM_TYPE_EXERCISE, $iId);
2885
        }
2886
    }
2887
2888
    public function search_engine_save()
2889
    {
2890
        if ($_POST['index_document'] != 1) {
2891
            return;
2892
        }
2893
        $course_id = api_get_course_id();
2894
2895
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2896
2897
        $specific_fields = get_specific_field_list();
2898
        $ic_slide = new IndexableChunk();
2899
2900
        $all_specific_terms = '';
2901
        foreach ($specific_fields as $specific_field) {
2902
            if (isset($_REQUEST[$specific_field['code']])) {
2903
                $sterms = trim($_REQUEST[$specific_field['code']]);
2904
                if (!empty($sterms)) {
2905
                    $all_specific_terms .= ' '.$sterms;
2906
                    $sterms = explode(',', $sterms);
2907
                    foreach ($sterms as $sterm) {
2908
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2909
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2910
                    }
2911
                }
2912
            }
2913
        }
2914
2915
        // build the chunk to index
2916
        $ic_slide->addValue("title", $this->exercise);
2917
        $ic_slide->addCourseId($course_id);
2918
        $ic_slide->addToolId(TOOL_QUIZ);
2919
        $xapian_data = [
2920
            SE_COURSE_ID => $course_id,
2921
            SE_TOOL_ID => TOOL_QUIZ,
2922
            SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2923
            SE_USER => (int) api_get_user_id(),
2924
        ];
2925
        $ic_slide->xapian_data = serialize($xapian_data);
2926
        $exercise_description = $all_specific_terms.' '.$this->description;
2927
        $ic_slide->addValue("content", $exercise_description);
2928
2929
        $di = new ChamiloIndexer();
2930
        isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2931
        $di->connectDb(null, null, $lang);
2932
        $di->addChunk($ic_slide);
2933
2934
        //index and return search engine document id
2935
        $did = $di->index();
2936
        if ($did) {
2937
            // save it to db
2938
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2939
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2940
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2941
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2942
            Database::query($sql);
2943
        }
2944
    }
2945
2946
    public function search_engine_edit()
2947
    {
2948
        // update search enchine and its values table if enabled
2949
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
2950
            $course_id = api_get_course_id();
2951
2952
            // actually, it consists on delete terms from db,
2953
            // insert new ones, create a new search engine document, and remove the old one
2954
            // get search_did
2955
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2956
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2957
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2958
            $res = Database::query($sql);
2959
2960
            if (Database::num_rows($res) > 0) {
2961
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2962
2963
                $se_ref = Database::fetch_array($res);
2964
                $specific_fields = get_specific_field_list();
2965
                $ic_slide = new IndexableChunk();
2966
2967
                $all_specific_terms = '';
2968
                foreach ($specific_fields as $specific_field) {
2969
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2970
                    if (isset($_REQUEST[$specific_field['code']])) {
2971
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2972
                        $all_specific_terms .= ' '.$sterms;
2973
                        $sterms = explode(',', $sterms);
2974
                        foreach ($sterms as $sterm) {
2975
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2976
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2977
                        }
2978
                    }
2979
                }
2980
2981
                // build the chunk to index
2982
                $ic_slide->addValue('title', $this->exercise);
2983
                $ic_slide->addCourseId($course_id);
2984
                $ic_slide->addToolId(TOOL_QUIZ);
2985
                $xapian_data = [
2986
                    SE_COURSE_ID => $course_id,
2987
                    SE_TOOL_ID => TOOL_QUIZ,
2988
                    SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->id],
2989
                    SE_USER => (int) api_get_user_id(),
2990
                ];
2991
                $ic_slide->xapian_data = serialize($xapian_data);
2992
                $exercise_description = $all_specific_terms.' '.$this->description;
2993
                $ic_slide->addValue('content', $exercise_description);
2994
2995
                $di = new ChamiloIndexer();
2996
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
2997
                $di->connectDb(null, null, $lang);
2998
                $di->remove_document($se_ref['search_did']);
2999
                $di->addChunk($ic_slide);
3000
3001
                //index and return search engine document id
3002
                $did = $di->index();
3003
                if ($did) {
3004
                    // save it to db
3005
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
3006
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
3007
                    Database::query($sql);
3008
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
3009
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
3010
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
3011
                    Database::query($sql);
3012
                }
3013
            } else {
3014
                $this->search_engine_save();
3015
            }
3016
        }
3017
    }
3018
3019
    public function search_engine_delete()
3020
    {
3021
        // remove from search engine if enabled
3022
        if ('true' == api_get_setting('search_enabled') && extension_loaded('xapian')) {
3023
            $course_id = api_get_course_id();
3024
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
3025
            $sql = 'SELECT * FROM %s
3026
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL
3027
                    LIMIT 1';
3028
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
3029
            $res = Database::query($sql);
3030
            if (Database::num_rows($res) > 0) {
3031
                $row = Database::fetch_array($res);
3032
                $di = new ChamiloIndexer();
3033
                $di->remove_document($row['search_did']);
3034
                unset($di);
3035
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
3036
                foreach ($this->questionList as $question_i) {
3037
                    $sql = 'SELECT type FROM %s WHERE id=%s';
3038
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
3039
                    $qres = Database::query($sql);
3040
                    if (Database::num_rows($qres) > 0) {
3041
                        $qrow = Database::fetch_array($qres);
3042
                        $objQuestion = Question::getInstance($qrow['type']);
3043
                        $objQuestion = Question::read((int) $question_i);
3044
                        $objQuestion->search_engine_edit($this->id, false, true);
3045
                        unset($objQuestion);
3046
                    }
3047
                }
3048
            }
3049
            $sql = 'DELETE FROM %s
3050
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL
3051
                    LIMIT 1';
3052
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
3053
            Database::query($sql);
3054
3055
            // remove terms from db
3056
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
3057
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
3058
        }
3059
    }
3060
3061
    public function selectExpiredTime()
3062
    {
3063
        return $this->expired_time;
3064
    }
3065
3066
    /**
3067
     * Cleans the student's results only for the Exercise tool (Not from the LP)
3068
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
3069
     * Works with exercises in sessions.
3070
     *
3071
     * @param bool   $cleanLpTests
3072
     * @param string $cleanResultBeforeDate
3073
     *
3074
     * @return int quantity of user's exercises deleted
3075
     */
3076
    public function cleanResults($cleanLpTests = false, $cleanResultBeforeDate = null)
3077
    {
3078
        $sessionId = api_get_session_id();
3079
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3080
        $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3081
3082
        $sql_where = '  AND
3083
                        orig_lp_id = 0 AND
3084
                        orig_lp_item_id = 0';
3085
3086
        // if we want to delete results from LP too
3087
        if ($cleanLpTests) {
3088
            $sql_where = '';
3089
        }
3090
3091
        // if we want to delete attempts before date $cleanResultBeforeDate
3092
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
3093
3094
        if (!empty($cleanResultBeforeDate)) {
3095
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
3096
            if (api_is_valid_date($cleanResultBeforeDate)) {
3097
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
3098
            } else {
3099
                return 0;
3100
            }
3101
        }
3102
3103
        $sql = "SELECT exe_id
3104
            FROM $table_track_e_exercises
3105
            WHERE
3106
                c_id = ".api_get_course_int_id()." AND
3107
                exe_exo_id = ".$this->id." AND
3108
                session_id = ".$sessionId." ".
3109
                $sql_where;
3110
3111
        $result = Database::query($sql);
3112
        $exe_list = Database::store_result($result);
3113
3114
        // deleting TRACK_E_ATTEMPT table
3115
        // check if exe in learning path or not
3116
        $i = 0;
3117
        if (is_array($exe_list) && count($exe_list) > 0) {
3118
            foreach ($exe_list as $item) {
3119
                $sql = "DELETE FROM $table_track_e_attempt
3120
                        WHERE exe_id = '".$item['exe_id']."'";
3121
                Database::query($sql);
3122
                $i++;
3123
            }
3124
        }
3125
3126
        // delete TRACK_E_EXERCISES table
3127
        $sql = "DELETE FROM $table_track_e_exercises
3128
                WHERE
3129
                  c_id = ".api_get_course_int_id()." AND
3130
                  exe_exo_id = ".$this->id." $sql_where AND
3131
                  session_id = ".$sessionId;
3132
        Database::query($sql);
3133
3134
        $this->generateStats($this->id, api_get_course_info(), $sessionId);
3135
3136
        Event::addEvent(
3137
            LOG_EXERCISE_RESULT_DELETE,
3138
            LOG_EXERCISE_ID,
3139
            $this->id,
3140
            null,
3141
            null,
3142
            api_get_course_int_id(),
3143
            $sessionId
3144
        );
3145
3146
        return $i;
3147
    }
3148
3149
    /**
3150
     * Copies an exercise (duplicate all questions and answers).
3151
     */
3152
    public function copyExercise()
3153
    {
3154
        $exerciseObject = $this;
3155
        $categories = $exerciseObject->getCategoriesInExercise(true);
3156
        // Get all questions no matter the order/category settings
3157
        $questionList = $exerciseObject->getQuestionOrderedList();
3158
        $sourceId = $exerciseObject->iId;
3159
        // Force the creation of a new exercise
3160
        $exerciseObject->updateTitle($exerciseObject->selectTitle().' - '.get_lang('Copy'));
3161
        // Hides the new exercise
3162
        $exerciseObject->updateStatus(false);
3163
        $exerciseObject->updateId(0);
3164
        $exerciseObject->sessionId = api_get_session_id();
3165
        $courseId = api_get_course_int_id();
3166
        $exerciseObject->save();
3167
        $newId = $exerciseObject->selectId();
3168
        $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
3169
3170
        $count = 1;
3171
        $batchSize = 20;
3172
        $em = Database::getManager();
3173
3174
        if ($newId && !empty($questionList)) {
3175
            $extraField = new ExtraFieldValue('exercise');
3176
            $extraField->copy($sourceId, $newId);
3177
3178
            // Question creation
3179
            foreach ($questionList as $oldQuestionId) {
3180
                $oldQuestionObj = Question::read($oldQuestionId, null, false);
3181
                $newQuestionId = $oldQuestionObj->duplicate();
3182
                if ($newQuestionId) {
3183
                    $newQuestionObj = Question::read($newQuestionId, null, false);
3184
                    if (isset($newQuestionObj) && $newQuestionObj) {
3185
                        $sql = "INSERT INTO $exerciseRelQuestionTable (c_id, question_id, exercice_id, question_order)
3186
                                VALUES ($courseId, ".$newQuestionId.", ".$newId.", '$count')";
3187
                        Database::query($sql);
3188
                        $count++;
3189
3190
                        if (!empty($oldQuestionObj->category)) {
3191
                            $newQuestionObj->saveCategory($oldQuestionObj->category);
3192
                        }
3193
3194
                        // This should be moved to the duplicate function
3195
                        $newAnswerObj = new Answer($oldQuestionId, $courseId, $exerciseObject);
3196
                        $newAnswerObj->read();
3197
                        $newAnswerObj->duplicate($newQuestionObj);
3198
3199
                        if (($count % $batchSize) === 0) {
3200
                            $em->clear(); // Detaches all objects from Doctrine!
3201
                        }
3202
                    }
3203
                }
3204
            }
3205
            if (!empty($categories)) {
3206
                $newCategoryList = [];
3207
                foreach ($categories as $category) {
3208
                    $newCategoryList[$category['category_id']] = $category['count_questions'];
3209
                }
3210
                $exerciseObject->save_categories_in_exercise($newCategoryList);
3211
            }
3212
        }
3213
    }
3214
3215
    /**
3216
     * Changes the exercise status.
3217
     *
3218
     * @param string $status - exercise status
3219
     */
3220
    public function updateStatus($status)
3221
    {
3222
        $this->active = $status;
3223
    }
3224
3225
    /**
3226
     * @param int    $lp_id
3227
     * @param int    $lp_item_id
3228
     * @param int    $lp_item_view_id
3229
     * @param string $status
3230
     *
3231
     * @return array
3232
     */
3233
    public function get_stat_track_exercise_info(
3234
        $lp_id = 0,
3235
        $lp_item_id = 0,
3236
        $lp_item_view_id = 0,
3237
        $status = 'incomplete'
3238
    ) {
3239
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3240
        if (empty($lp_id)) {
3241
            $lp_id = 0;
3242
        }
3243
        if (empty($lp_item_id)) {
3244
            $lp_item_id = 0;
3245
        }
3246
        if (empty($lp_item_view_id)) {
3247
            $lp_item_view_id = 0;
3248
        }
3249
        $condition = ' WHERE exe_exo_id 	= '."'".$this->id."'".' AND
3250
					   exe_user_id 			= '."'".api_get_user_id()."'".' AND
3251
					   c_id                 = '.api_get_course_int_id().' AND
3252
					   status 				= '."'".Database::escape_string($status)."'".' AND
3253
					   orig_lp_id 			= '."'".$lp_id."'".' AND
3254
					   orig_lp_item_id 		= '."'".$lp_item_id."'".' AND
3255
                       orig_lp_item_view_id = '."'".$lp_item_view_id."'".' AND
3256
					   session_id 			= '."'".api_get_session_id()."' LIMIT 1"; //Adding limit 1 just in case
3257
3258
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
3259
3260
        $result = Database::query($sql_track);
3261
        $new_array = [];
3262
        if (Database::num_rows($result) > 0) {
3263
            $new_array = Database::fetch_array($result, 'ASSOC');
3264
            $new_array['num_exe'] = Database::num_rows($result);
3265
        }
3266
3267
        return $new_array;
3268
    }
3269
3270
    /**
3271
     * Saves a test attempt.
3272
     *
3273
     * @param int $clock_expired_time clock_expired_time
3274
     * @param int  int lp id
3275
     * @param int  int lp item id
3276
     * @param int  int lp item_view id
3277
     * @param array $questionList
3278
     * @param float $weight
3279
     *
3280
     * @return int
3281
     */
3282
    public function save_stat_track_exercise_info(
3283
        $clock_expired_time = 0,
3284
        $safe_lp_id = 0,
3285
        $safe_lp_item_id = 0,
3286
        $safe_lp_item_view_id = 0,
3287
        $questionList = [],
3288
        $weight = 0
3289
    ) {
3290
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3291
        $safe_lp_id = (int) $safe_lp_id;
3292
        $safe_lp_item_id = (int) $safe_lp_item_id;
3293
        $safe_lp_item_view_id = (int) $safe_lp_item_view_id;
3294
3295
        if (empty($clock_expired_time)) {
3296
            $clock_expired_time = null;
3297
        }
3298
3299
        $questionList = array_map('intval', $questionList);
3300
3301
        $params = [
3302
            'exe_exo_id' => $this->id,
3303
            'exe_user_id' => api_get_user_id(),
3304
            'c_id' => api_get_course_int_id(),
3305
            'status' => 'incomplete',
3306
            'session_id' => api_get_session_id(),
3307
            'data_tracking' => implode(',', $questionList),
3308
            'start_date' => api_get_utc_datetime(),
3309
            'orig_lp_id' => $safe_lp_id,
3310
            'orig_lp_item_id' => $safe_lp_item_id,
3311
            'orig_lp_item_view_id' => $safe_lp_item_view_id,
3312
            'exe_weighting' => $weight,
3313
            'user_ip' => Database::escape_string(api_get_real_ip()),
3314
            'exe_date' => api_get_utc_datetime(),
3315
            'exe_result' => 0,
3316
            'steps_counter' => 0,
3317
            'exe_duration' => 0,
3318
            'expired_time_control' => $clock_expired_time,
3319
            'questions_to_check' => '',
3320
        ];
3321
3322
        return Database::insert($track_exercises, $params);
3323
    }
3324
3325
    /**
3326
     * @param int    $question_id
3327
     * @param int    $questionNum
3328
     * @param array  $questions_in_media
3329
     * @param string $currentAnswer
3330
     * @param array  $myRemindList
3331
     * @param bool   $showPreviousButton
3332
     *
3333
     * @return string
3334
     */
3335
    public function show_button(
3336
        $question_id,
3337
        $questionNum,
3338
        $questions_in_media = [],
3339
        $currentAnswer = '',
3340
        $myRemindList = [],
3341
        $showPreviousButton = true
3342
    ) {
3343
        global $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
3344
        $nbrQuestions = $this->countQuestionsInExercise();
3345
        $buttonList = [];
3346
        $html = $label = '';
3347
        $hotspotGet = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']) : null;
3348
3349
        if (in_array($this->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]) &&
3350
            $this->type == ONE_PER_PAGE
3351
        ) {
3352
            $urlTitle = get_lang('ContinueTest');
3353
            if ($questionNum == count($this->questionList)) {
3354
                $urlTitle = get_lang('EndTest');
3355
            }
3356
3357
            $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit_modal.php?'.api_get_cidreq();
3358
            $url .= '&'.http_build_query([
3359
                'learnpath_id' => $safe_lp_id,
3360
                'learnpath_item_id' => $safe_lp_item_id,
3361
                'learnpath_item_view_id' => $safe_lp_item_view_id,
3362
                'hotspot' => $hotspotGet,
3363
                'nbrQuestions' => $nbrQuestions,
3364
                'num' => $questionNum,
3365
                'exerciseType' => $this->type,
3366
                'exerciseId' => $this->id,
3367
                'reminder' => empty($myRemindList) ? null : 2,
3368
                'tryagain' => isset($_REQUEST['tryagain']) && 1 === (int) $_REQUEST['tryagain'] ? 1 : 0,
3369
            ]);
3370
3371
            $params = [
3372
                'class' => 'ajax btn btn-default no-close-button',
3373
                'data-title' => Security::remove_XSS(get_lang('Comment')),
3374
                'data-size' => 'md',
3375
                'id' => "button_$question_id",
3376
            ];
3377
3378
            if ($this->getFeedbackType() === EXERCISE_FEEDBACK_TYPE_POPUP) {
3379
                $params['data-block-closing'] = 'true';
3380
                $params['class'] .= ' no-header ';
3381
            }
3382
3383
            $html .= Display::url($urlTitle, $url, $params);
3384
            $html .= '<br />';
3385
3386
            return $html;
3387
        }
3388
3389
        if (!api_is_allowed_to_session_edit()) {
3390
            return '';
3391
        }
3392
3393
        $isReviewingAnswers = isset($_REQUEST['reminder']) && 2 === (int) $_REQUEST['reminder'];
3394
        $endReminderValue = false;
3395
        if (!empty($myRemindList) && $isReviewingAnswers) {
3396
            $endValue = end($myRemindList);
3397
            if ($endValue == $question_id) {
3398
                $endReminderValue = true;
3399
            }
3400
        }
3401
        $endTest = false;
3402
        if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum || $endReminderValue) {
3403
            if ($this->review_answers) {
3404
                $label = get_lang('ReviewQuestions');
3405
                $class = 'btn btn-success';
3406
            } else {
3407
                $endTest = true;
3408
                $label = get_lang('EndTest');
3409
                $class = 'btn btn-warning';
3410
            }
3411
        } else {
3412
            $label = get_lang('NextQuestion');
3413
            $class = 'btn btn-primary';
3414
        }
3415
        // used to select it with jquery
3416
        $class .= ' question-validate-btn';
3417
        if ($this->type == ONE_PER_PAGE) {
3418
            if ($questionNum != 1 && $this->showPreviousButton()) {
3419
                $prev_question = $questionNum - 2;
3420
                $showPreview = true;
3421
                if (!empty($myRemindList) && $isReviewingAnswers) {
3422
                    $beforeId = null;
3423
                    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...
3424
                        if (isset($myRemindList[$i]) && $myRemindList[$i] == $question_id) {
3425
                            $beforeId = isset($myRemindList[$i - 1]) ? $myRemindList[$i - 1] : null;
3426
                            break;
3427
                        }
3428
                    }
3429
3430
                    if (empty($beforeId)) {
3431
                        $showPreview = false;
3432
                    } else {
3433
                        $num = 0;
3434
                        foreach ($this->questionList as $originalQuestionId) {
3435
                            if ($originalQuestionId == $beforeId) {
3436
                                break;
3437
                            }
3438
                            $num++;
3439
                        }
3440
                        $prev_question = $num;
3441
                    }
3442
                }
3443
3444
                if ($showPreviousButton && $showPreview && 0 === $this->getPreventBackwards()) {
3445
                    $buttonList[] = Display::button(
3446
                        'previous_question_and_save',
3447
                        get_lang('PreviousQuestion'),
3448
                        [
3449
                            'type' => 'button',
3450
                            'class' => 'btn btn-default',
3451
                            'data-prev' => $prev_question,
3452
                            'data-question' => $question_id,
3453
                        ]
3454
                    );
3455
                }
3456
            }
3457
3458
            // Next question
3459
            if (!empty($questions_in_media)) {
3460
                $buttonList[] = Display::button(
3461
                    'save_question_list',
3462
                    $label,
3463
                    [
3464
                        'type' => 'button',
3465
                        'class' => $class,
3466
                        'data-list' => implode(",", $questions_in_media),
3467
                    ]
3468
                );
3469
            } else {
3470
                $attributes = ['type' => 'button', 'class' => $class, 'data-question' => $question_id];
3471
                $name = 'save_now';
3472
                if ($endTest && api_get_configuration_value('quiz_check_all_answers_before_end_test')) {
3473
                    $name = 'check_answers';
3474
                }
3475
                $buttonList[] = Display::button(
3476
                    $name,
3477
                    $label,
3478
                    $attributes
3479
                );
3480
            }
3481
            $buttonList[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>';
3482
3483
            $html .= implode(PHP_EOL, $buttonList).PHP_EOL;
3484
3485
            return $html;
3486
        }
3487
3488
        if ($this->review_answers) {
3489
            $all_label = get_lang('ReviewQuestions');
3490
            $class = 'btn btn-success';
3491
        } else {
3492
            $all_label = get_lang('EndTest');
3493
            $class = 'btn btn-warning';
3494
        }
3495
        // used to select it with jquery
3496
        $class .= ' question-validate-btn';
3497
        $buttonList[] = Display::button(
3498
            'validate_all',
3499
            $all_label,
3500
            ['type' => 'button', 'class' => $class]
3501
        );
3502
        $buttonList[] = Display::span(null, ['id' => 'save_all_response']);
3503
        $html .= implode(PHP_EOL, $buttonList).PHP_EOL;
3504
3505
        return $html;
3506
    }
3507
3508
    /**
3509
     * @param int    $timeLeft in seconds
3510
     * @param string $url
3511
     *
3512
     * @return string
3513
     */
3514
    public function showSimpleTimeControl($timeLeft, $url = '')
3515
    {
3516
        $timeLeft = (int) $timeLeft;
3517
3518
        return "<script>
3519
            function openClockWarning() {
3520
                $('#clock_warning').dialog({
3521
                    modal:true,
3522
                    height:320,
3523
                    width:550,
3524
                    closeOnEscape: false,
3525
                    resizable: false,
3526
                    buttons: {
3527
                        '".addslashes(get_lang('Close'))."': function() {
3528
                            $('#clock_warning').dialog('close');
3529
                        }
3530
                    },
3531
                    close: function() {
3532
                        window.location.href = '$url';
3533
                    }
3534
                });
3535
                $('#clock_warning').dialog('open');
3536
                $('#counter_to_redirect').epiclock({
3537
                    mode: $.epiclock.modes.countdown,
3538
                    offset: {seconds: 5},
3539
                    format: 's'
3540
                }).bind('timer', function () {
3541
                    window.location.href = '$url';
3542
                });
3543
            }
3544
3545
            function onExpiredTimeExercise() {
3546
                $('#wrapper-clock').hide();
3547
                $('#expired-message-id').show();
3548
                // Fixes bug #5263
3549
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3550
                openClockWarning();
3551
            }
3552
3553
			$(function() {
3554
				// time in seconds when using minutes there are some seconds lost
3555
                var time_left = parseInt(".$timeLeft.");
3556
                $('#exercise_clock_warning').epiclock({
3557
                    mode: $.epiclock.modes.countdown,
3558
                    offset: {seconds: time_left},
3559
                    format: 'x:i:s',
3560
                    renderer: 'minute'
3561
                }).bind('timer', function () {
3562
                    onExpiredTimeExercise();
3563
                });
3564
	       		$('#submit_save').click(function () {});
3565
	        });
3566
	    </script>";
3567
    }
3568
3569
    /**
3570
     * So the time control will work.
3571
     *
3572
     * @param int    $timeLeft
3573
     * @param string $redirectToUrl
3574
     *
3575
     * @return string
3576
     */
3577
    public function showTimeControlJS($timeLeft, $redirectToUrl = '')
3578
    {
3579
        $timeLeft = (int) $timeLeft;
3580
        $script = 'redirectExerciseToResult();';
3581
        if (ALL_ON_ONE_PAGE == $this->type) {
3582
            $script = "save_now_all('validate');";
3583
        } elseif (ONE_PER_PAGE == $this->type) {
3584
            $script = 'window.quizTimeEnding = true;
3585
                $(\'[name="save_now"]\').trigger(\'click\');';
3586
        }
3587
3588
        $exerciseSubmitRedirect = '';
3589
        if (!empty($redirectToUrl)) {
3590
            $exerciseSubmitRedirect = "window.location = '$redirectToUrl'";
3591
        }
3592
3593
        return "<script>
3594
            function openClockWarning() {
3595
                $('#clock_warning').dialog({
3596
                    modal:true,
3597
                    height:320,
3598
                    width:550,
3599
                    closeOnEscape: false,
3600
                    resizable: false,
3601
                    buttons: {
3602
                        '".addslashes(get_lang('EndTest'))."': function() {
3603
                            $('#clock_warning').dialog('close');
3604
                        }
3605
                    },
3606
                    close: function() {
3607
                        send_form();
3608
                    }
3609
                });
3610
3611
                $('#clock_warning').dialog('open');
3612
                $('#counter_to_redirect').epiclock({
3613
                    mode: $.epiclock.modes.countdown,
3614
                    offset: {seconds: 5},
3615
                    format: 's'
3616
                }).bind('timer', function () {
3617
                    send_form();
3618
                });
3619
            }
3620
3621
            function send_form() {
3622
                if ($('#exercise_form').length) {
3623
                    $script
3624
                } else {
3625
                    $exerciseSubmitRedirect
3626
                    // In exercise_reminder.php
3627
                    final_submit();
3628
                }
3629
            }
3630
3631
            function onExpiredTimeExercise() {
3632
                $('#wrapper-clock').hide();
3633
                $('#expired-message-id').show();
3634
                // Fixes bug #5263
3635
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3636
                openClockWarning();
3637
            }
3638
3639
			$(function() {
3640
				// time in seconds when using minutes there are some seconds lost
3641
                var time_left = parseInt(".$timeLeft.");
3642
                $('#exercise_clock_warning').epiclock({
3643
                    mode: $.epiclock.modes.countdown,
3644
                    offset: {seconds: time_left},
3645
                    format: 'x:C:s',
3646
                    renderer: 'minute'
3647
                }).bind('timer', function () {
3648
                    onExpiredTimeExercise();
3649
                });
3650
	       		$('#submit_save').click(function () {});
3651
	        });
3652
	    </script>";
3653
    }
3654
3655
    /**
3656
     * This function was originally found in the exercise_show.php.
3657
     *
3658
     * @param int    $exeId
3659
     * @param int    $questionId
3660
     * @param mixed  $choice                                    the user-selected option
3661
     * @param string $from                                      function is called from 'exercise_show' or
3662
     *                                                          'exercise_result'
3663
     * @param array  $exerciseResultCoordinates                 the hotspot coordinates $hotspot[$question_id] =
3664
     *                                                          coordinates
3665
     * @param bool   $save_results                              save results in the DB or just show the response
3666
     * @param bool   $from_database                             gets information from DB or from the current selection
3667
     * @param bool   $show_result                               show results or not
3668
     * @param int    $propagate_neg
3669
     * @param array  $hotspot_delineation_result
3670
     * @param bool   $showTotalScoreAndUserChoicesInLastAttempt
3671
     * @param bool   $updateResults
3672
     * @param bool   $showHotSpotDelineationTable
3673
     * @param int    $questionDuration                          seconds
3674
     *
3675
     * @todo    reduce parameters of this function
3676
     *
3677
     * @return string html code
3678
     */
3679
    public function manage_answer(
3680
        $exeId,
3681
        $questionId,
3682
        $choice,
3683
        $from = 'exercise_show',
3684
        $exerciseResultCoordinates = [],
3685
        $save_results = true,
3686
        $from_database = false,
3687
        $show_result = true,
3688
        $propagate_neg = 0,
3689
        $hotspot_delineation_result = [],
3690
        $showTotalScoreAndUserChoicesInLastAttempt = true,
3691
        $updateResults = false,
3692
        $showHotSpotDelineationTable = false,
3693
        $questionDuration = 0
3694
    ) {
3695
        $debug = false;
3696
        //needed in order to use in the exercise_attempt() for the time
3697
        global $learnpath_id, $learnpath_item_id;
3698
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3699
        $em = Database::getManager();
3700
        $feedback_type = $this->getFeedbackType();
3701
        $results_disabled = $this->selectResultsDisabled();
3702
        $questionDuration = (int) $questionDuration;
3703
3704
        if ($debug) {
3705
            error_log("<------ manage_answer ------> ");
3706
            error_log('exe_id: '.$exeId);
3707
            error_log('$from:  '.$from);
3708
            error_log('$save_results: '.intval($save_results));
3709
            error_log('$from_database: '.intval($from_database));
3710
            error_log('$show_result: '.intval($show_result));
3711
            error_log('$propagate_neg: '.$propagate_neg);
3712
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3713
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3714
            error_log('$learnpath_id: '.$learnpath_id);
3715
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3716
            error_log('$choice: '.print_r($choice, 1));
3717
            error_log('-----------------------------');
3718
        }
3719
3720
        $final_overlap = 0;
3721
        $final_missing = 0;
3722
        $final_excess = 0;
3723
        $overlap_color = 0;
3724
        $missing_color = 0;
3725
        $excess_color = 0;
3726
        $threadhold1 = 0;
3727
        $threadhold2 = 0;
3728
        $threadhold3 = 0;
3729
        $arrques = null;
3730
        $arrans = null;
3731
        $studentChoice = null;
3732
        $expectedAnswer = '';
3733
        $calculatedChoice = '';
3734
        $calculatedStatus = '';
3735
        $questionId = (int) $questionId;
3736
        $exeId = (int) $exeId;
3737
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3738
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3739
        $studentChoiceDegree = null;
3740
3741
        // Creates a temporary Question object
3742
        $course_id = $this->course_id;
3743
        $objQuestionTmp = Question::read($questionId, $this->course);
3744
3745
        if (false === $objQuestionTmp) {
3746
            return false;
3747
        }
3748
3749
        $questionName = $objQuestionTmp->selectTitle();
3750
        $questionWeighting = $objQuestionTmp->selectWeighting();
3751
        $answerType = $objQuestionTmp->selectType();
3752
        $quesId = $objQuestionTmp->selectId();
3753
        $extra = $objQuestionTmp->extra;
3754
        $next = 1; //not for now
3755
        $totalWeighting = 0;
3756
        $totalScore = 0;
3757
3758
        // Extra information of the question
3759
        if ((
3760
            $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
3761
            $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
3762
            )
3763
            && !empty($extra)
3764
        ) {
3765
            $extra = explode(':', $extra);
3766
            // Fixes problems with negatives values using intval
3767
            $true_score = (float) trim($extra[0]);
3768
            $false_score = (float) trim($extra[1]);
3769
            $doubt_score = (float) trim($extra[2]);
3770
        }
3771
3772
        // Construction of the Answer object
3773
        $objAnswerTmp = new Answer($questionId, $course_id);
3774
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3775
3776
        if ($debug) {
3777
            error_log('Count of possible answers: '.$nbrAnswers);
3778
            error_log('$answerType: '.$answerType);
3779
        }
3780
3781
        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
3782
            $choiceTmp = $choice;
3783
            $choice = isset($choiceTmp['choice']) ? $choiceTmp['choice'] : '';
3784
            $choiceDegreeCertainty = isset($choiceTmp['choiceDegreeCertainty']) ? $choiceTmp['choiceDegreeCertainty'] : '';
3785
        }
3786
3787
        if ($answerType == FREE_ANSWER ||
3788
            $answerType == ORAL_EXPRESSION ||
3789
            $answerType == CALCULATED_ANSWER ||
3790
            $answerType == ANNOTATION
3791
        ) {
3792
            $nbrAnswers = 1;
3793
        }
3794
3795
        $generatedFile = '';
3796
        if ($answerType == ORAL_EXPRESSION) {
3797
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3798
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3799
            $objQuestionTmp->initFile(
3800
                api_get_session_id(),
3801
                isset($exe_info['exe_user_id']) ? $exe_info['exe_user_id'] : api_get_user_id(),
3802
                isset($exe_info['exe_exo_id']) ? $exe_info['exe_exo_id'] : $this->id,
3803
                isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId
3804
            );
3805
3806
            // Probably this attempt came in an exercise all question by page
3807
            if ($feedback_type == 0) {
3808
                $objQuestionTmp->replaceWithRealExe($exeId);
3809
            }
3810
            $generatedFile = $objQuestionTmp->getFileUrl();
3811
        }
3812
3813
        $user_answer = '';
3814
        // Get answer list for matching.
3815
        $sql = "SELECT id_auto, id, answer
3816
                FROM $table_ans
3817
                WHERE c_id = $course_id AND question_id = $questionId";
3818
        $res_answer = Database::query($sql);
3819
3820
        $answerMatching = [];
3821
        while ($real_answer = Database::fetch_array($res_answer)) {
3822
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3823
        }
3824
3825
        // Get first answer needed for global question, no matter the answer shuffle option;
3826
        $firstAnswer = [];
3827
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
3828
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
3829
        ) {
3830
            $sql = "SELECT *
3831
                    FROM $table_ans
3832
                    WHERE c_id = $course_id AND question_id = $questionId
3833
                    ORDER BY position
3834
                    LIMIT 1";
3835
            $result = Database::query($sql);
3836
            if (Database::num_rows($result)) {
3837
                $firstAnswer = Database::fetch_array($result);
3838
            }
3839
        }
3840
3841
        $real_answers = [];
3842
        $quiz_question_options = Question::readQuestionOption($questionId, $course_id);
3843
        $organs_at_risk_hit = 0;
3844
        $questionScore = 0;
3845
        $orderedHotSpots = [];
3846
        if ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
3847
            $orderedHotSpots = $em->getRepository('ChamiloCoreBundle:TrackEHotspot')->findBy(
3848
                [
3849
                    'hotspotQuestionId' => $questionId,
3850
                    'cId' => $course_id,
3851
                    'hotspotExeId' => $exeId,
3852
                ],
3853
                ['hotspotAnswerId' => 'ASC']
3854
            );
3855
        }
3856
3857
        if ($debug) {
3858
            error_log('-- Start answer loop --');
3859
        }
3860
3861
        $answerDestination = null;
3862
        $userAnsweredQuestion = false;
3863
        $correctAnswerId = [];
3864
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3865
            $answer = $objAnswerTmp->selectAnswer($answerId);
3866
            $answerComment = $objAnswerTmp->selectComment($answerId);
3867
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3868
            $answerWeighting = (float) $objAnswerTmp->selectWeighting($answerId);
3869
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3870
            $answerIid = isset($objAnswerTmp->iid[$answerId]) ? (int) $objAnswerTmp->iid[$answerId] : 0;
3871
3872
            if ($debug) {
3873
                error_log("c_quiz_answer.id_auto: $answerAutoId ");
3874
                error_log("Answer marked as correct in db (0/1)?: $answerCorrect ");
3875
                error_log("answerWeighting: $answerWeighting");
3876
            }
3877
3878
            // Delineation.
3879
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3880
            $answer_delineation_destination = $objAnswerTmp->selectDestination(1);
3881
3882
            switch ($answerType) {
3883
                case UNIQUE_ANSWER:
3884
                case UNIQUE_ANSWER_IMAGE:
3885
                case UNIQUE_ANSWER_NO_OPTION:
3886
                case READING_COMPREHENSION:
3887
                    if ($from_database) {
3888
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3889
                                WHERE
3890
                                    exe_id = $exeId AND
3891
                                    question_id = $questionId";
3892
                        $result = Database::query($sql);
3893
                        $choice = Database::result($result, 0, 'answer');
3894
3895
                        if (false === $userAnsweredQuestion) {
3896
                            $userAnsweredQuestion = !empty($choice);
3897
                        }
3898
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3899
                        if ($studentChoice) {
3900
                            $questionScore += $answerWeighting;
3901
                            $answerDestination = $objAnswerTmp->selectDestination($answerId);
3902
                            $correctAnswerId[] = $answerId;
3903
                        }
3904
                    } else {
3905
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3906
                        if ($studentChoice) {
3907
                            $questionScore += $answerWeighting;
3908
                            $answerDestination = $objAnswerTmp->selectDestination($answerId);
3909
                            $correctAnswerId[] = $answerId;
3910
                        }
3911
                    }
3912
                    break;
3913
                case MULTIPLE_ANSWER_TRUE_FALSE:
3914
                    if ($from_database) {
3915
                        $choice = [];
3916
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3917
                                WHERE
3918
                                    exe_id = $exeId AND
3919
                                    question_id = ".$questionId;
3920
3921
                        $result = Database::query($sql);
3922
                        while ($row = Database::fetch_array($result)) {
3923
                            $values = explode(':', $row['answer']);
3924
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3925
                            $option = isset($values[1]) ? $values[1] : '';
3926
                            $choice[$my_answer_id] = $option;
3927
                        }
3928
                        $userAnsweredQuestion = !empty($choice);
3929
                    }
3930
3931
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3932
                    if (!empty($studentChoice)) {
3933
                        $correctAnswerId[] = $answerAutoId;
3934
                        if ($studentChoice == $answerCorrect) {
3935
                            $questionScore += $true_score;
3936
                        } else {
3937
                            if ($quiz_question_options[$studentChoice]['name'] === "Don't know" ||
3938
                                $quiz_question_options[$studentChoice]['name'] === "DoubtScore"
3939
                            ) {
3940
                                $questionScore += $doubt_score;
3941
                            } else {
3942
                                $questionScore += $false_score;
3943
                            }
3944
                        }
3945
                    } else {
3946
                        // If no result then the user just hit don't know
3947
                        $studentChoice = 3;
3948
                        $questionScore += $doubt_score;
3949
                    }
3950
                    $totalScore = $questionScore;
3951
                    break;
3952
                case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
3953
                    if ($from_database) {
3954
                        $choice = [];
3955
                        $choiceDegreeCertainty = [];
3956
                        $sql = "SELECT answer
3957
                            FROM $TBL_TRACK_ATTEMPT
3958
                            WHERE
3959
                            exe_id = $exeId AND question_id = $questionId";
3960
3961
                        $result = Database::query($sql);
3962
                        while ($row = Database::fetch_array($result)) {
3963
                            $ind = $row['answer'];
3964
                            $values = explode(':', $ind);
3965
                            $myAnswerId = $values[0];
3966
                            $option = $values[1];
3967
                            $percent = $values[2];
3968
                            $choice[$myAnswerId] = $option;
3969
                            $choiceDegreeCertainty[$myAnswerId] = $percent;
3970
                        }
3971
                    }
3972
3973
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3974
                    $studentChoiceDegree = isset($choiceDegreeCertainty[$answerAutoId]) ? $choiceDegreeCertainty[$answerAutoId] : null;
3975
3976
                    // student score update
3977
                    if (!empty($studentChoice)) {
3978
                        if ($studentChoice == $answerCorrect) {
3979
                            // correct answer and student is Unsure or PrettySur
3980
                            if (isset($quiz_question_options[$studentChoiceDegree]) &&
3981
                                $quiz_question_options[$studentChoiceDegree]['position'] >= 3 &&
3982
                                $quiz_question_options[$studentChoiceDegree]['position'] < 9
3983
                            ) {
3984
                                $questionScore += $true_score;
3985
                            } else {
3986
                                // student ignore correct answer
3987
                                $questionScore += $doubt_score;
3988
                            }
3989
                        } else {
3990
                            // false answer and student is Unsure or PrettySur
3991
                            if ($quiz_question_options[$studentChoiceDegree]['position'] >= 3
3992
                                && $quiz_question_options[$studentChoiceDegree]['position'] < 9) {
3993
                                $questionScore += $false_score;
3994
                            } else {
3995
                                // student ignore correct answer
3996
                                $questionScore += $doubt_score;
3997
                            }
3998
                        }
3999
                    }
4000
                    $totalScore = $questionScore;
4001
                    break;
4002
                case MULTIPLE_ANSWER:
4003
                    if ($from_database) {
4004
                        $choice = [];
4005
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4006
                                WHERE exe_id = $exeId AND question_id = $questionId ";
4007
                        $resultans = Database::query($sql);
4008
                        while ($row = Database::fetch_array($resultans)) {
4009
                            $choice[$row['answer']] = 1;
4010
                        }
4011
4012
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
4013
                        $real_answers[$answerId] = (bool) $studentChoice;
4014
4015
                        if ($studentChoice) {
4016
                            $questionScore += $answerWeighting;
4017
                        }
4018
                    } else {
4019
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
4020
                        $real_answers[$answerId] = (bool) $studentChoice;
4021
4022
                        if (isset($studentChoice)) {
4023
                            $correctAnswerId[] = $answerAutoId;
4024
                            $questionScore += $answerWeighting;
4025
                        }
4026
                    }
4027
                    $totalScore += $answerWeighting;
4028
                    break;
4029
                case GLOBAL_MULTIPLE_ANSWER:
4030
                    if ($from_database) {
4031
                        $choice = [];
4032
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4033
                                WHERE exe_id = $exeId AND question_id = $questionId ";
4034
                        $resultans = Database::query($sql);
4035
                        while ($row = Database::fetch_array($resultans)) {
4036
                            $choice[$row['answer']] = 1;
4037
                        }
4038
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
4039
                        $real_answers[$answerId] = (bool) $studentChoice;
4040
                        if ($studentChoice) {
4041
                            $questionScore += $answerWeighting;
4042
                        }
4043
                    } else {
4044
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
4045
                        if (isset($studentChoice)) {
4046
                            $questionScore += $answerWeighting;
4047
                        }
4048
                        $real_answers[$answerId] = (bool) $studentChoice;
4049
                    }
4050
                    $totalScore += $answerWeighting;
4051
                    if ($debug) {
4052
                        error_log("studentChoice: $studentChoice");
4053
                    }
4054
                    break;
4055
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4056
                    if ($from_database) {
4057
                        $choice = [];
4058
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4059
                                WHERE exe_id = $exeId AND question_id = $questionId";
4060
                        $resultans = Database::query($sql);
4061
                        while ($row = Database::fetch_array($resultans)) {
4062
                            $result = explode(':', $row['answer']);
4063
                            if (isset($result[0])) {
4064
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
4065
                                $option = isset($result[1]) ? $result[1] : '';
4066
                                $choice[$my_answer_id] = $option;
4067
                            }
4068
                        }
4069
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
4070
                        $real_answers[$answerId] = false;
4071
                        if ($answerCorrect == $studentChoice) {
4072
                            $real_answers[$answerId] = true;
4073
                        }
4074
                    } else {
4075
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
4076
                        $real_answers[$answerId] = false;
4077
                        if ($answerCorrect == $studentChoice) {
4078
                            $real_answers[$answerId] = true;
4079
                        }
4080
                    }
4081
                    break;
4082
                case MULTIPLE_ANSWER_COMBINATION:
4083
                    if ($from_database) {
4084
                        $choice = [];
4085
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4086
                                WHERE exe_id = $exeId AND question_id = $questionId";
4087
                        $resultans = Database::query($sql);
4088
                        while ($row = Database::fetch_array($resultans)) {
4089
                            $choice[$row['answer']] = 1;
4090
                        }
4091
4092
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
4093
                        if (1 == $answerCorrect) {
4094
                            $real_answers[$answerId] = false;
4095
                            if ($studentChoice) {
4096
                                $real_answers[$answerId] = true;
4097
                            }
4098
                        } else {
4099
                            $real_answers[$answerId] = true;
4100
                            if ($studentChoice) {
4101
                                $real_answers[$answerId] = false;
4102
                            }
4103
                        }
4104
                    } else {
4105
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
4106
                        if (1 == $answerCorrect) {
4107
                            $real_answers[$answerId] = false;
4108
                            if ($studentChoice) {
4109
                                $real_answers[$answerId] = true;
4110
                            }
4111
                        } else {
4112
                            $real_answers[$answerId] = true;
4113
                            if ($studentChoice) {
4114
                                $real_answers[$answerId] = false;
4115
                            }
4116
                        }
4117
                    }
4118
                    break;
4119
                case FILL_IN_BLANKS:
4120
                    $str = '';
4121
                    $answerFromDatabase = '';
4122
                    if ($from_database) {
4123
                        $sql = "SELECT answer
4124
                                FROM $TBL_TRACK_ATTEMPT
4125
                                WHERE
4126
                                    exe_id = $exeId AND
4127
                                    question_id= $questionId ";
4128
                        $result = Database::query($sql);
4129
                        $str = $answerFromDatabase = Database::result($result, 0, 'answer');
4130
                    }
4131
4132
                    // if ($save_results == false && strpos($answerFromDatabase, 'font color') !== false) {
4133
                    if (false) {
4134
                        // the question is encoded like this
4135
                        // [A] B [C] D [E] F::10,10,10@1
4136
                        // number 1 before the "@" means that is a switchable fill in blank question
4137
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
4138
                        // means that is a normal fill blank question
4139
                        // first we explode the "::"
4140
                        $pre_array = explode('::', $answer);
4141
4142
                        // is switchable fill blank or not
4143
                        $last = count($pre_array) - 1;
4144
                        $is_set_switchable = explode('@', $pre_array[$last]);
4145
                        $switchable_answer_set = false;
4146
                        if (isset($is_set_switchable[1]) && 1 == $is_set_switchable[1]) {
4147
                            $switchable_answer_set = true;
4148
                        }
4149
                        $answer = '';
4150
                        for ($k = 0; $k < $last; $k++) {
4151
                            $answer .= $pre_array[$k];
4152
                        }
4153
                        // splits weightings that are joined with a comma
4154
                        $answerWeighting = explode(',', $is_set_switchable[0]);
4155
                        // we save the answer because it will be modified
4156
                        $temp = $answer;
4157
                        $answer = '';
4158
                        $j = 0;
4159
                        //initialise answer tags
4160
                        $user_tags = $correct_tags = $real_text = [];
4161
                        // the loop will stop at the end of the text
4162
                        while (1) {
4163
                            // quits the loop if there are no more blanks (detect '[')
4164
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
4165
                                // adds the end of the text
4166
                                $answer = $temp;
4167
                                $real_text[] = $answer;
4168
                                break; //no more "blanks", quit the loop
4169
                            }
4170
                            // adds the piece of text that is before the blank
4171
                            //and ends with '[' into a general storage array
4172
                            $real_text[] = api_substr($temp, 0, $pos + 1);
4173
                            $answer .= api_substr($temp, 0, $pos + 1);
4174
                            //take the string remaining (after the last "[" we found)
4175
                            $temp = api_substr($temp, $pos + 1);
4176
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
4177
                            if (($pos = api_strpos($temp, ']')) === false) {
4178
                                // adds the end of the text
4179
                                $answer .= $temp;
4180
                                break;
4181
                            }
4182
                            if ($from_database) {
4183
                                $str = $answerFromDatabase;
4184
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
4185
                                $str = str_replace('\r\n', '', $str);
4186
4187
                                $choice = $arr[1];
4188
                                if (isset($choice[$j])) {
4189
                                    $tmp = api_strrpos($choice[$j], ' / ');
4190
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
4191
                                    $choice[$j] = trim($choice[$j]);
4192
                                    // Needed to let characters ' and " to work as part of an answer
4193
                                    $choice[$j] = stripslashes($choice[$j]);
4194
                                } else {
4195
                                    $choice[$j] = null;
4196
                                }
4197
                            } else {
4198
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
4199
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
4200
                            }
4201
4202
                            $user_tags[] = $choice[$j];
4203
                            // Put the contents of the [] answer tag into correct_tags[]
4204
                            $correct_tags[] = api_substr($temp, 0, $pos);
4205
                            $j++;
4206
                            $temp = api_substr($temp, $pos + 1);
4207
                        }
4208
                        $answer = '';
4209
                        $real_correct_tags = $correct_tags;
4210
                        $chosen_list = [];
4211
4212
                        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...
4213
                            if (0 == $i) {
4214
                                $answer .= $real_text[0];
4215
                            }
4216
                            if (!$switchable_answer_set) {
4217
                                // Needed to parse ' and " characters
4218
                                $user_tags[$i] = stripslashes($user_tags[$i]);
4219
                                if ($correct_tags[$i] == $user_tags[$i]) {
4220
                                    // gives the related weighting to the student
4221
                                    $questionScore += $answerWeighting[$i];
4222
                                    // increments total score
4223
                                    $totalScore += $answerWeighting[$i];
4224
                                    // adds the word in green at the end of the string
4225
                                    $answer .= $correct_tags[$i];
4226
                                } elseif (!empty($user_tags[$i])) {
4227
                                    // else if the word entered by the student IS NOT the same as
4228
                                    // the one defined by the professor
4229
                                    // adds the word in red at the end of the string, and strikes it
4230
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
4231
                                } else {
4232
                                    // adds a tabulation if no word has been typed by the student
4233
                                    $answer .= ''; // remove &nbsp; that causes issue
4234
                                }
4235
                            } else {
4236
                                // switchable fill in the blanks
4237
                                if (in_array($user_tags[$i], $correct_tags)) {
4238
                                    $chosen_list[] = $user_tags[$i];
4239
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
4240
                                    // gives the related weighting to the student
4241
                                    $questionScore += $answerWeighting[$i];
4242
                                    // increments total score
4243
                                    $totalScore += $answerWeighting[$i];
4244
                                    // adds the word in green at the end of the string
4245
                                    $answer .= $user_tags[$i];
4246
                                } elseif (!empty($user_tags[$i])) {
4247
                                    // else if the word entered by the student IS NOT the same
4248
                                    // as the one defined by the professor
4249
                                    // adds the word in red at the end of the string, and strikes it
4250
                                    $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>';
4251
                                } else {
4252
                                    // adds a tabulation if no word has been typed by the student
4253
                                    $answer .= ''; // remove &nbsp; that causes issue
4254
                                }
4255
                            }
4256
4257
                            // adds the correct word, followed by ] to close the blank
4258
                            $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]';
4259
                            if (isset($real_text[$i + 1])) {
4260
                                $answer .= $real_text[$i + 1];
4261
                            }
4262
                        }
4263
                    } else {
4264
                        // insert the student result in the track_e_attempt table, field answer
4265
                        // $answer is the answer like in the c_quiz_answer table for the question
4266
                        // student data are choice[]
4267
                        $listCorrectAnswers = FillBlanks::getAnswerInfo($answer);
4268
                        $switchableAnswerSet = $listCorrectAnswers['switchable'];
4269
                        $answerWeighting = $listCorrectAnswers['weighting'];
4270
                        // user choices is an array $choice
4271
4272
                        // get existing user data in n the BDD
4273
                        if ($from_database) {
4274
                            $listStudentResults = FillBlanks::getAnswerInfo(
4275
                                $answerFromDatabase,
4276
                                true
4277
                            );
4278
                            $choice = $listStudentResults['student_answer'];
4279
                        }
4280
4281
                        // loop other all blanks words
4282
                        if (!$switchableAnswerSet) {
4283
                            // not switchable answer, must be in the same place than teacher order
4284
                            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...
4285
                                $studentAnswer = isset($choice[$i]) ? $choice[$i] : '';
4286
                                $correctAnswer = $listCorrectAnswers['words'][$i];
4287
4288
                                if ($debug) {
4289
                                    error_log("Student answer: $i");
4290
                                    error_log($studentAnswer);
4291
                                }
4292
4293
                                // This value is the user input, not escaped while correct answer is escaped by ckeditor
4294
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
4295
                                // ENT_QUOTES is used in order to transform ' to &#039;
4296
                                if (!$from_database) {
4297
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
4298
                                    if ($debug) {
4299
                                        error_log('Student answer cleaned:');
4300
                                        error_log($studentAnswer);
4301
                                    }
4302
                                }
4303
4304
                                $isAnswerCorrect = 0;
4305
                                if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database)) {
4306
                                    // gives the related weighting to the student
4307
                                    $questionScore += $answerWeighting[$i];
4308
                                    // increments total score
4309
                                    $totalScore += $answerWeighting[$i];
4310
                                    $isAnswerCorrect = 1;
4311
                                }
4312
                                if ($debug) {
4313
                                    error_log("isAnswerCorrect $i: $isAnswerCorrect");
4314
                                }
4315
4316
                                $studentAnswerToShow = $studentAnswer;
4317
                                $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
4318
                                if ($debug) {
4319
                                    error_log("Fill in blank type: $type");
4320
                                }
4321
                                if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
4322
                                    $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
4323
                                    if ($studentAnswer != '') {
4324
                                        foreach ($listMenu as $item) {
4325
                                            if (sha1($item) == $studentAnswer) {
4326
                                                $studentAnswerToShow = $item;
4327
                                            }
4328
                                        }
4329
                                    }
4330
                                }
4331
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
4332
                                $listCorrectAnswers['student_score'][$i] = $isAnswerCorrect;
4333
                            }
4334
                        } else {
4335
                            // switchable answer
4336
                            $listStudentAnswerTemp = $choice;
4337
                            $listTeacherAnswerTemp = $listCorrectAnswers['words'];
4338
4339
                            // for every teacher answer, check if there is a student answer
4340
                            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...
4341
                                $studentAnswer = trim($listStudentAnswerTemp[$i]);
4342
                                $studentAnswerToShow = $studentAnswer;
4343
4344
                                if (empty($studentAnswer)) {
4345
                                    break;
4346
                                }
4347
4348
                                if ($debug) {
4349
                                    error_log("Student answer: $i");
4350
                                    error_log($studentAnswer);
4351
                                }
4352
4353
                                if (!$from_database) {
4354
                                    $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer);
4355
                                    if ($debug) {
4356
                                        error_log("Student answer cleaned:");
4357
                                        error_log($studentAnswer);
4358
                                    }
4359
                                }
4360
4361
                                $found = false;
4362
                                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...
4363
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
4364
4365
                                    if (!$found) {
4366
                                        if (FillBlanks::isStudentAnswerGood(
4367
                                            $studentAnswer,
4368
                                            $correctAnswer,
4369
                                            $from_database
4370
                                        )) {
4371
                                            $questionScore += $answerWeighting[$i];
4372
                                            $totalScore += $answerWeighting[$i];
4373
                                            $listTeacherAnswerTemp[$j] = '';
4374
                                            $found = true;
4375
                                        }
4376
                                    }
4377
4378
                                    $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer);
4379
                                    if ($type == FillBlanks::FILL_THE_BLANK_MENU) {
4380
                                        $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false);
4381
                                        if (!empty($studentAnswer)) {
4382
                                            foreach ($listMenu as $key => $item) {
4383
                                                if ($key == $correctAnswer) {
4384
                                                    $studentAnswerToShow = $item;
4385
                                                    break;
4386
                                                }
4387
                                            }
4388
                                        }
4389
                                    }
4390
                                }
4391
                                $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow;
4392
                                $listCorrectAnswers['student_score'][$i] = $found ? 1 : 0;
4393
                            }
4394
                        }
4395
                        $answer = FillBlanks::getAnswerInStudentAttempt($listCorrectAnswers);
4396
                    }
4397
                    break;
4398
                case CALCULATED_ANSWER:
4399
                    $calculatedAnswerList = Session::read('calculatedAnswerId');
4400
                    if (!empty($calculatedAnswerList)) {
4401
                        $answer = $objAnswerTmp->selectAnswer($calculatedAnswerList[$questionId]);
4402
                        $preArray = explode('@@', $answer);
4403
                        $last = count($preArray) - 1;
4404
                        $answer = '';
4405
                        for ($k = 0; $k < $last; $k++) {
4406
                            $answer .= $preArray[$k];
4407
                        }
4408
                        $answerWeighting = [$answerWeighting];
4409
                        // we save the answer because it will be modified
4410
                        $temp = $answer;
4411
                        $answer = '';
4412
                        $j = 0;
4413
                        // initialise answer tags
4414
                        $userTags = $correctTags = $realText = [];
4415
                        // the loop will stop at the end of the text
4416
                        while (1) {
4417
                            // quits the loop if there are no more blanks (detect '[')
4418
                            if ($temp == false || ($pos = api_strpos($temp, '[')) === false) {
4419
                                // adds the end of the text
4420
                                $answer = $temp;
4421
                                $realText[] = $answer;
4422
                                break; //no more "blanks", quit the loop
4423
                            }
4424
                            // adds the piece of text that is before the blank
4425
                            // and ends with '[' into a general storage array
4426
                            $realText[] = api_substr($temp, 0, $pos + 1);
4427
                            $answer .= api_substr($temp, 0, $pos + 1);
4428
                            // take the string remaining (after the last "[" we found)
4429
                            $temp = api_substr($temp, $pos + 1);
4430
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
4431
                            if (false === ($pos = api_strpos($temp, ']'))) {
4432
                                // adds the end of the text
4433
                                $answer .= $temp;
4434
                                break;
4435
                            }
4436
4437
                            if ($from_database) {
4438
                                $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4439
                                        WHERE
4440
                                            exe_id = $exeId AND
4441
                                            question_id = $questionId ";
4442
                                $result = Database::query($sql);
4443
                                $str = Database::result($result, 0, 'answer');
4444
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
4445
                                $str = str_replace('\r\n', '', $str);
4446
                                $choice = $arr[1];
4447
                                if (isset($choice[$j])) {
4448
                                    $tmp = api_strrpos($choice[$j], ' / ');
4449
                                    if ($tmp) {
4450
                                        $choice[$j] = api_substr($choice[$j], 0, $tmp);
4451
                                    } else {
4452
                                        $tmp = ltrim($tmp, '[');
4453
                                        $tmp = rtrim($tmp, ']');
4454
                                    }
4455
                                    $choice[$j] = trim($choice[$j]);
4456
                                    // Needed to let characters ' and " to work as part of an answer
4457
                                    $choice[$j] = stripslashes($choice[$j]);
4458
                                } else {
4459
                                    $choice[$j] = null;
4460
                                }
4461
                            } else {
4462
                                // This value is the user input not escaped while correct answer is escaped by ckeditor
4463
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
4464
                            }
4465
                            $userTags[] = $choice[$j];
4466
                            // put the contents of the [] answer tag into correct_tags[]
4467
                            $correctTags[] = api_substr($temp, 0, $pos);
4468
                            $j++;
4469
                            $temp = api_substr($temp, $pos + 1);
4470
                        }
4471
                        $answer = '';
4472
                        $realCorrectTags = $correctTags;
4473
                        $calculatedStatus = Display::label(get_lang('Incorrect'), 'danger');
4474
                        $expectedAnswer = '';
4475
                        $calculatedChoice = '';
4476
4477
                        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...
4478
                            if (0 == $i) {
4479
                                $answer .= $realText[0];
4480
                            }
4481
                            // Needed to parse ' and " characters
4482
                            $userTags[$i] = stripslashes($userTags[$i]);
4483
                            if ($correctTags[$i] == $userTags[$i]) {
4484
                                // gives the related weighting to the student
4485
                                $questionScore += $answerWeighting[$i];
4486
                                // increments total score
4487
                                $totalScore += $answerWeighting[$i];
4488
                                // adds the word in green at the end of the string
4489
                                $answer .= $correctTags[$i];
4490
                                $calculatedChoice = $correctTags[$i];
4491
                            } elseif (!empty($userTags[$i])) {
4492
                                // else if the word entered by the student IS NOT the same as
4493
                                // the one defined by the professor
4494
                                // adds the word in red at the end of the string, and strikes it
4495
                                $answer .= '<font color="red"><s>'.$userTags[$i].'</s></font>';
4496
                                $calculatedChoice = $userTags[$i];
4497
                            } else {
4498
                                // adds a tabulation if no word has been typed by the student
4499
                                $answer .= ''; // remove &nbsp; that causes issue
4500
                            }
4501
                            // adds the correct word, followed by ] to close the blank
4502
                            if (EXERCISE_FEEDBACK_TYPE_EXAM != $this->results_disabled) {
4503
                                $answer .= ' / <font color="green"><b>'.$realCorrectTags[$i].'</b></font>';
4504
                                $calculatedStatus = Display::label(get_lang('Correct'), 'success');
4505
                                $expectedAnswer = $realCorrectTags[$i];
4506
                            }
4507
                            $answer .= ']';
4508
                            if (isset($realText[$i + 1])) {
4509
                                $answer .= $realText[$i + 1];
4510
                            }
4511
                        }
4512
                    } else {
4513
                        if ($from_database) {
4514
                            $sql = "SELECT *
4515
                                    FROM $TBL_TRACK_ATTEMPT
4516
                                    WHERE
4517
                                        exe_id = $exeId AND
4518
                                        question_id = $questionId ";
4519
                            $result = Database::query($sql);
4520
                            $resultData = Database::fetch_array($result, 'ASSOC');
4521
                            $answer = $resultData['answer'];
4522
                            $questionScore = $resultData['marks'];
4523
                        }
4524
                    }
4525
                    break;
4526
                case FREE_ANSWER:
4527
                    if ($from_database) {
4528
                        $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT
4529
                                 WHERE
4530
                                    exe_id = $exeId AND
4531
                                    question_id= ".$questionId;
4532
                        $result = Database::query($sql);
4533
                        $data = Database::fetch_array($result);
4534
                        $choice = '';
4535
                        $questionScore = 0;
4536
                        if ($data) {
4537
                            $choice = $data['answer'];
4538
                            $questionScore = $data['marks'];
4539
                        }
4540
                        $choice = str_replace('\r\n', '', $choice);
4541
                        $choice = stripslashes($choice);
4542
                        if (-1 == $questionScore) {
4543
                            $totalScore += 0;
4544
                        } else {
4545
                            $totalScore += $questionScore;
4546
                        }
4547
                        if ('' == $questionScore) {
4548
                            $questionScore = 0;
4549
                        }
4550
                        $arrques = $questionName;
4551
                        $arrans = $choice;
4552
                    } else {
4553
                        $studentChoice = $choice;
4554
                        if ($studentChoice) {
4555
                            //Fixing negative puntation see #2193
4556
                            $questionScore = 0;
4557
                            $totalScore += 0;
4558
                        }
4559
                    }
4560
                    break;
4561
                case ORAL_EXPRESSION:
4562
                    if ($from_database) {
4563
                        $query = "SELECT answer, marks
4564
                                  FROM $TBL_TRACK_ATTEMPT
4565
                                  WHERE
4566
                                        exe_id = $exeId AND
4567
                                        question_id = $questionId
4568
                                 ";
4569
                        $resq = Database::query($query);
4570
                        $row = Database::fetch_assoc($resq);
4571
4572
                        $choice = [
4573
                            'answer' => '',
4574
                            'marks' => 0,
4575
                        ];
4576
                        $questionScore = 0;
4577
4578
                        if (is_array($row)) {
4579
                            $choice = $row['answer'];
4580
                            $choice = str_replace('\r\n', '', $choice);
4581
                            $choice = stripslashes($choice);
4582
                            $questionScore = $row['marks'];
4583
                        }
4584
4585
                        if ($questionScore == -1) {
4586
                            $totalScore += 0;
4587
                        } else {
4588
                            $totalScore += $questionScore;
4589
                        }
4590
                        $arrques = $questionName;
4591
                        $arrans = $choice;
4592
                    } else {
4593
                        $studentChoice = $choice;
4594
                        if ($studentChoice) {
4595
                            //Fixing negative puntation see #2193
4596
                            $questionScore = 0;
4597
                            $totalScore += 0;
4598
                        }
4599
                    }
4600
                    break;
4601
                case DRAGGABLE:
4602
                case MATCHING_DRAGGABLE:
4603
                case MATCHING:
4604
                    if ($from_database) {
4605
                        $sql = "SELECT id, answer, id_auto
4606
                                FROM $table_ans
4607
                                WHERE
4608
                                    c_id = $course_id AND
4609
                                    question_id = $questionId AND
4610
                                    correct = 0
4611
                                ";
4612
                        $result = Database::query($sql);
4613
                        // Getting the real answer
4614
                        $real_list = [];
4615
                        while ($realAnswer = Database::fetch_array($result)) {
4616
                            $real_list[$realAnswer['id_auto']] = $realAnswer['answer'];
4617
                        }
4618
4619
                        $orderBy = ' ORDER BY id_auto ';
4620
                        if (DRAGGABLE == $answerType) {
4621
                            $orderBy = ' ORDER BY correct ';
4622
                        }
4623
4624
                        $sql = "SELECT id, answer, correct, id_auto, ponderation
4625
                                FROM $table_ans
4626
                                WHERE
4627
                                    c_id = $course_id AND
4628
                                    question_id = $questionId AND
4629
                                    correct <> 0
4630
                                $orderBy";
4631
                        $result = Database::query($sql);
4632
                        $options = [];
4633
                        $correctAnswers = [];
4634
                        while ($row = Database::fetch_array($result, 'ASSOC')) {
4635
                            $options[] = $row;
4636
                            $correctAnswers[$row['correct']] = $row['answer'];
4637
                        }
4638
4639
                        $questionScore = 0;
4640
                        $counterAnswer = 1;
4641
                        foreach ($options as $a_answers) {
4642
                            $i_answer_id = $a_answers['id']; //3
4643
                            $s_answer_label = $a_answers['answer']; // your daddy - your mother
4644
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
4645
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
4646
4647
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
4648
                                    WHERE
4649
                                        exe_id = '$exeId' AND
4650
                                        question_id = '$questionId' AND
4651
                                        position = '$i_answer_id_auto'";
4652
                            $result = Database::query($sql);
4653
                            $s_user_answer = 0;
4654
                            if (Database::num_rows($result) > 0) {
4655
                                //  rich - good looking
4656
                                $s_user_answer = Database::result($result, 0, 0);
4657
                            }
4658
                            $i_answerWeighting = $a_answers['ponderation'];
4659
                            $user_answer = '';
4660
                            $status = Display::label(get_lang('Incorrect'), 'danger');
4661
4662
                            if (!empty($s_user_answer)) {
4663
                                if (DRAGGABLE == $answerType) {
4664
                                    if ($s_user_answer == $i_answer_correct_answer) {
4665
                                        $questionScore += $i_answerWeighting;
4666
                                        $totalScore += $i_answerWeighting;
4667
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
4668
                                        if ($this->showExpectedChoice() && !empty($i_answer_id_auto)) {
4669
                                            $user_answer = $answerMatching[$i_answer_id_auto];
4670
                                        }
4671
                                        $status = Display::label(get_lang('Correct'), 'success');
4672
                                    } else {
4673
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4674
                                        if ($this->showExpectedChoice() && !empty($s_user_answer)) {
4675
                                            /*$data = $options[$real_list[$s_user_answer] - 1];
4676
                                            $user_answer = $data['answer'];*/
4677
                                            $user_answer = $correctAnswers[$s_user_answer] ?? '';
4678
                                        }
4679
                                    }
4680
                                } else {
4681
                                    if ($s_user_answer == $i_answer_correct_answer) {
4682
                                        $questionScore += $i_answerWeighting;
4683
                                        $totalScore += $i_answerWeighting;
4684
                                        $status = Display::label(get_lang('Correct'), 'success');
4685
                                        // Try with id
4686
                                        if (isset($real_list[$i_answer_id])) {
4687
                                            $user_answer = Display::span(
4688
                                                $real_list[$i_answer_id],
4689
                                                ['style' => 'color: #008000; font-weight: bold;']
4690
                                            );
4691
                                        }
4692
4693
                                        // Try with $i_answer_id_auto
4694
                                        if (empty($user_answer)) {
4695
                                            if (isset($real_list[$i_answer_id_auto])) {
4696
                                                $user_answer = Display::span(
4697
                                                    $real_list[$i_answer_id_auto],
4698
                                                    ['style' => 'color: #008000; font-weight: bold;']
4699
                                                );
4700
                                            }
4701
                                        }
4702
4703
                                        if (isset($real_list[$i_answer_correct_answer])) {
4704
                                            $user_answer = Display::span(
4705
                                                $real_list[$i_answer_correct_answer],
4706
                                                ['style' => 'color: #008000; font-weight: bold;']
4707
                                            );
4708
                                        }
4709
                                    } else {
4710
                                        $user_answer = Display::span(
4711
                                            $real_list[$s_user_answer],
4712
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
4713
                                        );
4714
                                        if ($this->showExpectedChoice()) {
4715
                                            if (isset($real_list[$s_user_answer])) {
4716
                                                $user_answer = Display::span($real_list[$s_user_answer]);
4717
                                            }
4718
                                        }
4719
                                    }
4720
                                }
4721
                            } elseif (DRAGGABLE == $answerType) {
4722
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4723
                                if ($this->showExpectedChoice()) {
4724
                                    $user_answer = '';
4725
                                }
4726
                            } else {
4727
                                $user_answer = Display::span(
4728
                                    get_lang('Incorrect').' &nbsp;',
4729
                                    ['style' => 'color: #FF0000; text-decoration: line-through;']
4730
                                );
4731
                                if ($this->showExpectedChoice()) {
4732
                                    $user_answer = '';
4733
                                }
4734
                            }
4735
4736
                            if ($show_result) {
4737
                                if (false === $this->showExpectedChoice() &&
4738
                                    false === $showTotalScoreAndUserChoicesInLastAttempt
4739
                                ) {
4740
                                    $user_answer = '';
4741
                                }
4742
                                switch ($answerType) {
4743
                                    case MATCHING:
4744
                                    case MATCHING_DRAGGABLE:
4745
                                        if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK == $this->results_disabled) {
4746
                                            if (false === $showTotalScoreAndUserChoicesInLastAttempt && empty($s_user_answer)) {
4747
                                                break;
4748
                                            }
4749
                                        }
4750
4751
                                        echo '<tr>';
4752
                                        if (!in_array(
4753
                                            $this->results_disabled,
4754
                                            [
4755
                                                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
4756
                                            ]
4757
                                        )
4758
                                        ) {
4759
                                            echo '<td>'.$s_answer_label.'</td>';
4760
                                            echo '<td>'.$user_answer.'</td>';
4761
                                        } else {
4762
                                            echo '<td>'.$s_answer_label.'</td>';
4763
                                            $status = Display::label(get_lang('Correct'), 'success');
4764
                                        }
4765
4766
                                        if ($this->showExpectedChoice()) {
4767
                                            if ($this->showExpectedChoiceColumn()) {
4768
                                                echo '<td>';
4769
                                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4770
                                                    if (isset($real_list[$i_answer_correct_answer]) &&
4771
                                                        $showTotalScoreAndUserChoicesInLastAttempt == true
4772
                                                    ) {
4773
                                                        echo Display::span(
4774
                                                            $real_list[$i_answer_correct_answer]
4775
                                                        );
4776
                                                    }
4777
                                                }
4778
                                                echo '</td>';
4779
                                            }
4780
                                            echo '<td>'.$status.'</td>';
4781
                                        } else {
4782
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4783
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4784
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4785
                                                ) {
4786
                                                    if ($this->showExpectedChoiceColumn()) {
4787
                                                        echo '<td>';
4788
                                                        echo Display::span(
4789
                                                            $real_list[$i_answer_correct_answer],
4790
                                                            ['style' => 'color: #008000; font-weight: bold;']
4791
                                                        );
4792
                                                        echo '</td>';
4793
                                                    }
4794
                                                }
4795
                                            }
4796
                                        }
4797
                                        echo '</tr>';
4798
                                        break;
4799
                                    case DRAGGABLE:
4800
                                        if (false == $showTotalScoreAndUserChoicesInLastAttempt) {
4801
                                            $s_answer_label = '';
4802
                                        }
4803
                                        if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK == $this->results_disabled) {
4804
                                            if (false === $showTotalScoreAndUserChoicesInLastAttempt && empty($s_user_answer)) {
4805
                                                break;
4806
                                            }
4807
                                        }
4808
4809
                                        echo '<tr>';
4810
                                        if ($this->showExpectedChoice()) {
4811
                                            if (!in_array($this->results_disabled, [
4812
                                                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
4813
                                                //RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
4814
                                            ])
4815
                                            ) {
4816
                                                echo '<td>'.$user_answer.'</td>';
4817
                                            } else {
4818
                                                $status = Display::label(get_lang('Correct'), 'success');
4819
                                            }
4820
                                            echo '<td>'.$s_answer_label.'</td>';
4821
                                            echo '<td>'.$status.'</td>';
4822
                                        } else {
4823
                                            echo '<td>'.$s_answer_label.'</td>';
4824
                                            echo '<td>'.$user_answer.'</td>';
4825
                                            echo '<td>';
4826
                                            if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4827
                                                if (isset($real_list[$i_answer_correct_answer]) &&
4828
                                                    $showTotalScoreAndUserChoicesInLastAttempt === true
4829
                                                ) {
4830
                                                    echo Display::span(
4831
                                                        $real_list[$i_answer_correct_answer],
4832
                                                        ['style' => 'color: #008000; font-weight: bold;']
4833
                                                    );
4834
                                                }
4835
                                            }
4836
                                            echo '</td>';
4837
                                        }
4838
                                        echo '</tr>';
4839
                                        break;
4840
                                }
4841
                            }
4842
                            $counterAnswer++;
4843
                        }
4844
                        break 2; // break the switch and the "for" condition
4845
                    } else {
4846
                        if ($answerCorrect) {
4847
                            if (isset($choice[$answerAutoId]) &&
4848
                                $answerCorrect == $choice[$answerAutoId]
4849
                            ) {
4850
                                $correctAnswerId[] = $answerAutoId;
4851
                                $questionScore += $answerWeighting;
4852
                                $totalScore += $answerWeighting;
4853
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4854
                            } else {
4855
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4856
                                    $user_answer = Display::span(
4857
                                        $answerMatching[$choice[$answerAutoId]],
4858
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4859
                                    );
4860
                                }
4861
                            }
4862
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4863
                        }
4864
                    }
4865
                    break;
4866
                case HOT_SPOT:
4867
                    if ($from_database) {
4868
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4869
                        // Check auto id
4870
                        $foundAnswerId = $answerAutoId;
4871
                        $sql = "SELECT hotspot_correct
4872
                                FROM $TBL_TRACK_HOTSPOT
4873
                                WHERE
4874
                                    hotspot_exe_id = $exeId AND
4875
                                    hotspot_question_id= $questionId AND
4876
                                    hotspot_answer_id = $answerAutoId
4877
                                ORDER BY hotspot_id ASC";
4878
                        $result = Database::query($sql);
4879
                        if (Database::num_rows($result)) {
4880
                            $studentChoice = Database::result(
4881
                                $result,
4882
                                0,
4883
                                'hotspot_correct'
4884
                            );
4885
4886
                            if ($studentChoice) {
4887
                                $questionScore += $answerWeighting;
4888
                                $totalScore += $answerWeighting;
4889
                            }
4890
                        } else {
4891
                            // If answer.id is different:
4892
                            $sql = "SELECT hotspot_correct
4893
                                FROM $TBL_TRACK_HOTSPOT
4894
                                WHERE
4895
                                    hotspot_exe_id = $exeId AND
4896
                                    hotspot_question_id= $questionId AND
4897
                                    hotspot_answer_id = ".intval($answerId)."
4898
                                ORDER BY hotspot_id ASC";
4899
                            $result = Database::query($sql);
4900
                            $foundAnswerId = $answerId;
4901
                            if (Database::num_rows($result)) {
4902
                                $studentChoice = Database::result(
4903
                                    $result,
4904
                                    0,
4905
                                    'hotspot_correct'
4906
                                );
4907
4908
                                if ($studentChoice) {
4909
                                    $questionScore += $answerWeighting;
4910
                                    $totalScore += $answerWeighting;
4911
                                }
4912
                            } else {
4913
                                // check answer.iid
4914
                                if (!empty($answerIid)) {
4915
                                    $sql = "SELECT hotspot_correct
4916
                                            FROM $TBL_TRACK_HOTSPOT
4917
                                            WHERE
4918
                                                hotspot_exe_id = $exeId AND
4919
                                                hotspot_question_id= $questionId AND
4920
                                                hotspot_answer_id = $answerIid
4921
                                            ORDER BY hotspot_id ASC";
4922
                                    $result = Database::query($sql);
4923
                                    $foundAnswerId = $answerIid;
4924
                                    $studentChoice = Database::result(
4925
                                        $result,
4926
                                        0,
4927
                                        'hotspot_correct'
4928
                                    );
4929
4930
                                    if ($studentChoice) {
4931
                                        $questionScore += $answerWeighting;
4932
                                        $totalScore += $answerWeighting;
4933
                                    }
4934
                                }
4935
                            }
4936
                        }
4937
                    } else {
4938
                        if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) {
4939
                            $choice[$answerAutoId] = 0;
4940
                            $choice[$answerIid] = 0;
4941
                        } else {
4942
                            $studentChoice = $choice[$answerAutoId];
4943
                            if (empty($studentChoice)) {
4944
                                $studentChoice = $choice[$answerIid];
4945
                            }
4946
                            $choiceIsValid = false;
4947
                            if (!empty($studentChoice)) {
4948
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4949
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4950
                                $choicePoint = Geometry::decodePoint($studentChoice);
4951
4952
                                switch ($hotspotType) {
4953
                                    case 'square':
4954
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4955
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4956
                                        break;
4957
                                    case 'circle':
4958
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4959
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4960
                                        break;
4961
                                    case 'poly':
4962
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4963
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4964
                                        break;
4965
                                }
4966
                            }
4967
4968
                            $choice[$answerAutoId] = 0;
4969
                            if ($choiceIsValid) {
4970
                                $questionScore += $answerWeighting;
4971
                                $totalScore += $answerWeighting;
4972
                                $choice[$answerAutoId] = 1;
4973
                                $choice[$answerIid] = 1;
4974
                            }
4975
                        }
4976
                    }
4977
                    break;
4978
                case HOT_SPOT_ORDER:
4979
                    // @todo never added to chamilo
4980
                    // for hotspot with fixed order
4981
                    $studentChoice = $choice['order'][$answerId];
4982
                    if ($studentChoice == $answerId) {
4983
                        $questionScore += $answerWeighting;
4984
                        $totalScore += $answerWeighting;
4985
                        $studentChoice = true;
4986
                    } else {
4987
                        $studentChoice = false;
4988
                    }
4989
                    break;
4990
                case HOT_SPOT_DELINEATION:
4991
                    // for hotspot with delineation
4992
                    if ($from_database) {
4993
                        // getting the user answer
4994
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4995
                        $query = "SELECT hotspot_correct, hotspot_coordinate
4996
                                    FROM $TBL_TRACK_HOTSPOT
4997
                                    WHERE
4998
                                        hotspot_exe_id = $exeId AND
4999
                                        hotspot_question_id= $questionId AND
5000
                                        hotspot_answer_id = '1'";
5001
                        // By default we take 1 because it's a delineation
5002
                        $resq = Database::query($query);
5003
                        $row = Database::fetch_array($resq, 'ASSOC');
5004
5005
                        $choice = $row['hotspot_correct'];
5006
                        $user_answer = $row['hotspot_coordinate'];
5007
5008
                        // THIS is very important otherwise the poly_compile will throw an error!!
5009
                        // round-up the coordinates
5010
                        $coords = explode('/', $user_answer);
5011
                        $coords = array_filter($coords);
5012
                        $user_array = '';
5013
                        foreach ($coords as $coord) {
5014
                            [$x, $y] = explode(';', $coord);
5015
                            $user_array .= round($x).';'.round($y).'/';
5016
                        }
5017
                        $user_array = substr($user_array, 0, -1) ?: '';
5018
                    } else {
5019
                        if (!empty($studentChoice)) {
5020
                            $correctAnswerId[] = $answerAutoId;
5021
                            $newquestionList[] = $questionId;
5022
                        }
5023
5024
                        if (1 === $answerId) {
5025
                            $studentChoice = $choice[$answerId];
5026
                            $questionScore += $answerWeighting;
5027
                        }
5028
                        if (isset($_SESSION['exerciseResultCoordinates'][$questionId])) {
5029
                            $user_array = $_SESSION['exerciseResultCoordinates'][$questionId];
5030
                        }
5031
                    }
5032
                    $_SESSION['hotspot_coord'][$questionId][1] = $delineation_cord;
5033
                    $_SESSION['hotspot_dest'][$questionId][1] = $answer_delineation_destination;
5034
                    break;
5035
                case ANNOTATION:
5036
                    if ($from_database) {
5037
                        $sql = "SELECT answer, marks
5038
                                FROM $TBL_TRACK_ATTEMPT
5039
                                WHERE
5040
                                  exe_id = $exeId AND
5041
                                  question_id = $questionId ";
5042
                        $resq = Database::query($sql);
5043
                        $data = Database::fetch_array($resq);
5044
5045
                        $questionScore = empty($data['marks']) ? 0 : $data['marks'];
5046
                        $arrques = $questionName;
5047
                        break;
5048
                    }
5049
                    $studentChoice = $choice;
5050
                    if ($studentChoice) {
5051
                        $questionScore = 0;
5052
                    }
5053
                    break;
5054
            }
5055
5056
            if ($show_result) {
5057
                if ('exercise_result' === $from) {
5058
                    // Display answers (if not matching type, or if the answer is correct)
5059
                    if (!in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
5060
                        $answerCorrect
5061
                    ) {
5062
                        if (in_array(
5063
                            $answerType,
5064
                            [
5065
                                UNIQUE_ANSWER,
5066
                                UNIQUE_ANSWER_IMAGE,
5067
                                UNIQUE_ANSWER_NO_OPTION,
5068
                                MULTIPLE_ANSWER,
5069
                                MULTIPLE_ANSWER_COMBINATION,
5070
                                GLOBAL_MULTIPLE_ANSWER,
5071
                                READING_COMPREHENSION,
5072
                            ]
5073
                        )) {
5074
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
5075
                                $this,
5076
                                $feedback_type,
5077
                                $answerType,
5078
                                $studentChoice,
5079
                                $answer,
5080
                                $answerComment,
5081
                                $answerCorrect,
5082
                                0,
5083
                                0,
5084
                                0,
5085
                                $results_disabled,
5086
                                $showTotalScoreAndUserChoicesInLastAttempt,
5087
                                $this->export
5088
                            );
5089
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
5090
                            ExerciseShowFunctions::display_multiple_answer_true_false(
5091
                                $this,
5092
                                $feedback_type,
5093
                                $answerType,
5094
                                $studentChoice,
5095
                                $answer,
5096
                                $answerComment,
5097
                                $answerCorrect,
5098
                                0,
5099
                                $questionId,
5100
                                0,
5101
                                $results_disabled,
5102
                                $showTotalScoreAndUserChoicesInLastAttempt
5103
                            );
5104
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
5105
                            ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5106
                                $this,
5107
                                $feedback_type,
5108
                                $studentChoice,
5109
                                $studentChoiceDegree,
5110
                                $answer,
5111
                                $answerComment,
5112
                                $answerCorrect,
5113
                                $questionId,
5114
                                $results_disabled
5115
                            );
5116
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5117
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
5118
                                $this,
5119
                                $feedback_type,
5120
                                $answerType,
5121
                                $studentChoice,
5122
                                $answer,
5123
                                $answerComment,
5124
                                $answerCorrect,
5125
                                0,
5126
                                0,
5127
                                0,
5128
                                $results_disabled,
5129
                                $showTotalScoreAndUserChoicesInLastAttempt
5130
                            );
5131
                        } elseif ($answerType == FILL_IN_BLANKS) {
5132
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
5133
                                $this,
5134
                                $feedback_type,
5135
                                $answer,
5136
                                0,
5137
                                0,
5138
                                $results_disabled,
5139
                                '',
5140
                                $showTotalScoreAndUserChoicesInLastAttempt
5141
                            );
5142
                        } elseif ($answerType == CALCULATED_ANSWER) {
5143
                            ExerciseShowFunctions::display_calculated_answer(
5144
                                $this,
5145
                                $feedback_type,
5146
                                $answer,
5147
                                0,
5148
                                0,
5149
                                $results_disabled,
5150
                                $showTotalScoreAndUserChoicesInLastAttempt,
5151
                                $expectedAnswer,
5152
                                $calculatedChoice,
5153
                                $calculatedStatus
5154
                            );
5155
                        } elseif ($answerType == FREE_ANSWER) {
5156
                            ExerciseShowFunctions::display_free_answer(
5157
                                $feedback_type,
5158
                                $choice,
5159
                                $exeId,
5160
                                $questionId,
5161
                                $questionScore,
5162
                                $results_disabled
5163
                            );
5164
                        } elseif ($answerType == ORAL_EXPRESSION) {
5165
                            // to store the details of open questions in an array to be used in mail
5166
                            /** @var OralExpression $objQuestionTmp */
5167
                            ExerciseShowFunctions::display_oral_expression_answer(
5168
                                $feedback_type,
5169
                                $choice,
5170
                                0,
5171
                                0,
5172
                                $objQuestionTmp->getFileUrl(true),
5173
                                $results_disabled,
5174
                                $questionScore
5175
                            );
5176
                        } elseif ($answerType == HOT_SPOT) {
5177
                            $correctAnswerId = 0;
5178
                            /** @var TrackEHotspot $hotspot */
5179
                            foreach ($orderedHotSpots as $correctAnswerId => $hotspot) {
5180
                                if ($hotspot->getHotspotAnswerId() == $answerIid) {
5181
                                    break;
5182
                                }
5183
                            }
5184
5185
                            // force to show whether the choice is correct or not
5186
                            $showTotalScoreAndUserChoicesInLastAttempt = true;
5187
                            ExerciseShowFunctions::display_hotspot_answer(
5188
                                $this,
5189
                                $feedback_type,
5190
                                $answerId,
5191
                                $answer,
5192
                                $studentChoice,
5193
                                $answerComment,
5194
                                $results_disabled,
5195
                                $answerId,
5196
                                $showTotalScoreAndUserChoicesInLastAttempt
5197
                            );
5198
                        } elseif ($answerType == HOT_SPOT_ORDER) {
5199
                            ExerciseShowFunctions::display_hotspot_order_answer(
5200
                                $feedback_type,
5201
                                $answerId,
5202
                                $answer,
5203
                                $studentChoice,
5204
                                $answerComment
5205
                            );
5206
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
5207
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
5208
5209
                            // Round-up the coordinates
5210
                            $coords = explode('/', $user_answer);
5211
                            $coords = array_filter($coords);
5212
                            $user_array = '';
5213
                            foreach ($coords as $coord) {
5214
                                if (!empty($coord)) {
5215
                                    $parts = explode(';', $coord);
5216
                                    if (!empty($parts)) {
5217
                                        $user_array .= round($parts[0]).';'.round($parts[1]).'/';
5218
                                    }
5219
                                }
5220
                            }
5221
                            $user_array = substr($user_array, 0, -1) ?: '';
5222
                            if ($next) {
5223
                                $user_answer = $user_array;
5224
                                // We compare only the delineation not the other points
5225
                                $answer_question = $_SESSION['hotspot_coord'][$questionId][1];
5226
                                $answerDestination = $_SESSION['hotspot_dest'][$questionId][1];
5227
5228
                                // Calculating the area
5229
                                $poly_user = convert_coordinates($user_answer, '/');
5230
                                $poly_answer = convert_coordinates($answer_question, '|');
5231
                                $max_coord = poly_get_max($poly_user, $poly_answer);
5232
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
5233
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5234
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
5235
5236
                                $overlap = $poly_results['both'];
5237
                                $poly_answer_area = $poly_results['s1'];
5238
                                $poly_user_area = $poly_results['s2'];
5239
                                $missing = $poly_results['s1Only'];
5240
                                $excess = $poly_results['s2Only'];
5241
5242
                                // //this is an area in pixels
5243
                                if ($debug > 0) {
5244
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
5245
                                }
5246
5247
                                if ($overlap < 1) {
5248
                                    // Shortcut to avoid complicated calculations
5249
                                    $final_overlap = 0;
5250
                                    $final_missing = 100;
5251
                                    $final_excess = 100;
5252
                                } else {
5253
                                    // the final overlap is the percentage of the initial polygon
5254
                                    // that is overlapped by the user's polygon
5255
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
5256
                                    if ($debug > 1) {
5257
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap, 0);
5258
                                    }
5259
                                    // the final missing area is the percentage of the initial polygon
5260
                                    // that is not overlapped by the user's polygon
5261
                                    $final_missing = 100 - $final_overlap;
5262
                                    if ($debug > 1) {
5263
                                        error_log(__LINE__.' - Final missing is '.$final_missing, 0);
5264
                                    }
5265
                                    // the final excess area is the percentage of the initial polygon's size
5266
                                    // that is covered by the user's polygon outside of the initial polygon
5267
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
5268
                                    if ($debug > 1) {
5269
                                        error_log(__LINE__.' - Final excess is '.$final_excess, 0);
5270
                                    }
5271
                                }
5272
5273
                                // Checking the destination parameters parsing the "@@"
5274
                                $destination_items = explode('@@', $answerDestination);
5275
                                $threadhold_total = $destination_items[0];
5276
                                $threadhold_items = explode(';', $threadhold_total);
5277
                                $threadhold1 = $threadhold_items[0]; // overlap
5278
                                $threadhold2 = $threadhold_items[1]; // excess
5279
                                $threadhold3 = $threadhold_items[2]; // missing
5280
5281
                                // if is delineation
5282
                                if ($answerId === 1) {
5283
                                    //setting colors
5284
                                    if ($final_overlap >= $threadhold1) {
5285
                                        $overlap_color = true;
5286
                                    }
5287
                                    if ($final_excess <= $threadhold2) {
5288
                                        $excess_color = true;
5289
                                    }
5290
                                    if ($final_missing <= $threadhold3) {
5291
                                        $missing_color = true;
5292
                                    }
5293
5294
                                    // if pass
5295
                                    if ($final_overlap >= $threadhold1 &&
5296
                                        $final_missing <= $threadhold3 &&
5297
                                        $final_excess <= $threadhold2
5298
                                    ) {
5299
                                        $next = 1; //go to the oars
5300
                                        $result_comment = get_lang('Acceptable');
5301
                                        $final_answer = 1; // do not update with  update_exercise_attempt
5302
                                    } else {
5303
                                        $next = 0;
5304
                                        $result_comment = get_lang('Unacceptable');
5305
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
5306
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
5307
                                        // checking the destination parameters parsing the "@@"
5308
                                        $destination_items = explode('@@', $answerDestination);
5309
                                    }
5310
                                } elseif ($answerId > 1) {
5311
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
5312
                                        if ($debug > 0) {
5313
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
5314
                                        }
5315
                                        //type no error shouldn't be treated
5316
                                        $next = 1;
5317
                                        continue;
5318
                                    }
5319
                                    if ($debug > 0) {
5320
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
5321
                                    }
5322
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
5323
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
5324
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
5325
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5326
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
5327
5328
                                    if ($overlap == false) {
5329
                                        //all good, no overlap
5330
                                        $next = 1;
5331
                                        continue;
5332
                                    } else {
5333
                                        if ($debug > 0) {
5334
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
5335
                                        }
5336
                                        $organs_at_risk_hit++;
5337
                                        //show the feedback
5338
                                        $next = 0;
5339
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
5340
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
5341
5342
                                        $destination_items = explode('@@', $answerDestination);
5343
                                        $try_hotspot = $destination_items[1];
5344
                                        $lp_hotspot = $destination_items[2];
5345
                                        $select_question_hotspot = $destination_items[3];
5346
                                        $url_hotspot = $destination_items[4];
5347
                                    }
5348
                                }
5349
                            } else {
5350
                                // the first delineation feedback
5351
                                if ($debug > 0) {
5352
                                    error_log(__LINE__.' first', 0);
5353
                                }
5354
                            }
5355
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
5356
                            echo '<tr>';
5357
                            echo Display::tag('td', $answerMatching[$answerId]);
5358
                            echo Display::tag(
5359
                                'td',
5360
                                "$user_answer / ".Display::tag(
5361
                                    'strong',
5362
                                    $answerMatching[$answerCorrect],
5363
                                    ['style' => 'color: #008000; font-weight: bold;']
5364
                                )
5365
                            );
5366
                            echo '</tr>';
5367
                        } elseif ($answerType == ANNOTATION) {
5368
                            ExerciseShowFunctions::displayAnnotationAnswer(
5369
                                $feedback_type,
5370
                                $exeId,
5371
                                $questionId,
5372
                                $questionScore,
5373
                                $results_disabled
5374
                            );
5375
                        }
5376
                    }
5377
                } else {
5378
                    if ($debug) {
5379
                        error_log('Showing questions $from '.$from);
5380
                    }
5381
5382
                    switch ($answerType) {
5383
                        case UNIQUE_ANSWER:
5384
                        case UNIQUE_ANSWER_IMAGE:
5385
                        case UNIQUE_ANSWER_NO_OPTION:
5386
                        case MULTIPLE_ANSWER:
5387
                        case GLOBAL_MULTIPLE_ANSWER:
5388
                        case MULTIPLE_ANSWER_COMBINATION:
5389
                        case READING_COMPREHENSION:
5390
                            if ($answerId == 1) {
5391
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
5392
                                    $this,
5393
                                    $feedback_type,
5394
                                    $answerType,
5395
                                    $studentChoice,
5396
                                    $answer,
5397
                                    $answerComment,
5398
                                    $answerCorrect,
5399
                                    $exeId,
5400
                                    $questionId,
5401
                                    $answerId,
5402
                                    $results_disabled,
5403
                                    $showTotalScoreAndUserChoicesInLastAttempt,
5404
                                    $this->export
5405
                                );
5406
                            } else {
5407
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
5408
                                    $this,
5409
                                    $feedback_type,
5410
                                    $answerType,
5411
                                    $studentChoice,
5412
                                    $answer,
5413
                                    $answerComment,
5414
                                    $answerCorrect,
5415
                                    $exeId,
5416
                                    $questionId,
5417
                                    '',
5418
                                    $results_disabled,
5419
                                    $showTotalScoreAndUserChoicesInLastAttempt,
5420
                                    $this->export
5421
                                );
5422
                            }
5423
                            break;
5424
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
5425
                            if ($answerId == 1) {
5426
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
5427
                                    $this,
5428
                                    $feedback_type,
5429
                                    $answerType,
5430
                                    $studentChoice,
5431
                                    $answer,
5432
                                    $answerComment,
5433
                                    $answerCorrect,
5434
                                    $exeId,
5435
                                    $questionId,
5436
                                    $answerId,
5437
                                    $results_disabled,
5438
                                    $showTotalScoreAndUserChoicesInLastAttempt
5439
                                );
5440
                            } else {
5441
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
5442
                                    $this,
5443
                                    $feedback_type,
5444
                                    $answerType,
5445
                                    $studentChoice,
5446
                                    $answer,
5447
                                    $answerComment,
5448
                                    $answerCorrect,
5449
                                    $exeId,
5450
                                    $questionId,
5451
                                    '',
5452
                                    $results_disabled,
5453
                                    $showTotalScoreAndUserChoicesInLastAttempt
5454
                                );
5455
                            }
5456
                            break;
5457
                        case MULTIPLE_ANSWER_TRUE_FALSE:
5458
                            if ($answerId == 1) {
5459
                                ExerciseShowFunctions::display_multiple_answer_true_false(
5460
                                    $this,
5461
                                    $feedback_type,
5462
                                    $answerType,
5463
                                    $studentChoice,
5464
                                    $answer,
5465
                                    $answerComment,
5466
                                    $answerCorrect,
5467
                                    $exeId,
5468
                                    $questionId,
5469
                                    $answerId,
5470
                                    $results_disabled,
5471
                                    $showTotalScoreAndUserChoicesInLastAttempt
5472
                                );
5473
                            } else {
5474
                                ExerciseShowFunctions::display_multiple_answer_true_false(
5475
                                    $this,
5476
                                    $feedback_type,
5477
                                    $answerType,
5478
                                    $studentChoice,
5479
                                    $answer,
5480
                                    $answerComment,
5481
                                    $answerCorrect,
5482
                                    $exeId,
5483
                                    $questionId,
5484
                                    '',
5485
                                    $results_disabled,
5486
                                    $showTotalScoreAndUserChoicesInLastAttempt
5487
                                );
5488
                            }
5489
                            break;
5490
                        case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY:
5491
                            if ($answerId == 1) {
5492
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5493
                                    $this,
5494
                                    $feedback_type,
5495
                                    $studentChoice,
5496
                                    $studentChoiceDegree,
5497
                                    $answer,
5498
                                    $answerComment,
5499
                                    $answerCorrect,
5500
                                    $questionId,
5501
                                    $results_disabled
5502
                                );
5503
                            } else {
5504
                                ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty(
5505
                                    $this,
5506
                                    $feedback_type,
5507
                                    $studentChoice,
5508
                                    $studentChoiceDegree,
5509
                                    $answer,
5510
                                    $answerComment,
5511
                                    $answerCorrect,
5512
                                    $questionId,
5513
                                    $results_disabled
5514
                                );
5515
                            }
5516
                            break;
5517
                        case FILL_IN_BLANKS:
5518
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
5519
                                $this,
5520
                                $feedback_type,
5521
                                $answer,
5522
                                $exeId,
5523
                                $questionId,
5524
                                $results_disabled,
5525
                                $str,
5526
                                $showTotalScoreAndUserChoicesInLastAttempt
5527
                            );
5528
                            break;
5529
                        case CALCULATED_ANSWER:
5530
                            ExerciseShowFunctions::display_calculated_answer(
5531
                                $this,
5532
                                $feedback_type,
5533
                                $answer,
5534
                                $exeId,
5535
                                $questionId,
5536
                                $results_disabled,
5537
                                '',
5538
                                $showTotalScoreAndUserChoicesInLastAttempt
5539
                            );
5540
                            break;
5541
                        case FREE_ANSWER:
5542
                            echo ExerciseShowFunctions::display_free_answer(
5543
                                $feedback_type,
5544
                                $choice,
5545
                                $exeId,
5546
                                $questionId,
5547
                                $questionScore,
5548
                                $results_disabled
5549
                            );
5550
                            break;
5551
                        case ORAL_EXPRESSION:
5552
                            echo '<tr>
5553
                                <td valign="top">'.
5554
                                ExerciseShowFunctions::display_oral_expression_answer(
5555
                                    $feedback_type,
5556
                                    $choice,
5557
                                    $exeId,
5558
                                    $questionId,
5559
                                    $objQuestionTmp->getFileUrl(),
5560
                                    $results_disabled,
5561
                                    $questionScore
5562
                                ).'</td>
5563
                                </tr>
5564
                                </table>';
5565
                            break;
5566
                        case HOT_SPOT:
5567
                            $correctAnswerId = 0;
5568
                            /** @var TrackEHotspot $hotspot */
5569
                            foreach ($orderedHotSpots as $correctAnswerId => $hotspot) {
5570
                                if ($hotspot->getHotspotAnswerId() == $foundAnswerId) {
5571
                                    break;
5572
                                }
5573
                            }
5574
5575
                            ExerciseShowFunctions::display_hotspot_answer(
5576
                                $this,
5577
                                $feedback_type,
5578
                                $answerId,
5579
                                $answer,
5580
                                $studentChoice,
5581
                                $answerComment,
5582
                                $results_disabled,
5583
                                $answerId,
5584
                                $showTotalScoreAndUserChoicesInLastAttempt
5585
                            );
5586
                            break;
5587
                        case HOT_SPOT_DELINEATION:
5588
                            $user_answer = $user_array;
5589
                            if ($next) {
5590
                                $user_answer = $user_array;
5591
                                // we compare only the delineation not the other points
5592
                                $answer_question = $_SESSION['hotspot_coord'][$questionId][1];
5593
                                $answerDestination = $_SESSION['hotspot_dest'][$questionId][1];
5594
5595
                                // calculating the area
5596
                                $poly_user = convert_coordinates($user_answer, '/');
5597
                                $poly_answer = convert_coordinates($answer_question, '|');
5598
                                $max_coord = poly_get_max($poly_user, $poly_answer);
5599
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
5600
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5601
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
5602
5603
                                $overlap = $poly_results['both'];
5604
                                $poly_answer_area = $poly_results['s1'];
5605
                                $poly_user_area = $poly_results['s2'];
5606
                                $missing = $poly_results['s1Only'];
5607
                                $excess = $poly_results['s2Only'];
5608
                                if ($debug > 0) {
5609
                                    error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0);
5610
                                }
5611
                                if ($overlap < 1) {
5612
                                    //shortcut to avoid complicated calculations
5613
                                    $final_overlap = 0;
5614
                                    $final_missing = 100;
5615
                                    $final_excess = 100;
5616
                                } else {
5617
                                    // the final overlap is the percentage of the initial polygon
5618
                                    // that is overlapped by the user's polygon
5619
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
5620
5621
                                    // the final missing area is the percentage of the initial polygon that
5622
                                    // is not overlapped by the user's polygon
5623
                                    $final_missing = 100 - $final_overlap;
5624
                                    // the final excess area is the percentage of the initial polygon's size that is
5625
                                    // covered by the user's polygon outside of the initial polygon
5626
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
5627
5628
                                    if ($debug > 1) {
5629
                                        error_log(__LINE__.' - Final overlap is '.$final_overlap);
5630
                                        error_log(__LINE__.' - Final excess is '.$final_excess);
5631
                                        error_log(__LINE__.' - Final missing is '.$final_missing);
5632
                                    }
5633
                                }
5634
5635
                                // Checking the destination parameters parsing the "@@"
5636
                                $destination_items = explode('@@', $answerDestination);
5637
                                $threadhold_total = $destination_items[0];
5638
                                $threadhold_items = explode(';', $threadhold_total);
5639
                                $threadhold1 = $threadhold_items[0]; // overlap
5640
                                $threadhold2 = $threadhold_items[1]; // excess
5641
                                $threadhold3 = $threadhold_items[2]; //missing
5642
                                // if is delineation
5643
                                if ($answerId === 1) {
5644
                                    //setting colors
5645
                                    if ($final_overlap >= $threadhold1) {
5646
                                        $overlap_color = true;
5647
                                    }
5648
                                    if ($final_excess <= $threadhold2) {
5649
                                        $excess_color = true;
5650
                                    }
5651
                                    if ($final_missing <= $threadhold3) {
5652
                                        $missing_color = true;
5653
                                    }
5654
5655
                                    // if pass
5656
                                    if ($final_overlap >= $threadhold1 &&
5657
                                        $final_missing <= $threadhold3 &&
5658
                                        $final_excess <= $threadhold2
5659
                                    ) {
5660
                                        $next = 1; //go to the oars
5661
                                        $result_comment = get_lang('Acceptable');
5662
                                        $final_answer = 1; // do not update with  update_exercise_attempt
5663
                                    } else {
5664
                                        $next = 0;
5665
                                        $result_comment = get_lang('Unacceptable');
5666
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
5667
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
5668
                                        //checking the destination parameters parsing the "@@"
5669
                                        $destination_items = explode('@@', $answerDestination);
5670
                                    }
5671
                                } elseif ($answerId > 1) {
5672
                                    if ($objAnswerTmp->selectHotspotType($answerId) === 'noerror') {
5673
                                        if ($debug > 0) {
5674
                                            error_log(__LINE__.' - answerId is of type noerror', 0);
5675
                                        }
5676
                                        //type no error shouldn't be treated
5677
                                        $next = 1;
5678
                                        break;
5679
                                    }
5680
                                    if ($debug > 0) {
5681
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0);
5682
                                    }
5683
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
5684
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
5685
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
5686
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
5687
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord);
5688
5689
                                    if ($overlap == false) {
5690
                                        //all good, no overlap
5691
                                        $next = 1;
5692
                                        break;
5693
                                    } else {
5694
                                        if ($debug > 0) {
5695
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0);
5696
                                        }
5697
                                        $organs_at_risk_hit++;
5698
                                        //show the feedback
5699
                                        $next = 0;
5700
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
5701
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
5702
5703
                                        $destination_items = explode('@@', $answerDestination);
5704
                                        $try_hotspot = $destination_items[1];
5705
                                        $lp_hotspot = $destination_items[2];
5706
                                        $select_question_hotspot = $destination_items[3];
5707
                                        $url_hotspot = $destination_items[4];
5708
                                    }
5709
                                }
5710
                            }
5711
                            break;
5712
                        case HOT_SPOT_ORDER:
5713
                            ExerciseShowFunctions::display_hotspot_order_answer(
5714
                                $feedback_type,
5715
                                $answerId,
5716
                                $answer,
5717
                                $studentChoice,
5718
                                $answerComment
5719
                            );
5720
                            break;
5721
                        case DRAGGABLE:
5722
                        case MATCHING_DRAGGABLE:
5723
                        case MATCHING:
5724
                            echo '<tr>';
5725
                            echo Display::tag('td', $answerMatching[$answerId]);
5726
                            echo Display::tag(
5727
                                'td',
5728
                                "$user_answer / ".Display::tag(
5729
                                    'strong',
5730
                                    $answerMatching[$answerCorrect],
5731
                                    ['style' => 'color: #008000; font-weight: bold;']
5732
                                )
5733
                            );
5734
                            echo '</tr>';
5735
                            break;
5736
                        case ANNOTATION:
5737
                            ExerciseShowFunctions::displayAnnotationAnswer(
5738
                                $feedback_type,
5739
                                $exeId,
5740
                                $questionId,
5741
                                $questionScore,
5742
                                $results_disabled
5743
                            );
5744
                            break;
5745
                    }
5746
                }
5747
            }
5748
        } // end for that loops over all answers of the current question
5749
5750
        if ($debug) {
5751
            error_log('-- End answer loop --');
5752
        }
5753
5754
        $final_answer = true;
5755
5756
        foreach ($real_answers as $my_answer) {
5757
            if (!$my_answer) {
5758
                $final_answer = false;
5759
            }
5760
        }
5761
5762
        // We add the total score after dealing with the answers.
5763
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
5764
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
5765
        ) {
5766
            if ($final_answer) {
5767
                //getting only the first score where we save the weight of all the question
5768
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
5769
                if (empty($answerWeighting) && !empty($firstAnswer) && isset($firstAnswer['ponderation'])) {
5770
                    $answerWeighting = $firstAnswer['ponderation'];
5771
                }
5772
                $questionScore += $answerWeighting;
5773
            }
5774
        }
5775
5776
        $extra_data = [
5777
            'final_overlap' => $final_overlap,
5778
            'final_missing' => $final_missing,
5779
            'final_excess' => $final_excess,
5780
            'overlap_color' => $overlap_color,
5781
            'missing_color' => $missing_color,
5782
            'excess_color' => $excess_color,
5783
            'threadhold1' => $threadhold1,
5784
            'threadhold2' => $threadhold2,
5785
            'threadhold3' => $threadhold3,
5786
        ];
5787
5788
        if ($from === 'exercise_result') {
5789
            // if answer is hotspot. To the difference of exercise_show.php,
5790
            //  we use the results from the session (from_db=0)
5791
            // TODO Change this, because it is wrong to show the user
5792
            //  some results that haven't been stored in the database yet
5793
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION) {
5794
                if ($debug) {
5795
                    error_log('$from AND this is a hotspot kind of question ');
5796
                }
5797
                if ($answerType === HOT_SPOT_DELINEATION) {
5798
                    if ($showHotSpotDelineationTable) {
5799
                        if (!is_numeric($final_overlap)) {
5800
                            $final_overlap = 0;
5801
                        }
5802
                        if (!is_numeric($final_missing)) {
5803
                            $final_missing = 0;
5804
                        }
5805
                        if (!is_numeric($final_excess)) {
5806
                            $final_excess = 0;
5807
                        }
5808
5809
                        if ($final_overlap > 100) {
5810
                            $final_overlap = 100;
5811
                        }
5812
5813
                        $table_resume = '<table class="table table-hover table-striped data_table">
5814
                                <tr class="row_odd" >
5815
                                    <td></td>
5816
                                    <td ><b>'.get_lang('Requirements').'</b></td>
5817
                                    <td><b>'.get_lang('YourAnswer').'</b></td>
5818
                                </tr>
5819
                                <tr class="row_even">
5820
                                    <td><b>'.get_lang('Overlap').'</b></td>
5821
                                    <td>'.get_lang('Min').' '.$threadhold1.'</td>
5822
                                    <td class="text-right '.($overlap_color ? 'text-success' : 'text-danger').'">'
5823
                            .(($final_overlap < 0) ? 0 : intval($final_overlap)).'</td>
5824
                                </tr>
5825
                                <tr>
5826
                                    <td><b>'.get_lang('Excess').'</b></td>
5827
                                    <td>'.get_lang('Max').' '.$threadhold2.'</td>
5828
                                    <td class="text-right '.($excess_color ? 'text-success' : 'text-danger').'">'
5829
                            .(($final_excess < 0) ? 0 : intval($final_excess)).'</td>
5830
                                </tr>
5831
                                <tr class="row_even">
5832
                                    <td><b>'.get_lang('Missing').'</b></td>
5833
                                    <td>'.get_lang('Max').' '.$threadhold3.'</td>
5834
                                    <td class="text-right '.($missing_color ? 'text-success' : 'text-danger').'">'
5835
                            .(($final_missing < 0) ? 0 : intval($final_missing)).'</td>
5836
                                </tr>
5837
                            </table>';
5838
                        if ($next == 0) {
5839
                        } else {
5840
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
5841
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
5842
                        }
5843
5844
                        $message = '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>
5845
                                    <p style="text-align:center">';
5846
                        $message .= '<p>'.get_lang('YourDelineation').'</p>';
5847
                        $message .= $table_resume;
5848
                        $message .= '<br />'.get_lang('ResultIs').' '.$result_comment.'<br />';
5849
                        if ($organs_at_risk_hit > 0) {
5850
                            $message .= '<p><b>'.get_lang('OARHit').'</b></p>';
5851
                        }
5852
                        $message .= '<p>'.$comment.'</p>';
5853
                        echo $message;
5854
5855
                        $_SESSION['hotspot_delineation_result'][$this->selectId()][$questionId][0] = $message;
5856
                        $_SESSION['hotspot_delineation_result'][$this->selectId()][$questionId][1] = $_SESSION['exerciseResultCoordinates'][$questionId];
5857
                    } else {
5858
                        echo $hotspot_delineation_result[0];
5859
                    }
5860
5861
                    // Save the score attempts
5862
                    if (1) {
5863
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
5864
                        $final_answer = isset($hotspot_delineation_result[1]) ? $hotspot_delineation_result[1] : '';
5865
                        if ($final_answer == 0) {
5866
                            $questionScore = 0;
5867
                        }
5868
                        // we always insert the answer_id 1 = delineation
5869
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
5870
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
5871
                        $hotspotValue = isset($hotspot_delineation_result[1]) ? (int) $hotspot_delineation_result[1] === 1 ? 1 : 0 : 0;
5872
                        Event::saveExerciseAttemptHotspot(
5873
                            $exeId,
5874
                            $quesId,
5875
                            1,
5876
                            $hotspotValue,
5877
                            $exerciseResultCoordinates[$quesId],
5878
                            false,
5879
                            0,
5880
                            $learnpath_id,
5881
                            $learnpath_item_id
5882
                        );
5883
                    } else {
5884
                        if ($final_answer == 0) {
5885
                            $questionScore = 0;
5886
                            $answer = 0;
5887
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5888
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5889
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5890
                                    Event::saveExerciseAttemptHotspot(
5891
                                        $exeId,
5892
                                        $quesId,
5893
                                        $idx,
5894
                                        0,
5895
                                        $val,
5896
                                        false,
5897
                                        0,
5898
                                        $learnpath_id,
5899
                                        $learnpath_item_id
5900
                                    );
5901
                                }
5902
                            }
5903
                        } else {
5904
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
5905
                            if (is_array($exerciseResultCoordinates[$quesId])) {
5906
                                foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) {
5907
                                    $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
5908
                                    Event::saveExerciseAttemptHotspot(
5909
                                        $exeId,
5910
                                        $quesId,
5911
                                        $idx,
5912
                                        $hotspotValue,
5913
                                        $val,
5914
                                        false,
5915
                                        0,
5916
                                        $learnpath_id,
5917
                                        $learnpath_item_id
5918
                                    );
5919
                                }
5920
                            }
5921
                        }
5922
                    }
5923
                }
5924
            }
5925
5926
            $relPath = api_get_path(WEB_CODE_PATH);
5927
5928
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5929
                // We made an extra table for the answers
5930
                if ($show_result) {
5931
                    echo '</table></td></tr>';
5932
                    echo "
5933
                        <tr>
5934
                            <td>
5935
                                <p><em>".get_lang('HotSpot')."</em></p>
5936
                                <div id=\"hotspot-solution-$questionId\"></div>
5937
                                <script>
5938
                                    $(function() {
5939
                                        new HotspotQuestion({
5940
                                            questionId: $questionId,
5941
                                            exerciseId: {$this->id},
5942
                                            exeId: $exeId,
5943
                                            selector: '#hotspot-solution-$questionId',
5944
                                            for: 'solution',
5945
                                            relPath: '$relPath'
5946
                                        });
5947
                                    });
5948
                                </script>
5949
                            </td>
5950
                        </tr>
5951
                    ";
5952
                }
5953
            } elseif ($answerType == ANNOTATION) {
5954
                if ($show_result) {
5955
                    echo '
5956
                        <p><em>'.get_lang('Annotation').'</em></p>
5957
                        <div id="annotation-canvas-'.$questionId.'"></div>
5958
                        <script>
5959
                            AnnotationQuestion({
5960
                                questionId: parseInt('.$questionId.'),
5961
                                exerciseId: parseInt('.$exeId.'),
5962
                                relPath: \''.$relPath.'\',
5963
                                courseId: parseInt('.$course_id.')
5964
                            });
5965
                        </script>
5966
                    ';
5967
                }
5968
            }
5969
5970
            if ($show_result && $answerType != ANNOTATION) {
5971
                echo '</table>';
5972
            }
5973
        }
5974
        unset($objAnswerTmp);
5975
5976
        $totalWeighting += $questionWeighting;
5977
        // Store results directly in the database
5978
        // For all in one page exercises, the results will be
5979
        // stored by exercise_results.php (using the session)
5980
        if ($save_results) {
5981
            if ($debug) {
5982
                error_log("Save question results $save_results");
5983
                error_log("Question score: $questionScore");
5984
                error_log('choice: ');
5985
                error_log(print_r($choice, 1));
5986
            }
5987
5988
            if (empty($choice)) {
5989
                $choice = 0;
5990
            }
5991
            // with certainty degree
5992
            if (empty($choiceDegreeCertainty)) {
5993
                $choiceDegreeCertainty = 0;
5994
            }
5995
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
5996
                $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE ||
5997
                $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY
5998
            ) {
5999
                if ($choice != 0) {
6000
                    $reply = array_keys($choice);
6001
                    $countReply = count($reply);
6002
                    for ($i = 0; $i < $countReply; $i++) {
6003
                        $chosenAnswer = $reply[$i];
6004
                        if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
6005
                            if ($choiceDegreeCertainty != 0) {
6006
                                $replyDegreeCertainty = array_keys($choiceDegreeCertainty);
6007
                                $answerDegreeCertainty = isset($replyDegreeCertainty[$i]) ? $replyDegreeCertainty[$i] : '';
6008
                                $answerValue = isset($choiceDegreeCertainty[$answerDegreeCertainty]) ? $choiceDegreeCertainty[$answerDegreeCertainty] : '';
6009
                                Event::saveQuestionAttempt(
6010
                                    $questionScore,
6011
                                    $chosenAnswer.':'.$choice[$chosenAnswer].':'.$answerValue,
6012
                                    $quesId,
6013
                                    $exeId,
6014
                                    $i,
6015
                                    $this->id,
6016
                                    $updateResults,
6017
                                    $questionDuration
6018
                                );
6019
                            }
6020
                        } else {
6021
                            Event::saveQuestionAttempt(
6022
                                $questionScore,
6023
                                $chosenAnswer.':'.$choice[$chosenAnswer],
6024
                                $quesId,
6025
                                $exeId,
6026
                                $i,
6027
                                $this->id,
6028
                                $updateResults,
6029
                                $questionDuration
6030
                            );
6031
                        }
6032
                        if ($debug) {
6033
                            error_log('result =>'.$questionScore.' '.$chosenAnswer.':'.$choice[$chosenAnswer]);
6034
                        }
6035
                    }
6036
                } else {
6037
                    Event::saveQuestionAttempt(
6038
                        $questionScore,
6039
                        0,
6040
                        $quesId,
6041
                        $exeId,
6042
                        0,
6043
                        $this->id,
6044
                        false,
6045
                        $questionDuration
6046
                    );
6047
                }
6048
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
6049
                if ($choice != 0) {
6050
                    $reply = array_keys($choice);
6051
                    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...
6052
                        $ans = $reply[$i];
6053
                        Event::saveQuestionAttempt(
6054
                            $questionScore,
6055
                            $ans,
6056
                            $quesId,
6057
                            $exeId,
6058
                            $i,
6059
                            $this->id,
6060
                            false,
6061
                            $questionDuration
6062
                        );
6063
                    }
6064
                } else {
6065
                    Event::saveQuestionAttempt(
6066
                        $questionScore,
6067
                        0,
6068
                        $quesId,
6069
                        $exeId,
6070
                        0,
6071
                        $this->id,
6072
                        false,
6073
                        $questionDuration
6074
                    );
6075
                }
6076
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
6077
                if ($choice != 0) {
6078
                    $reply = array_keys($choice);
6079
                    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...
6080
                        $ans = $reply[$i];
6081
                        Event::saveQuestionAttempt(
6082
                            $questionScore,
6083
                            $ans,
6084
                            $quesId,
6085
                            $exeId,
6086
                            $i,
6087
                            $this->id,
6088
                            false,
6089
                            $questionDuration
6090
                        );
6091
                    }
6092
                } else {
6093
                    Event::saveQuestionAttempt(
6094
                        $questionScore,
6095
                        0,
6096
                        $quesId,
6097
                        $exeId,
6098
                        0,
6099
                        $this->id,
6100
                        false,
6101
                        $questionDuration
6102
                    );
6103
                }
6104
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
6105
                if (isset($matching)) {
6106
                    foreach ($matching as $j => $val) {
6107
                        Event::saveQuestionAttempt(
6108
                            $questionScore,
6109
                            $val,
6110
                            $quesId,
6111
                            $exeId,
6112
                            $j,
6113
                            $this->id,
6114
                            false,
6115
                            $questionDuration
6116
                        );
6117
                    }
6118
                }
6119
            } elseif ($answerType == FREE_ANSWER) {
6120
                $answer = $choice;
6121
                Event::saveQuestionAttempt(
6122
                    $questionScore,
6123
                    $answer,
6124
                    $quesId,
6125
                    $exeId,
6126
                    0,
6127
                    $this->id,
6128
                    false,
6129
                    $questionDuration
6130
                );
6131
            } elseif ($answerType == ORAL_EXPRESSION) {
6132
                $answer = $choice;
6133
                Event::saveQuestionAttempt(
6134
                    $questionScore,
6135
                    $answer,
6136
                    $quesId,
6137
                    $exeId,
6138
                    0,
6139
                    $this->id,
6140
                    false,
6141
                    $questionDuration,
6142
                    $objQuestionTmp->getAbsoluteFilePath()
6143
                );
6144
            } elseif (
6145
                in_array(
6146
                    $answerType,
6147
                    [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION]
6148
                )
6149
            ) {
6150
                $answer = $choice;
6151
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id, false, $questionDuration);
6152
            } elseif ($answerType == HOT_SPOT || $answerType == ANNOTATION) {
6153
                $answer = [];
6154
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
6155
                    if ($debug) {
6156
                        error_log('Checking result coordinates');
6157
                    }
6158
                    Database::delete(
6159
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
6160
                        [
6161
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
6162
                                $exeId,
6163
                                $questionId,
6164
                                api_get_course_int_id(),
6165
                            ],
6166
                        ]
6167
                    );
6168
6169
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
6170
                        $answer[] = $val;
6171
                        $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0;
6172
                        if ($debug) {
6173
                            error_log('Hotspot value: '.$hotspotValue);
6174
                        }
6175
                        Event::saveExerciseAttemptHotspot(
6176
                            $exeId,
6177
                            $quesId,
6178
                            $idx,
6179
                            $hotspotValue,
6180
                            $val,
6181
                            false,
6182
                            $this->id,
6183
                            $learnpath_id,
6184
                            $learnpath_item_id
6185
                        );
6186
                    }
6187
                } else {
6188
                    if ($debug) {
6189
                        error_log('Empty: exerciseResultCoordinates');
6190
                    }
6191
                }
6192
                Event::saveQuestionAttempt(
6193
                    $questionScore,
6194
                    implode('|', $answer),
6195
                    $quesId,
6196
                    $exeId,
6197
                    0,
6198
                    $this->id,
6199
                    false,
6200
                    $questionDuration
6201
                );
6202
            } else {
6203
                Event::saveQuestionAttempt(
6204
                    $questionScore,
6205
                    $answer,
6206
                    $quesId,
6207
                    $exeId,
6208
                    0,
6209
                    $this->id,
6210
                    false,
6211
                    $questionDuration
6212
                );
6213
            }
6214
        }
6215
6216
        if ($propagate_neg == 0 && $questionScore < 0) {
6217
            $questionScore = 0;
6218
        }
6219
6220
        if ($save_results) {
6221
            $statsTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6222
            $sql = "UPDATE $statsTable SET
6223
                        exe_result = exe_result + ".floatval($questionScore)."
6224
                    WHERE exe_id = $exeId";
6225
            Database::query($sql);
6226
        }
6227
6228
        return [
6229
            'score' => $questionScore,
6230
            'weight' => $questionWeighting,
6231
            'extra' => $extra_data,
6232
            'open_question' => $arrques,
6233
            'open_answer' => $arrans,
6234
            'answer_type' => $answerType,
6235
            'generated_oral_file' => $generatedFile,
6236
            'user_answered' => $userAnsweredQuestion,
6237
            'correct_answer_id' => $correctAnswerId,
6238
            'answer_destination' => $answerDestination,
6239
        ];
6240
    }
6241
6242
    /**
6243
     * Sends a notification when a user ends an examn.
6244
     *
6245
     * @param string $type                  'start' or 'end' of an exercise
6246
     * @param array  $question_list_answers
6247
     * @param string $origin
6248
     * @param int    $exe_id
6249
     * @param float  $score
6250
     * @param float  $weight
6251
     *
6252
     * @return bool
6253
     */
6254
    public function send_mail_notification_for_exam(
6255
        $type,
6256
        $question_list_answers,
6257
        $origin,
6258
        $exe_id,
6259
        $score = null,
6260
        $weight = null
6261
    ) {
6262
        $setting = api_get_course_setting('email_alert_manager_on_new_quiz');
6263
6264
        if (empty($setting) && empty($this->getNotifications())) {
6265
            return false;
6266
        }
6267
6268
        $settingFromExercise = $this->getNotifications();
6269
        if (!empty($settingFromExercise)) {
6270
            $setting = $settingFromExercise;
6271
        }
6272
6273
        // Email configuration settings
6274
        $courseCode = api_get_course_id();
6275
        $courseInfo = api_get_course_info($courseCode);
6276
6277
        if (empty($courseInfo)) {
6278
            return false;
6279
        }
6280
6281
        $sessionId = api_get_session_id();
6282
6283
        $sessionData = '';
6284
        if (!empty($sessionId)) {
6285
            $sessionInfo = api_get_session_info($sessionId);
6286
            if (!empty($sessionInfo)) {
6287
                $sessionData = '<tr>'
6288
                    .'<td>'.get_lang('SessionName').'</td>'
6289
                    .'<td>'.$sessionInfo['name'].'</td>'
6290
                    .'</tr>';
6291
            }
6292
        }
6293
6294
        $sendStart = false;
6295
        $sendEnd = false;
6296
        $sendEndOpenQuestion = false;
6297
        $sendEndOralQuestion = false;
6298
6299
        foreach ($setting as $option) {
6300
            switch ($option) {
6301
                case 0:
6302
                    return false;
6303
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
6304
                case 1: // End
6305
                    if ($type === 'end') {
6306
                        $sendEnd = true;
6307
                    }
6308
                    break;
6309
                case 2: // start
6310
                    if ($type === 'start') {
6311
                        $sendStart = true;
6312
                    }
6313
                    break;
6314
                case 3: // end + open
6315
                    if ($type === 'end') {
6316
                        $sendEndOpenQuestion = true;
6317
                    }
6318
                    break;
6319
                case 4: // end + oral
6320
                    if ($type === 'end') {
6321
                        $sendEndOralQuestion = true;
6322
                    }
6323
                    break;
6324
            }
6325
        }
6326
6327
        $user_info = api_get_user_info(api_get_user_id());
6328
        $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'.
6329
            api_get_cidreq(true, true, 'qualify').'&id='.$exe_id.'&action=qualify';
6330
6331
        if (!empty($sessionId)) {
6332
            $addGeneralCoach = true;
6333
            $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach');
6334
            if ($setting === true) {
6335
                $addGeneralCoach = false;
6336
            }
6337
            $teachers = CourseManager::get_coach_list_from_course_code(
6338
                $courseCode,
6339
                $sessionId,
6340
                $addGeneralCoach
6341
            );
6342
        } else {
6343
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
6344
        }
6345
6346
        if ($sendEndOpenQuestion) {
6347
            $this->sendNotificationForOpenQuestions(
6348
                $question_list_answers,
6349
                $origin,
6350
                $user_info,
6351
                $url,
6352
                $teachers
6353
            );
6354
        }
6355
6356
        if ($sendEndOralQuestion) {
6357
            $this->sendNotificationForOralQuestions(
6358
                $question_list_answers,
6359
                $origin,
6360
                $exe_id,
6361
                $user_info,
6362
                $url,
6363
                $teachers
6364
            );
6365
        }
6366
6367
        if (!$sendEnd && !$sendStart) {
6368
            return false;
6369
        }
6370
6371
        $scoreLabel = '';
6372
        if ($sendEnd &&
6373
            api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true
6374
        ) {
6375
            $notificationPercentage = api_get_configuration_value('send_notification_score_in_percentage');
6376
            $scoreLabel = ExerciseLib::show_score($score, $weight, $notificationPercentage, true);
6377
            $scoreLabel = "<tr>
6378
                            <td>".get_lang('Score')."</td>
6379
                            <td>&nbsp;$scoreLabel</td>
6380
                        </tr>";
6381
        }
6382
6383
        if ($sendEnd) {
6384
            $msg = get_lang('ExerciseAttempted').'<br /><br />';
6385
        } else {
6386
            $msg = get_lang('StudentStartExercise').'<br /><br />';
6387
        }
6388
6389
        $msg .= get_lang('AttemptDetails').' : <br /><br />
6390
                    <table>
6391
                        <tr>
6392
                            <td>'.get_lang('CourseName').'</td>
6393
                            <td>#course#</td>
6394
                        </tr>
6395
                        '.$sessionData.'
6396
                        <tr>
6397
                            <td>'.get_lang('Exercise').'</td>
6398
                            <td>&nbsp;#exercise#</td>
6399
                        </tr>
6400
                        <tr>
6401
                            <td>'.get_lang('StudentName').'</td>
6402
                            <td>&nbsp;#student_complete_name#</td>
6403
                        </tr>
6404
                        <tr>
6405
                            <td>'.get_lang('StudentEmail').'</td>
6406
                            <td>&nbsp;#email#</td>
6407
                        </tr>
6408
                        '.$scoreLabel.'
6409
                    </table>';
6410
6411
        $variables = [
6412
            '#email#' => $user_info['email'],
6413
            '#exercise#' => $this->exercise,
6414
            '#student_complete_name#' => $user_info['complete_name'],
6415
            '#course#' => Display::url(
6416
                $courseInfo['title'],
6417
                $courseInfo['course_public_url'].'?id_session='.$sessionId
6418
            ),
6419
        ];
6420
6421
        if ($sendEnd) {
6422
            $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
6423
            $variables['#url#'] = $url;
6424
        }
6425
6426
        $content = str_replace(array_keys($variables), array_values($variables), $msg);
6427
6428
        if ($sendEnd) {
6429
            $subject = get_lang('ExerciseAttempted');
6430
        } else {
6431
            $subject = get_lang('StudentStartExercise');
6432
        }
6433
6434
        if (!empty($teachers)) {
6435
            foreach ($teachers as $user_id => $teacher_data) {
6436
                MessageManager::send_message_simple(
6437
                    $user_id,
6438
                    $subject,
6439
                    $content
6440
                );
6441
            }
6442
        }
6443
    }
6444
6445
    /**
6446
     * @param array $user_data         result of api_get_user_info()
6447
     * @param array $trackExerciseInfo result of get_stat_track_exercise_info
6448
     * @param bool  $saveUserResult
6449
     * @param bool  $allowSignature
6450
     * @param bool  $allowExportPdf
6451
     *
6452
     * @return string
6453
     */
6454
    public function showExerciseResultHeader(
6455
        $user_data,
6456
        $trackExerciseInfo,
6457
        $saveUserResult,
6458
        $allowSignature = false,
6459
        $allowExportPdf = false
6460
    ) {
6461
        if (api_get_configuration_value('hide_user_info_in_quiz_result')) {
6462
            return '';
6463
        }
6464
6465
        $start_date = null;
6466
        if (isset($trackExerciseInfo['start_date'])) {
6467
            $start_date = api_convert_and_format_date($trackExerciseInfo['start_date']);
6468
        }
6469
        $duration = isset($trackExerciseInfo['duration_formatted']) ? $trackExerciseInfo['duration_formatted'] : null;
6470
        $ip = isset($trackExerciseInfo['user_ip']) ? $trackExerciseInfo['user_ip'] : null;
6471
6472
        if (!empty($user_data)) {
6473
            $userFullName = $user_data['complete_name'];
6474
            if (api_is_teacher() || api_is_platform_admin(true, true)) {
6475
                $userFullName = '<a href="'.$user_data['profile_url'].'" title="'.get_lang('GoToStudentDetails').'">'.
6476
                    $user_data['complete_name'].'</a>';
6477
            }
6478
6479
            $data = [
6480
                'name_url' => $userFullName,
6481
                'complete_name' => $user_data['complete_name'],
6482
                'username' => $user_data['username'],
6483
                'avatar' => $user_data['avatar_medium'],
6484
                'url' => $user_data['profile_url'],
6485
            ];
6486
6487
            if (!empty($user_data['official_code'])) {
6488
                $data['code'] = $user_data['official_code'];
6489
            }
6490
        }
6491
        // Description can be very long and is generally meant to explain
6492
        //   rules *before* the exam. Leaving here to make display easier if
6493
        //   necessary
6494
        /*
6495
        if (!empty($this->description)) {
6496
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
6497
        }
6498
        */
6499
        if (!empty($start_date)) {
6500
            $data['start_date'] = $start_date;
6501
        }
6502
6503
        if (!empty($duration)) {
6504
            $data['duration'] = $duration;
6505
        }
6506
6507
        if (!empty($ip)) {
6508
            $data['ip'] = $ip;
6509
        }
6510
6511
        if (api_get_configuration_value('save_titles_as_html')) {
6512
            $data['title'] = $this->get_formated_title().get_lang('Result');
6513
        } else {
6514
            $data['title'] = PHP_EOL.$this->exercise.' : '.get_lang('Result');
6515
        }
6516
6517
        $questionsCount = count(explode(',', $trackExerciseInfo['data_tracking']));
6518
        $savedAnswersCount = $this->countUserAnswersSavedInExercise($trackExerciseInfo['exe_id']);
6519
6520
        $data['number_of_answers'] = $questionsCount;
6521
        $data['number_of_answers_saved'] = $savedAnswersCount;
6522
        $exeId = $trackExerciseInfo['exe_id'];
6523
6524
        if (false !== api_get_configuration_value('quiz_confirm_saved_answers')) {
6525
            $em = Database::getManager();
6526
6527
            if ($saveUserResult) {
6528
                $trackConfirmation = new TrackEExerciseConfirmation();
6529
                $trackConfirmation
6530
                    ->setUserId($trackExerciseInfo['exe_user_id'])
6531
                    ->setQuizId($trackExerciseInfo['exe_exo_id'])
6532
                    ->setAttemptId($trackExerciseInfo['exe_id'])
6533
                    ->setQuestionsCount($questionsCount)
6534
                    ->setSavedAnswersCount($savedAnswersCount)
6535
                    ->setCourseId($trackExerciseInfo['c_id'])
6536
                    ->setSessionId($trackExerciseInfo['session_id'])
6537
                    ->setCreatedAt(api_get_utc_datetime(null, false, true));
6538
6539
                $em->persist($trackConfirmation);
6540
                $em->flush();
6541
            } else {
6542
                $trackConfirmation = $em
6543
                    ->getRepository('ChamiloCoreBundle:TrackEExerciseConfirmation')
6544
                    ->findOneBy(
6545
                        [
6546
                            'attemptId' => $trackExerciseInfo['exe_id'],
6547
                            'quizId' => $trackExerciseInfo['exe_exo_id'],
6548
                            'courseId' => $trackExerciseInfo['c_id'],
6549
                            'sessionId' => $trackExerciseInfo['session_id'],
6550
                        ]
6551
                    );
6552
            }
6553
6554
            $data['track_confirmation'] = $trackConfirmation;
6555
        }
6556
6557
        $signature = '';
6558
        if (ExerciseSignaturePlugin::exerciseHasSignatureActivated($this)) {
6559
            $signature = ExerciseSignaturePlugin::getSignature($trackExerciseInfo['exe_user_id'], $trackExerciseInfo);
6560
        }
6561
6562
        $tpl = new Template(null, false, false, false, false, false, false);
6563
        $tpl->assign('data', $data);
6564
        $tpl->assign('allow_signature', $allowSignature);
6565
        $tpl->assign('signature', $signature);
6566
        $tpl->assign('allow_export_pdf', $allowExportPdf);
6567
        $tpl->assign('export_url', api_get_path(WEB_CODE_PATH).'exercise/result.php?action=export&id='.$exeId.'&'.api_get_cidreq());
6568
6569
        $layoutTemplate = $tpl->get_template('exercise/partials/result_exercise.tpl');
6570
6571
        return $tpl->fetch($layoutTemplate);
6572
    }
6573
6574
    /**
6575
     * Returns the exercise result.
6576
     *
6577
     * @param 	int		attempt id
6578
     *
6579
     * @return array
6580
     */
6581
    public function get_exercise_result($exe_id)
6582
    {
6583
        $result = [];
6584
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
6585
6586
        if (!empty($track_exercise_info)) {
6587
            $totalScore = 0;
6588
            $objExercise = new self();
6589
            $objExercise->read($track_exercise_info['exe_exo_id']);
6590
            if (!empty($track_exercise_info['data_tracking'])) {
6591
                $question_list = explode(',', $track_exercise_info['data_tracking']);
6592
            }
6593
            foreach ($question_list as $questionId) {
6594
                $question_result = $objExercise->manage_answer(
6595
                    $exe_id,
6596
                    $questionId,
6597
                    '',
6598
                    'exercise_show',
6599
                    [],
6600
                    false,
6601
                    true,
6602
                    false,
6603
                    $objExercise->selectPropagateNeg()
6604
                );
6605
                $totalScore += $question_result['score'];
6606
            }
6607
6608
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
6609
                $totalScore = 0;
6610
            }
6611
            $result = [
6612
                'score' => $totalScore,
6613
                'weight' => $track_exercise_info['exe_weighting'],
6614
            ];
6615
        }
6616
6617
        return $result;
6618
    }
6619
6620
    /**
6621
     * Checks if the exercise is visible due a lot of conditions
6622
     * visibility, time limits, student attempts
6623
     * Return associative array
6624
     * value : true if exercise visible
6625
     * message : HTML formatted message
6626
     * rawMessage : text message.
6627
     *
6628
     * @param int  $lpId
6629
     * @param int  $lpItemId
6630
     * @param int  $lpItemViewId
6631
     * @param bool $filterByAdmin
6632
     *
6633
     * @return array
6634
     */
6635
    public function is_visible(
6636
        $lpId = 0,
6637
        $lpItemId = 0,
6638
        $lpItemViewId = 0,
6639
        $filterByAdmin = true,
6640
        $sessionId = 0
6641
    ) {
6642
        $sessionId = (int) $sessionId;
6643
        if ($sessionId == 0) {
6644
            $sessionId = $this->sessionId;
6645
        }
6646
        // 1. By default the exercise is visible
6647
        $isVisible = true;
6648
        $message = null;
6649
6650
        // 1.1 Admins, teachers and tutors can access to the exercise
6651
        if ($filterByAdmin) {
6652
            if (api_is_platform_admin() || api_is_course_admin() || api_is_course_tutor()) {
6653
                return ['value' => true, 'message' => ''];
6654
            }
6655
        }
6656
6657
        // Deleted exercise.
6658
        if ($this->active == -1) {
6659
            return [
6660
                'value' => false,
6661
                'message' => Display::return_message(
6662
                    get_lang('ExerciseNotFound'),
6663
                    'warning',
6664
                    false
6665
                ),
6666
                'rawMessage' => get_lang('ExerciseNotFound'),
6667
            ];
6668
        }
6669
6670
        // Checking visibility in the item_property table.
6671
        $visibility = api_get_item_visibility(
6672
            api_get_course_info(),
6673
            TOOL_QUIZ,
6674
            $this->id,
6675
            api_get_session_id()
6676
        );
6677
6678
        if ($visibility == 0 || $visibility == 2) {
6679
            $this->active = 0;
6680
        }
6681
6682
        // 2. If the exercise is not active.
6683
        if (empty($lpId)) {
6684
            // 2.1 LP is OFF
6685
            if ($this->active == 0) {
6686
                return [
6687
                    'value' => false,
6688
                    'message' => Display::return_message(
6689
                        get_lang('ExerciseNotFound'),
6690
                        'warning',
6691
                        false
6692
                    ),
6693
                    'rawMessage' => get_lang('ExerciseNotFound'),
6694
                ];
6695
            }
6696
        } else {
6697
            // 2.1 LP is loaded
6698
            if ($this->active == 0 &&
6699
                !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())
6700
            ) {
6701
                return [
6702
                    'value' => false,
6703
                    'message' => Display::return_message(
6704
                        get_lang('ExerciseNotFound'),
6705
                        'warning',
6706
                        false
6707
                    ),
6708
                    'rawMessage' => get_lang('ExerciseNotFound'),
6709
                ];
6710
            }
6711
        }
6712
6713
        // 3. We check if the time limits are on
6714
        $limitTimeExists = false;
6715
        if (!empty($this->start_time) || !empty($this->end_time)) {
6716
            $limitTimeExists = true;
6717
        }
6718
6719
        if ($limitTimeExists) {
6720
            $timeNow = time();
6721
            $existsStartDate = false;
6722
            $nowIsAfterStartDate = true;
6723
            $existsEndDate = false;
6724
            $nowIsBeforeEndDate = true;
6725
6726
            if (!empty($this->start_time)) {
6727
                $existsStartDate = true;
6728
            }
6729
6730
            if (!empty($this->end_time)) {
6731
                $existsEndDate = true;
6732
            }
6733
6734
            // check if we are before-or-after end-or-start date
6735
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
6736
                $nowIsAfterStartDate = false;
6737
            }
6738
6739
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
6740
                $nowIsBeforeEndDate = false;
6741
            }
6742
6743
            // lets check all cases
6744
            if ($existsStartDate && !$existsEndDate) {
6745
                // exists start date and dont exists end date
6746
                if ($nowIsAfterStartDate) {
6747
                    // after start date, no end date
6748
                    $isVisible = true;
6749
                    $message = sprintf(
6750
                        get_lang('ExerciseAvailableSinceX'),
6751
                        api_convert_and_format_date($this->start_time)
6752
                    );
6753
                } else {
6754
                    // before start date, no end date
6755
                    $isVisible = false;
6756
                    $message = sprintf(
6757
                        get_lang('ExerciseAvailableFromX'),
6758
                        api_convert_and_format_date($this->start_time)
6759
                    );
6760
                }
6761
            } elseif (!$existsStartDate && $existsEndDate) {
6762
                // doesnt exist start date, exists end date
6763
                if ($nowIsBeforeEndDate) {
6764
                    // before end date, no start date
6765
                    $isVisible = true;
6766
                    $message = sprintf(
6767
                        get_lang('ExerciseAvailableUntilX'),
6768
                        api_convert_and_format_date($this->end_time)
6769
                    );
6770
                } else {
6771
                    // after end date, no start date
6772
                    $isVisible = false;
6773
                    $message = sprintf(
6774
                        get_lang('ExerciseAvailableUntilX'),
6775
                        api_convert_and_format_date($this->end_time)
6776
                    );
6777
                }
6778
            } elseif ($existsStartDate && $existsEndDate) {
6779
                // exists start date and end date
6780
                if ($nowIsAfterStartDate) {
6781
                    if ($nowIsBeforeEndDate) {
6782
                        // after start date and before end date
6783
                        $isVisible = true;
6784
                        $message = sprintf(
6785
                            get_lang('ExerciseIsActivatedFromXToY'),
6786
                            api_convert_and_format_date($this->start_time),
6787
                            api_convert_and_format_date($this->end_time)
6788
                        );
6789
                    } else {
6790
                        // after start date and after end date
6791
                        $isVisible = false;
6792
                        $message = sprintf(
6793
                            get_lang('ExerciseWasActivatedFromXToY'),
6794
                            api_convert_and_format_date($this->start_time),
6795
                            api_convert_and_format_date($this->end_time)
6796
                        );
6797
                    }
6798
                } else {
6799
                    if ($nowIsBeforeEndDate) {
6800
                        // before start date and before end date
6801
                        $isVisible = false;
6802
                        $message = sprintf(
6803
                            get_lang('ExerciseWillBeActivatedFromXToY'),
6804
                            api_convert_and_format_date($this->start_time),
6805
                            api_convert_and_format_date($this->end_time)
6806
                        );
6807
                    }
6808
                    // case before start date and after end date is impossible
6809
                }
6810
            } elseif (!$existsStartDate && !$existsEndDate) {
6811
                // doesnt exist start date nor end date
6812
                $isVisible = true;
6813
                $message = '';
6814
            }
6815
        }
6816
6817
        $remedialCoursePlugin = RemedialCoursePlugin::create();
6818
6819
        // BT#18165
6820
        $exerciseAttempts = $this->selectAttempts();
6821
        if ($exerciseAttempts > 0) {
6822
            $userId = api_get_user_id();
6823
            $attemptCount = Event::get_attempt_count_not_finished(
6824
                $userId,
6825
                $this->id,
6826
                $lpId,
6827
                $lpItemId,
6828
                $lpItemViewId
6829
            );
6830
            $message .= $this->advancedCourseList($userId, api_get_session_id());
6831
            if ($attemptCount >= $exerciseAttempts) {
6832
                $message .= $remedialCoursePlugin->getRemedialCourseList($this, $userId, api_get_session_id());
6833
            }
6834
        }
6835
        // 4. We check if the student have attempts
6836
        if ($isVisible) {
6837
            $exerciseAttempts = $this->selectAttempts();
6838
6839
            if ($exerciseAttempts > 0) {
6840
                $attemptCount = Event::get_attempt_count_not_finished(
6841
                    api_get_user_id(),
6842
                    $this->id,
6843
                    $lpId,
6844
                    $lpItemId,
6845
                    $lpItemViewId
6846
                );
6847
6848
                if ($attemptCount >= $exerciseAttempts) {
6849
                    $message = sprintf(
6850
                        get_lang('ReachedMaxAttempts'),
6851
                        $this->name,
6852
                        $exerciseAttempts
6853
                    );
6854
                    $isVisible = false;
6855
                } else {
6856
                    // Check blocking exercise.
6857
                    $extraFieldValue = new ExtraFieldValue('exercise');
6858
                    $blockExercise = $extraFieldValue->get_values_by_handler_and_field_variable(
6859
                        $this->iId,
6860
                        'blocking_percentage'
6861
                    );
6862
                    if ($blockExercise && isset($blockExercise['value']) && !empty($blockExercise['value'])) {
6863
                        $blockPercentage = (int) $blockExercise['value'];
6864
                        $userAttempts = Event::getExerciseResultsByUser(
6865
                            api_get_user_id(),
6866
                            $this->iId,
6867
                            $this->course_id,
6868
                            $sessionId,
6869
                            $lpId,
6870
                            $lpItemId
6871
                        );
6872
6873
                        if (!empty($userAttempts)) {
6874
                            $currentAttempt = current($userAttempts);
6875
                            if ($currentAttempt['total_percentage'] <= $blockPercentage) {
6876
                                $message = sprintf(
6877
                                    get_lang('ExerciseBlockBecausePercentageX'),
6878
                                    $blockPercentage
6879
                                );
6880
                                $isVisible = false; // See BT#18165
6881
                                $message .= $remedialCoursePlugin->getRemedialCourseList(
6882
                                    $this,
6883
                                    api_get_user_id(),
6884
                                    api_get_session_id()
6885
                                );
6886
                            }
6887
                        }
6888
                    }
6889
                }
6890
            }
6891
        }
6892
6893
        $rawMessage = '';
6894
        if (!empty($message)) {
6895
            $rawMessage = $message;
6896
            $message = Display::return_message($message, 'warning', false);
6897
        }
6898
6899
        return [
6900
            'value' => $isVisible,
6901
            'message' => $message,
6902
            'rawMessage' => $rawMessage,
6903
        ];
6904
    }
6905
6906
    /**
6907
     * @return bool
6908
     */
6909
    public function added_in_lp()
6910
    {
6911
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
6912
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
6913
                WHERE
6914
                    c_id = {$this->course_id} AND
6915
                    item_type = '".TOOL_QUIZ."' AND
6916
                    path = '{$this->id}'";
6917
        $result = Database::query($sql);
6918
        if (Database::num_rows($result) > 0) {
6919
            return true;
6920
        }
6921
6922
        return false;
6923
    }
6924
6925
    /**
6926
     * Returns an array with this form.
6927
     *
6928
     * @example
6929
     * <code>
6930
     * array (size=3)
6931
     * 999 =>
6932
     * array (size=3)
6933
     * 0 => int 3422
6934
     * 1 => int 3423
6935
     * 2 => int 3424
6936
     * 100 =>
6937
     * array (size=2)
6938
     * 0 => int 3469
6939
     * 1 => int 3470
6940
     * 101 =>
6941
     * array (size=1)
6942
     * 0 => int 3482
6943
     * </code>
6944
     * The array inside the key 999 means the question list that belongs to the media id = 999,
6945
     * this case is special because 999 means "no media".
6946
     *
6947
     * @return array
6948
     */
6949
    public function getMediaList()
6950
    {
6951
        return $this->mediaList;
6952
    }
6953
6954
    /**
6955
     * Is media question activated?
6956
     *
6957
     * @return bool
6958
     */
6959
    public function mediaIsActivated()
6960
    {
6961
        $mediaQuestions = $this->getMediaList();
6962
        $active = false;
6963
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
6964
            $media_count = count($mediaQuestions);
6965
            if ($media_count > 1) {
6966
                return true;
6967
            } elseif ($media_count == 1) {
6968
                if (isset($mediaQuestions[999])) {
6969
                    return false;
6970
                } else {
6971
                    return true;
6972
                }
6973
            }
6974
        }
6975
6976
        return $active;
6977
    }
6978
6979
    /**
6980
     * Gets question list from the exercise.
6981
     *
6982
     * @return array
6983
     */
6984
    public function getQuestionList()
6985
    {
6986
        return $this->questionList;
6987
    }
6988
6989
    /**
6990
     * Question list with medias compressed like this.
6991
     *
6992
     * @example
6993
     * <code>
6994
     * array(
6995
     *      question_id_1,
6996
     *      question_id_2,
6997
     *      media_id, <- this media id contains question ids
6998
     *      question_id_3,
6999
     * )
7000
     * </code>
7001
     *
7002
     * @return array
7003
     */
7004
    public function getQuestionListWithMediasCompressed()
7005
    {
7006
        return $this->questionList;
7007
    }
7008
7009
    /**
7010
     * Question list with medias uncompressed like this.
7011
     *
7012
     * @example
7013
     * <code>
7014
     * array(
7015
     *      question_id,
7016
     *      question_id,
7017
     *      question_id, <- belongs to a media id
7018
     *      question_id, <- belongs to a media id
7019
     *      question_id,
7020
     * )
7021
     * </code>
7022
     *
7023
     * @return array
7024
     */
7025
    public function getQuestionListWithMediasUncompressed()
7026
    {
7027
        return $this->questionListUncompressed;
7028
    }
7029
7030
    /**
7031
     * Sets the question list when the exercise->read() is executed.
7032
     *
7033
     * @param bool $adminView Whether to view the set the list of *all* questions or just the normal student view
7034
     */
7035
    public function setQuestionList($adminView = false)
7036
    {
7037
        // Getting question list.
7038
        $questionList = $this->selectQuestionList(true, $adminView);
7039
        $this->setMediaList($questionList);
7040
        $this->questionList = $this->transformQuestionListWithMedias($questionList, false);
7041
        $this->questionListUncompressed = $this->transformQuestionListWithMedias(
7042
            $questionList,
7043
            true
7044
        );
7045
    }
7046
7047
    /**
7048
     * @params array question list
7049
     * @params bool expand or not question list (true show all questions,
7050
     * false show media question id instead of the question ids)
7051
     */
7052
    public function transformQuestionListWithMedias(
7053
        $question_list,
7054
        $expand_media_questions = false
7055
    ) {
7056
        $new_question_list = [];
7057
        if (!empty($question_list)) {
7058
            $media_questions = $this->getMediaList();
7059
            $media_active = $this->mediaIsActivated($media_questions);
7060
7061
            if ($media_active) {
7062
                $counter = 1;
7063
                foreach ($question_list as $question_id) {
7064
                    $add_question = true;
7065
                    foreach ($media_questions as $media_id => $question_list_in_media) {
7066
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
7067
                            $add_question = false;
7068
                            if (!in_array($media_id, $new_question_list)) {
7069
                                $new_question_list[$counter] = $media_id;
7070
                                $counter++;
7071
                            }
7072
                            break;
7073
                        }
7074
                    }
7075
                    if ($add_question) {
7076
                        $new_question_list[$counter] = $question_id;
7077
                        $counter++;
7078
                    }
7079
                }
7080
                if ($expand_media_questions) {
7081
                    $media_key_list = array_keys($media_questions);
7082
                    foreach ($new_question_list as &$question_id) {
7083
                        if (in_array($question_id, $media_key_list)) {
7084
                            $question_id = $media_questions[$question_id];
7085
                        }
7086
                    }
7087
                    $new_question_list = array_flatten($new_question_list);
7088
                }
7089
            } else {
7090
                $new_question_list = $question_list;
7091
            }
7092
        }
7093
7094
        return $new_question_list;
7095
    }
7096
7097
    /**
7098
     * Get question list depend on the random settings.
7099
     *
7100
     * @return array
7101
     */
7102
    public function get_validated_question_list()
7103
    {
7104
        $isRandomByCategory = $this->isRandomByCat();
7105
        if ($isRandomByCategory == 0) {
7106
            if ($this->isRandom()) {
7107
                return $this->getRandomList();
7108
            }
7109
7110
            return $this->selectQuestionList();
7111
        }
7112
7113
        if ($this->isRandom()) {
7114
            // USE question categories
7115
            // get questions by category for this exercise
7116
            // we have to choice $objExercise->random question in each array values of $categoryQuestions
7117
            // key of $categoryQuestions are the categopy id (0 for not in a category)
7118
            // value is the array of question id of this category
7119
            $questionList = [];
7120
            $categoryQuestions = TestCategory::getQuestionsByCat($this->id);
7121
            $isRandomByCategory = $this->getRandomByCategory();
7122
            // We sort categories based on the term between [] in the head
7123
            // of the category's description
7124
            /* examples of categories :
7125
             * [biologie] Maitriser les mecanismes de base de la genetique
7126
             * [biologie] Relier les moyens de depenses et les agents infectieux
7127
             * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
7128
             * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
7129
             * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
7130
             * [chimie] Connaître les charges des particules
7131
             * We want that in the order of the groups defined by the term
7132
             * between brackets at the beginning of the category title
7133
            */
7134
            // If test option is Grouped By Categories
7135
            if ($isRandomByCategory == 2) {
7136
                $categoryQuestions = TestCategory::sortTabByBracketLabel($categoryQuestions);
7137
            }
7138
            foreach ($categoryQuestions as $question) {
7139
                $number_of_random_question = $this->random;
7140
                if ($this->random == -1) {
7141
                    $number_of_random_question = count($this->questionList);
7142
                }
7143
                $questionList = array_merge(
7144
                    $questionList,
7145
                    TestCategory::getNElementsFromArray(
7146
                        $question,
7147
                        $number_of_random_question
7148
                    )
7149
                );
7150
            }
7151
            // shuffle the question list if test is not grouped by categories
7152
            if ($isRandomByCategory == 1) {
7153
                shuffle($questionList); // or not
7154
            }
7155
7156
            return $questionList;
7157
        }
7158
7159
        // Problem, random by category has been selected and
7160
        // we have no $this->isRandom number of question selected
7161
        // Should not happened
7162
7163
        return [];
7164
    }
7165
7166
    public function get_question_list($expand_media_questions = false)
7167
    {
7168
        $question_list = $this->get_validated_question_list();
7169
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
7170
7171
        return $question_list;
7172
    }
7173
7174
    public function transform_question_list_with_medias($question_list, $expand_media_questions = false)
7175
    {
7176
        $new_question_list = [];
7177
        if (!empty($question_list)) {
7178
            $media_questions = $this->getMediaList();
7179
            $media_active = $this->mediaIsActivated($media_questions);
7180
7181
            if ($media_active) {
7182
                $counter = 1;
7183
                foreach ($question_list as $question_id) {
7184
                    $add_question = true;
7185
                    foreach ($media_questions as $media_id => $question_list_in_media) {
7186
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
7187
                            $add_question = false;
7188
                            if (!in_array($media_id, $new_question_list)) {
7189
                                $new_question_list[$counter] = $media_id;
7190
                                $counter++;
7191
                            }
7192
                            break;
7193
                        }
7194
                    }
7195
                    if ($add_question) {
7196
                        $new_question_list[$counter] = $question_id;
7197
                        $counter++;
7198
                    }
7199
                }
7200
                if ($expand_media_questions) {
7201
                    $media_key_list = array_keys($media_questions);
7202
                    foreach ($new_question_list as &$question_id) {
7203
                        if (in_array($question_id, $media_key_list)) {
7204
                            $question_id = $media_questions[$question_id];
7205
                        }
7206
                    }
7207
                    $new_question_list = array_flatten($new_question_list);
7208
                }
7209
            } else {
7210
                $new_question_list = $question_list;
7211
            }
7212
        }
7213
7214
        return $new_question_list;
7215
    }
7216
7217
    /**
7218
     * @param int $exe_id
7219
     *
7220
     * @return array
7221
     */
7222
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
7223
    {
7224
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7225
        $exe_id = (int) $exe_id;
7226
        $sql_track = "SELECT * FROM $table WHERE exe_id = $exe_id ";
7227
        $result = Database::query($sql_track);
7228
        $new_array = [];
7229
        if (Database::num_rows($result) > 0) {
7230
            $new_array = Database::fetch_array($result, 'ASSOC');
7231
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
7232
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
7233
            $new_array['duration_formatted'] = '';
7234
            if (!empty($new_array['exe_duration']) && !empty($start_date) && !empty($end_date)) {
7235
                $time = api_format_time($new_array['exe_duration'], 'js');
7236
                $new_array['duration_formatted'] = $time;
7237
            }
7238
        }
7239
7240
        return $new_array;
7241
    }
7242
7243
    /**
7244
     * @param int $exeId
7245
     *
7246
     * @return bool
7247
     */
7248
    public function removeAllQuestionToRemind($exeId)
7249
    {
7250
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7251
        $exeId = (int) $exeId;
7252
        if (empty($exeId)) {
7253
            return false;
7254
        }
7255
        $sql = "UPDATE $table
7256
                SET questions_to_check = ''
7257
                WHERE exe_id = $exeId ";
7258
        Database::query($sql);
7259
7260
        return true;
7261
    }
7262
7263
    /**
7264
     * @param int   $exeId
7265
     * @param array $questionList
7266
     *
7267
     * @return bool
7268
     */
7269
    public function addAllQuestionToRemind($exeId, $questionList = [])
7270
    {
7271
        $exeId = (int) $exeId;
7272
        if (empty($questionList)) {
7273
            return false;
7274
        }
7275
7276
        $questionListToString = implode(',', $questionList);
7277
        $questionListToString = Database::escape_string($questionListToString);
7278
7279
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7280
        $sql = "UPDATE $table
7281
                SET questions_to_check = '$questionListToString'
7282
                WHERE exe_id = $exeId";
7283
        Database::query($sql);
7284
7285
        return true;
7286
    }
7287
7288
    /**
7289
     * @param int    $exeId
7290
     * @param int    $questionId
7291
     * @param string $action
7292
     */
7293
    public function editQuestionToRemind($exeId, $questionId, $action = 'add')
7294
    {
7295
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exeId);
7296
        $questionId = (int) $questionId;
7297
        $exeId = (int) $exeId;
7298
7299
        if ($exercise_info) {
7300
            $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7301
            if (empty($exercise_info['questions_to_check'])) {
7302
                if ($action === 'add') {
7303
                    $sql = "UPDATE $track_exercises
7304
                            SET questions_to_check = '$questionId'
7305
                            WHERE exe_id = $exeId ";
7306
                    Database::query($sql);
7307
                }
7308
            } else {
7309
                $remind_list = explode(',', $exercise_info['questions_to_check']);
7310
                $remind_list_string = '';
7311
                if ($action === 'add') {
7312
                    if (!in_array($questionId, $remind_list)) {
7313
                        $newRemindList = [];
7314
                        $remind_list[] = $questionId;
7315
                        $questionListInSession = Session::read('questionList');
7316
                        if (!empty($questionListInSession)) {
7317
                            foreach ($questionListInSession as $originalQuestionId) {
7318
                                if (in_array($originalQuestionId, $remind_list)) {
7319
                                    $newRemindList[] = $originalQuestionId;
7320
                                }
7321
                            }
7322
                        }
7323
                        $remind_list_string = implode(',', $newRemindList);
7324
                    }
7325
                } elseif ($action === 'delete') {
7326
                    if (!empty($remind_list)) {
7327
                        if (in_array($questionId, $remind_list)) {
7328
                            $remind_list = array_flip($remind_list);
7329
                            unset($remind_list[$questionId]);
7330
                            $remind_list = array_flip($remind_list);
7331
7332
                            if (!empty($remind_list)) {
7333
                                sort($remind_list);
7334
                                array_filter($remind_list);
7335
                                $remind_list_string = implode(',', $remind_list);
7336
                            }
7337
                        }
7338
                    }
7339
                }
7340
                $value = Database::escape_string($remind_list_string);
7341
                $sql = "UPDATE $track_exercises
7342
                        SET questions_to_check = '$value'
7343
                        WHERE exe_id = $exeId ";
7344
                Database::query($sql);
7345
            }
7346
        }
7347
    }
7348
7349
    /**
7350
     * @param string $answer
7351
     *
7352
     * @return mixed
7353
     */
7354
    public function fill_in_blank_answer_to_array($answer)
7355
    {
7356
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
7357
        $teacher_answer_list = $teacher_answer_list[0];
7358
7359
        return $teacher_answer_list;
7360
    }
7361
7362
    /**
7363
     * @param string $answer
7364
     *
7365
     * @return string
7366
     */
7367
    public function fill_in_blank_answer_to_string($answer)
7368
    {
7369
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
7370
        $result = '';
7371
        if (!empty($teacher_answer_list)) {
7372
            foreach ($teacher_answer_list as $teacher_item) {
7373
                // Cleaning student answer list
7374
                $value = strip_tags($teacher_item);
7375
                $value = api_substr($value, 1, api_strlen($value) - 2);
7376
                $value = explode('/', $value);
7377
                if (!empty($value[0])) {
7378
                    $value = trim($value[0]);
7379
                    $value = str_replace('&nbsp;', '', $value);
7380
                    $result .= $value;
7381
                }
7382
            }
7383
        }
7384
7385
        return $result;
7386
    }
7387
7388
    /**
7389
     * @return string
7390
     */
7391
    public function returnTimeLeftDiv()
7392
    {
7393
        $html = '<div id="clock_warning" style="display:none">';
7394
        $html .= Display::return_message(
7395
            get_lang('ReachedTimeLimit'),
7396
            'warning'
7397
        );
7398
        $html .= ' ';
7399
        $html .= sprintf(
7400
            get_lang('YouWillBeRedirectedInXSeconds'),
7401
            '<span id="counter_to_redirect" class="red_alert"></span>'
7402
        );
7403
        $html .= '</div>';
7404
7405
        $icon = Display::returnFontAwesomeIcon('clock-o');
7406
        $html .= '<div class="count_down">
7407
                    '.get_lang('RemainingTimeToFinishExercise').'
7408
                    '.$icon.'<span id="exercise_clock_warning"></span>
7409
                </div>';
7410
7411
        return $html;
7412
    }
7413
7414
    /**
7415
     * Get categories added in the exercise--category matrix.
7416
     *
7417
     * @return array
7418
     */
7419
    public function getCategoriesInExercise()
7420
    {
7421
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
7422
        if (!empty($this->id)) {
7423
            $sql = "SELECT * FROM $table
7424
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
7425
            $result = Database::query($sql);
7426
            $list = [];
7427
            if (Database::num_rows($result)) {
7428
                while ($row = Database::fetch_array($result, 'ASSOC')) {
7429
                    $list[$row['category_id']] = $row;
7430
                }
7431
7432
                return $list;
7433
            }
7434
        }
7435
7436
        return [];
7437
    }
7438
7439
    /**
7440
     * Get total number of question that will be parsed when using the category/exercise.
7441
     *
7442
     * @return int
7443
     */
7444
    public function getNumberQuestionExerciseCategory()
7445
    {
7446
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
7447
        if (!empty($this->id)) {
7448
            $sql = "SELECT SUM(count_questions) count_questions
7449
                    FROM $table
7450
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
7451
            $result = Database::query($sql);
7452
            if (Database::num_rows($result)) {
7453
                $row = Database::fetch_array($result);
7454
7455
                return (int) $row['count_questions'];
7456
            }
7457
        }
7458
7459
        return 0;
7460
    }
7461
7462
    /**
7463
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table.
7464
     *
7465
     * @param array $categories
7466
     */
7467
    public function save_categories_in_exercise($categories)
7468
    {
7469
        if (!empty($categories) && !empty($this->id)) {
7470
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
7471
            $sql = "DELETE FROM $table
7472
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
7473
            Database::query($sql);
7474
            if (!empty($categories)) {
7475
                foreach ($categories as $categoryId => $countQuestions) {
7476
                    $params = [
7477
                        'c_id' => $this->course_id,
7478
                        'exercise_id' => $this->id,
7479
                        'category_id' => $categoryId,
7480
                        'count_questions' => $countQuestions,
7481
                    ];
7482
                    Database::insert($table, $params);
7483
                }
7484
            }
7485
        }
7486
    }
7487
7488
    /**
7489
     * @param array  $questionList
7490
     * @param int    $currentQuestion
7491
     * @param array  $conditions
7492
     * @param string $link
7493
     *
7494
     * @return string
7495
     */
7496
    public function progressExercisePaginationBar(
7497
        $questionList,
7498
        $currentQuestion,
7499
        $conditions,
7500
        $link
7501
    ) {
7502
        $mediaQuestions = $this->getMediaList();
7503
7504
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
7505
        $counter = 0;
7506
        $nextValue = 0;
7507
        $wasMedia = false;
7508
        $before = 0;
7509
        $counterNoMedias = 0;
7510
        foreach ($questionList as $questionId) {
7511
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
7512
7513
            if (!empty($nextValue)) {
7514
                if ($wasMedia) {
7515
                    $nextValue = $nextValue - $before + 1;
7516
                }
7517
            }
7518
7519
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
7520
                $fixedValue = $counterNoMedias;
7521
7522
                $html .= Display::progressPaginationBar(
7523
                    $nextValue,
7524
                    $mediaQuestions[$questionId],
7525
                    $currentQuestion,
7526
                    $fixedValue,
7527
                    $conditions,
7528
                    $link,
7529
                    true,
7530
                    true
7531
                );
7532
7533
                $counter += count($mediaQuestions[$questionId]) - 1;
7534
                $before = count($questionList);
7535
                $wasMedia = true;
7536
                $nextValue += count($questionList);
7537
            } else {
7538
                $html .= Display::parsePaginationItem(
7539
                    $questionId,
7540
                    $isCurrent,
7541
                    $conditions,
7542
                    $link,
7543
                    $counter
7544
                );
7545
                $counter++;
7546
                $nextValue++;
7547
                $wasMedia = false;
7548
            }
7549
            $counterNoMedias++;
7550
        }
7551
        $html .= '</ul></div>';
7552
7553
        return $html;
7554
    }
7555
7556
    /**
7557
     *  Shows a list of numbers that represents the question to answer in a exercise.
7558
     *
7559
     * @param array  $categories
7560
     * @param int    $current
7561
     * @param array  $conditions
7562
     * @param string $link
7563
     *
7564
     * @return string
7565
     */
7566
    public function progressExercisePaginationBarWithCategories(
7567
        $categories,
7568
        $current,
7569
        $conditions = [],
7570
        $link = null
7571
    ) {
7572
        $html = null;
7573
        $counterNoMedias = 0;
7574
        $nextValue = 0;
7575
        $wasMedia = false;
7576
        $before = 0;
7577
7578
        if (!empty($categories)) {
7579
            $selectionType = $this->getQuestionSelectionType();
7580
            $useRootAsCategoryTitle = false;
7581
7582
            // Grouping questions per parent category see BT#6540
7583
            if (in_array(
7584
                $selectionType,
7585
                [
7586
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
7587
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM,
7588
                ]
7589
            )) {
7590
                $useRootAsCategoryTitle = true;
7591
            }
7592
7593
            // If the exercise is set to only show the titles of the categories
7594
            // at the root of the tree, then pre-order the categories tree by
7595
            // removing children and summing their questions into the parent
7596
            // categories
7597
            if ($useRootAsCategoryTitle) {
7598
                // The new categories list starts empty
7599
                $newCategoryList = [];
7600
                foreach ($categories as $category) {
7601
                    $rootElement = $category['root'];
7602
7603
                    if (isset($category['parent_info'])) {
7604
                        $rootElement = $category['parent_info']['id'];
7605
                    }
7606
7607
                    //$rootElement = $category['id'];
7608
                    // If the current category's ancestor was never seen
7609
                    // before, then declare it and assign the current
7610
                    // category to it.
7611
                    if (!isset($newCategoryList[$rootElement])) {
7612
                        $newCategoryList[$rootElement] = $category;
7613
                    } else {
7614
                        // If it was already seen, then merge the previous with
7615
                        // the current category
7616
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
7617
                        $category['question_list'] = array_merge($oldQuestionList, $category['question_list']);
7618
                        $newCategoryList[$rootElement] = $category;
7619
                    }
7620
                }
7621
                // Now use the newly built categories list, with only parents
7622
                $categories = $newCategoryList;
7623
            }
7624
7625
            foreach ($categories as $category) {
7626
                $questionList = $category['question_list'];
7627
                // Check if in this category there questions added in a media
7628
                $mediaQuestionId = $category['media_question'];
7629
                $isMedia = false;
7630
                $fixedValue = null;
7631
7632
                // Media exists!
7633
                if ($mediaQuestionId != 999) {
7634
                    $isMedia = true;
7635
                    $fixedValue = $counterNoMedias;
7636
                }
7637
7638
                //$categoryName = $category['path']; << show the path
7639
                $categoryName = $category['name'];
7640
7641
                if ($useRootAsCategoryTitle) {
7642
                    if (isset($category['parent_info'])) {
7643
                        $categoryName = $category['parent_info']['title'];
7644
                    }
7645
                }
7646
                $html .= '<div class="row">';
7647
                $html .= '<div class="span2">'.$categoryName.'</div>';
7648
                $html .= '<div class="span8">';
7649
7650
                if (!empty($nextValue)) {
7651
                    if ($wasMedia) {
7652
                        $nextValue = $nextValue - $before + 1;
7653
                    }
7654
                }
7655
                $html .= Display::progressPaginationBar(
7656
                    $nextValue,
7657
                    $questionList,
7658
                    $current,
7659
                    $fixedValue,
7660
                    $conditions,
7661
                    $link,
7662
                    $isMedia,
7663
                    true
7664
                );
7665
                $html .= '</div>';
7666
                $html .= '</div>';
7667
7668
                if ($mediaQuestionId == 999) {
7669
                    $counterNoMedias += count($questionList);
7670
                } else {
7671
                    $counterNoMedias++;
7672
                }
7673
7674
                $nextValue += count($questionList);
7675
                $before = count($questionList);
7676
7677
                if ($mediaQuestionId != 999) {
7678
                    $wasMedia = true;
7679
                } else {
7680
                    $wasMedia = false;
7681
                }
7682
            }
7683
        }
7684
7685
        return $html;
7686
    }
7687
7688
    /**
7689
     * Renders a question list.
7690
     *
7691
     * @param array $questionList    (with media questions compressed)
7692
     * @param int   $currentQuestion
7693
     * @param array $exerciseResult
7694
     * @param array $attemptList
7695
     * @param array $remindList
7696
     */
7697
    public function renderQuestionList(
7698
        $questionList,
7699
        $currentQuestion,
7700
        $exerciseResult,
7701
        $attemptList,
7702
        $remindList
7703
    ) {
7704
        $mediaQuestions = $this->getMediaList();
7705
        $i = 0;
7706
7707
        // Normal question list render (medias compressed)
7708
        foreach ($questionList as $questionId) {
7709
            $i++;
7710
            // For sequential exercises
7711
7712
            if ($this->type == ONE_PER_PAGE) {
7713
                // If it is not the right question, goes to the next loop iteration
7714
                if ($currentQuestion != $i) {
7715
                    continue;
7716
                } else {
7717
                    if (!in_array(
7718
                        $this->getFeedbackType(),
7719
                        [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]
7720
                    )) {
7721
                        // if the user has already answered this question
7722
                        if (isset($exerciseResult[$questionId])) {
7723
                            echo Display::return_message(
7724
                                get_lang('AlreadyAnswered'),
7725
                                'normal'
7726
                            );
7727
                            break;
7728
                        }
7729
                    }
7730
                }
7731
            }
7732
7733
            // The $questionList contains the media id we check
7734
            // if this questionId is a media question type
7735
            if (isset($mediaQuestions[$questionId]) &&
7736
                $mediaQuestions[$questionId] != 999
7737
            ) {
7738
                // The question belongs to a media
7739
                $mediaQuestionList = $mediaQuestions[$questionId];
7740
                $objQuestionTmp = Question::read($questionId);
7741
7742
                $counter = 1;
7743
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
7744
                    echo $objQuestionTmp->show_media_content();
7745
7746
                    $countQuestionsInsideMedia = count($mediaQuestionList);
7747
7748
                    // Show questions that belongs to a media
7749
                    if (!empty($mediaQuestionList)) {
7750
                        // In order to parse media questions we use letters a, b, c, etc.
7751
                        $letterCounter = 97;
7752
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
7753
                            $isLastQuestionInMedia = false;
7754
                            if ($counter == $countQuestionsInsideMedia) {
7755
                                $isLastQuestionInMedia = true;
7756
                            }
7757
                            $this->renderQuestion(
7758
                                $questionIdInsideMedia,
7759
                                $attemptList,
7760
                                $remindList,
7761
                                chr($letterCounter),
7762
                                $currentQuestion,
7763
                                $mediaQuestionList,
7764
                                $isLastQuestionInMedia,
7765
                                $questionList
7766
                            );
7767
                            $letterCounter++;
7768
                            $counter++;
7769
                        }
7770
                    }
7771
                } else {
7772
                    $this->renderQuestion(
7773
                        $questionId,
7774
                        $attemptList,
7775
                        $remindList,
7776
                        $i,
7777
                        $currentQuestion,
7778
                        null,
7779
                        null,
7780
                        $questionList
7781
                    );
7782
                    $i++;
7783
                }
7784
            } else {
7785
                // Normal question render.
7786
                $this->renderQuestion(
7787
                    $questionId,
7788
                    $attemptList,
7789
                    $remindList,
7790
                    $i,
7791
                    $currentQuestion,
7792
                    null,
7793
                    null,
7794
                    $questionList
7795
                );
7796
            }
7797
7798
            // For sequential exercises.
7799
            if ($this->type == ONE_PER_PAGE) {
7800
                // quits the loop
7801
                break;
7802
            }
7803
        }
7804
        // end foreach()
7805
7806
        if ($this->type == ALL_ON_ONE_PAGE) {
7807
            $exercise_actions = $this->show_button($questionId, $currentQuestion);
7808
            echo Display::div($exercise_actions, ['class' => 'exercise_actions']);
7809
        }
7810
    }
7811
7812
    /**
7813
     * @param int   $questionId
7814
     * @param array $attemptList
7815
     * @param array $remindList
7816
     * @param int   $i
7817
     * @param int   $current_question
7818
     * @param array $questions_in_media
7819
     * @param bool  $last_question_in_media
7820
     * @param array $realQuestionList
7821
     * @param bool  $generateJS
7822
     */
7823
    public function renderQuestion(
7824
        $questionId,
7825
        $attemptList,
7826
        $remindList,
7827
        $i,
7828
        $current_question,
7829
        $questions_in_media = [],
7830
        $last_question_in_media = false,
7831
        $realQuestionList = [],
7832
        $generateJS = true
7833
    ) {
7834
        // With this option on the question is loaded via AJAX
7835
        //$generateJS = true;
7836
        //$this->loadQuestionAJAX = true;
7837
7838
        if ($generateJS && $this->loadQuestionAJAX) {
7839
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq();
7840
            $params = [
7841
                'questionId' => $questionId,
7842
                'attemptList' => $attemptList,
7843
                'remindList' => $remindList,
7844
                'i' => $i,
7845
                'current_question' => $current_question,
7846
                'questions_in_media' => $questions_in_media,
7847
                'last_question_in_media' => $last_question_in_media,
7848
            ];
7849
            $params = json_encode($params);
7850
7851
            $script = '<script>
7852
            $(function(){
7853
                var params = '.$params.';
7854
                $.ajax({
7855
                    type: "GET",
7856
                    data: params,
7857
                    url: "'.$url.'",
7858
                    success: function(return_value) {
7859
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
7860
                    }
7861
                });
7862
            });
7863
            </script>
7864
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
7865
            echo $script;
7866
        } else {
7867
            global $origin;
7868
            $question_obj = Question::read($questionId);
7869
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
7870
            $remind_highlight = null;
7871
7872
            // Hides questions when reviewing a ALL_ON_ONE_PAGE exercise
7873
            // see #4542 no_remind_highlight class hide with jquery
7874
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
7875
                $remind_highlight = 'no_remind_highlight';
7876
                if (in_array($question_obj->type, Question::question_type_no_review())) {
7877
                    return null;
7878
                }
7879
            }
7880
7881
            $attributes = ['id' => 'remind_list['.$questionId.']', 'data-question-id' => $questionId];
7882
7883
            // Showing the question
7884
            $exercise_actions = null;
7885
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
7886
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
7887
7888
            // Shows the question + possible answers
7889
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
7890
            echo $this->showQuestion(
7891
                $question_obj,
7892
                false,
7893
                $origin,
7894
                $i,
7895
                $showTitle,
7896
                false,
7897
                $user_choice,
7898
                false,
7899
                null,
7900
                false,
7901
                $this->getModelType(),
7902
                $this->categoryMinusOne
7903
            );
7904
7905
            // Button save and continue
7906
            switch ($this->type) {
7907
                case ONE_PER_PAGE:
7908
                    $exercise_actions .= $this->show_button(
7909
                        $questionId,
7910
                        $current_question,
7911
                        null,
7912
                        $remindList
7913
                    );
7914
                    break;
7915
                case ALL_ON_ONE_PAGE:
7916
                    if (api_is_allowed_to_session_edit()) {
7917
                        $button = [
7918
                            Display::button(
7919
                                'save_now',
7920
                                get_lang('SaveForNow'),
7921
                                ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7922
                            ),
7923
                            '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>',
7924
                        ];
7925
                        $exercise_actions .= Display::div(
7926
                            implode(PHP_EOL, $button),
7927
                            ['class' => 'exercise_save_now_button']
7928
                        );
7929
                    }
7930
                    break;
7931
            }
7932
7933
            if (!empty($questions_in_media)) {
7934
                $count_of_questions_inside_media = count($questions_in_media);
7935
                if ($count_of_questions_inside_media > 1 && api_is_allowed_to_session_edit()) {
7936
                    $button = [
7937
                        Display::button(
7938
                            'save_now',
7939
                            get_lang('SaveForNow'),
7940
                            ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId]
7941
                        ),
7942
                        '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;',
7943
                    ];
7944
                    $exercise_actions = Display::div(
7945
                        implode(PHP_EOL, $button),
7946
                        ['class' => 'exercise_save_now_button']
7947
                    );
7948
                }
7949
7950
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
7951
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
7952
                }
7953
            }
7954
7955
            // Checkbox review answers
7956
            if ($this->review_answers &&
7957
                !in_array($question_obj->type, Question::question_type_no_review())
7958
            ) {
7959
                $remind_question_div = Display::tag(
7960
                    'label',
7961
                    Display::input(
7962
                        'checkbox',
7963
                        'remind_list['.$questionId.']',
7964
                        '',
7965
                        $attributes
7966
                    ).get_lang('ReviewQuestionLater'),
7967
                    [
7968
                        'class' => 'checkbox',
7969
                        'for' => 'remind_list['.$questionId.']',
7970
                    ]
7971
                );
7972
                $exercise_actions .= Display::div(
7973
                    $remind_question_div,
7974
                    ['class' => 'exercise_save_now_button']
7975
                );
7976
            }
7977
7978
            echo Display::div(' ', ['class' => 'clear']);
7979
7980
            $paginationCounter = null;
7981
            if ($this->type == ONE_PER_PAGE) {
7982
                if (empty($questions_in_media)) {
7983
                    $paginationCounter = Display::paginationIndicator(
7984
                        $current_question,
7985
                        count($realQuestionList)
7986
                    );
7987
                } else {
7988
                    if ($last_question_in_media) {
7989
                        $paginationCounter = Display::paginationIndicator(
7990
                            $current_question,
7991
                            count($realQuestionList)
7992
                        );
7993
                    }
7994
                }
7995
            }
7996
7997
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
7998
            echo Display::div($exercise_actions, ['class' => 'form-actions']);
7999
            echo '</div>';
8000
        }
8001
    }
8002
8003
    /**
8004
     * Returns an array of categories details for the questions of the current
8005
     * exercise.
8006
     *
8007
     * @return array
8008
     */
8009
    public function getQuestionWithCategories()
8010
    {
8011
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
8012
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
8013
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8014
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
8015
        $sql = "SELECT DISTINCT cat.*
8016
                FROM $TBL_EXERCICE_QUESTION e
8017
                INNER JOIN $TBL_QUESTIONS q
8018
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8019
                INNER JOIN $categoryRelTable catRel
8020
                ON (catRel.question_id = e.question_id AND catRel.c_id = e.c_id)
8021
                INNER JOIN $categoryTable cat
8022
                ON (cat.id = catRel.category_id AND cat.c_id = e.c_id)
8023
                WHERE
8024
                  e.c_id = {$this->course_id} AND
8025
                  e.exercice_id	= ".intval($this->id);
8026
8027
        $result = Database::query($sql);
8028
        $categoriesInExercise = [];
8029
        if (Database::num_rows($result)) {
8030
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
8031
        }
8032
8033
        return $categoriesInExercise;
8034
    }
8035
8036
    /**
8037
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option.
8038
     */
8039
    public function get_max_score()
8040
    {
8041
        $out_max_score = 0;
8042
        // list of question's id !!! the array key start at 1 !!!
8043
        $questionList = $this->selectQuestionList(true);
8044
8045
        // test is randomQuestions - see field random of test
8046
        if ($this->random > 0 && 0 == $this->randomByCat) {
8047
            $numberRandomQuestions = $this->random;
8048
            $questionScoreList = [];
8049
            foreach ($questionList as $questionId) {
8050
                $tmpobj_question = Question::read($questionId);
8051
                if (is_object($tmpobj_question)) {
8052
                    $questionScoreList[] = $tmpobj_question->weighting;
8053
                }
8054
            }
8055
8056
            rsort($questionScoreList);
8057
            // add the first $numberRandomQuestions value of score array to get max_score
8058
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
8059
                $out_max_score += $questionScoreList[$i];
8060
            }
8061
        } elseif ($this->random > 0 && $this->randomByCat > 0) {
8062
            // test is random by category
8063
            // get the $numberRandomQuestions best score question of each category
8064
            $numberRandomQuestions = $this->random;
8065
            $tab_categories_scores = [];
8066
            foreach ($questionList as $questionId) {
8067
                $question_category_id = TestCategory::getCategoryForQuestion($questionId);
8068
                if (!is_array($tab_categories_scores[$question_category_id])) {
8069
                    $tab_categories_scores[$question_category_id] = [];
8070
                }
8071
                $tmpobj_question = Question::read($questionId);
8072
                if (is_object($tmpobj_question)) {
8073
                    $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
8074
                }
8075
            }
8076
8077
            // here we've got an array with first key, the category_id, second key, score of question for this cat
8078
            foreach ($tab_categories_scores as $tab_scores) {
8079
                rsort($tab_scores);
8080
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
8081
                    $out_max_score += $tab_scores[$i];
8082
                }
8083
            }
8084
        } else {
8085
            // standard test, just add each question score
8086
            foreach ($questionList as $questionId) {
8087
                $question = Question::read($questionId, $this->course);
8088
                $out_max_score += $question->weighting;
8089
            }
8090
        }
8091
8092
        return $out_max_score;
8093
    }
8094
8095
    /**
8096
     * @return string
8097
     */
8098
    public function get_formated_title()
8099
    {
8100
        if (api_get_configuration_value('save_titles_as_html')) {
8101
        }
8102
8103
        return api_html_entity_decode($this->selectTitle());
8104
    }
8105
8106
    /**
8107
     * @param string $title
8108
     *
8109
     * @return string
8110
     */
8111
    public static function get_formated_title_variable($title)
8112
    {
8113
        return api_html_entity_decode($title);
8114
    }
8115
8116
    /**
8117
     * @return string
8118
     */
8119
    public function format_title()
8120
    {
8121
        return api_htmlentities($this->title);
8122
    }
8123
8124
    /**
8125
     * @param string $title
8126
     *
8127
     * @return string
8128
     */
8129
    public static function format_title_variable($title)
8130
    {
8131
        return api_htmlentities($title);
8132
    }
8133
8134
    /**
8135
     * @param int $courseId
8136
     * @param int $sessionId
8137
     *
8138
     * @return array exercises
8139
     */
8140
    public function getExercisesByCourseSession($courseId, $sessionId)
8141
    {
8142
        $courseId = (int) $courseId;
8143
        $sessionId = (int) $sessionId;
8144
8145
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8146
        $sql = "SELECT * FROM $tbl_quiz cq
8147
                WHERE
8148
                    cq.c_id = %s AND
8149
                    (cq.session_id = %s OR cq.session_id = 0) AND
8150
                    cq.active = 0
8151
                ORDER BY cq.id";
8152
        $sql = sprintf($sql, $courseId, $sessionId);
8153
8154
        $result = Database::query($sql);
8155
8156
        $rows = [];
8157
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8158
            $rows[] = $row;
8159
        }
8160
8161
        return $rows;
8162
    }
8163
8164
    /**
8165
     * @param int   $courseId
8166
     * @param int   $sessionId
8167
     * @param array $quizId
8168
     *
8169
     * @return array exercises
8170
     */
8171
    public function getExerciseAndResult($courseId, $sessionId, $quizId = [])
8172
    {
8173
        if (empty($quizId)) {
8174
            return [];
8175
        }
8176
8177
        $sessionId = (int) $sessionId;
8178
        $courseId = (int) $courseId;
8179
8180
        $ids = is_array($quizId) ? $quizId : [$quizId];
8181
        $ids = array_map('intval', $ids);
8182
        $ids = implode(',', $ids);
8183
        $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8184
        if (0 != $sessionId) {
8185
            $sql = "SELECT * FROM $track_exercises te
8186
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8187
              WHERE
8188
              te.id = %s AND
8189
              te.session_id = %s AND
8190
              cq.id IN (%s)
8191
              ORDER BY cq.id";
8192
8193
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
8194
        } else {
8195
            $sql = "SELECT * FROM $track_exercises te
8196
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8197
              WHERE
8198
              te.id = %s AND
8199
              cq.id IN (%s)
8200
              ORDER BY cq.id";
8201
            $sql = sprintf($sql, $courseId, $ids);
8202
        }
8203
        $result = Database::query($sql);
8204
        $rows = [];
8205
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8206
            $rows[] = $row;
8207
        }
8208
8209
        return $rows;
8210
    }
8211
8212
    /**
8213
     * @param $exeId
8214
     * @param $exercise_stat_info
8215
     * @param $remindList
8216
     * @param $currentQuestion
8217
     *
8218
     * @return int|null
8219
     */
8220
    public static function getNextQuestionId(
8221
        $exeId,
8222
        $exercise_stat_info,
8223
        $remindList,
8224
        $currentQuestion
8225
    ) {
8226
        $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete');
8227
8228
        if (isset($result[$exeId])) {
8229
            $result = $result[$exeId];
8230
        } else {
8231
            return null;
8232
        }
8233
8234
        $data_tracking = $exercise_stat_info['data_tracking'];
8235
        $data_tracking = explode(',', $data_tracking);
8236
8237
        // if this is the final question do nothing.
8238
        if ($currentQuestion == count($data_tracking)) {
8239
            return null;
8240
        }
8241
8242
        $currentQuestion--;
8243
8244
        if (!empty($result['question_list'])) {
8245
            $answeredQuestions = [];
8246
            foreach ($result['question_list'] as $question) {
8247
                if (!empty($question['answer'])) {
8248
                    $answeredQuestions[] = $question['question_id'];
8249
                }
8250
            }
8251
8252
            // Checking answered questions
8253
            $counterAnsweredQuestions = 0;
8254
            foreach ($data_tracking as $questionId) {
8255
                if (!in_array($questionId, $answeredQuestions)) {
8256
                    if ($currentQuestion != $counterAnsweredQuestions) {
8257
                        break;
8258
                    }
8259
                }
8260
                $counterAnsweredQuestions++;
8261
            }
8262
8263
            $counterRemindListQuestions = 0;
8264
            // Checking questions saved in the reminder list
8265
            if (!empty($remindList)) {
8266
                foreach ($data_tracking as $questionId) {
8267
                    if (in_array($questionId, $remindList)) {
8268
                        // Skip the current question
8269
                        if ($currentQuestion != $counterRemindListQuestions) {
8270
                            break;
8271
                        }
8272
                    }
8273
                    $counterRemindListQuestions++;
8274
                }
8275
8276
                if ($counterRemindListQuestions < $currentQuestion) {
8277
                    return null;
8278
                }
8279
8280
                if (!empty($counterRemindListQuestions)) {
8281
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
8282
                        return $counterAnsweredQuestions;
8283
                    } else {
8284
                        return $counterRemindListQuestions;
8285
                    }
8286
                }
8287
            }
8288
8289
            return $counterAnsweredQuestions;
8290
        }
8291
    }
8292
8293
    /**
8294
     * Gets the position of a questionId in the question list.
8295
     *
8296
     * @param $questionId
8297
     *
8298
     * @return int
8299
     */
8300
    public function getPositionInCompressedQuestionList($questionId)
8301
    {
8302
        $questionList = $this->getQuestionListWithMediasCompressed();
8303
        $mediaQuestions = $this->getMediaList();
8304
        $position = 1;
8305
        foreach ($questionList as $id) {
8306
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
8307
                $mediaQuestionList = $mediaQuestions[$id];
8308
                if (in_array($questionId, $mediaQuestionList)) {
8309
                    return $position;
8310
                } else {
8311
                    $position++;
8312
                }
8313
            } else {
8314
                if ($id == $questionId) {
8315
                    return $position;
8316
                } else {
8317
                    $position++;
8318
                }
8319
            }
8320
        }
8321
8322
        return 1;
8323
    }
8324
8325
    /**
8326
     * Get the correct answers in all attempts.
8327
     *
8328
     * @param int  $learnPathId
8329
     * @param int  $learnPathItemId
8330
     * @param bool $onlyCorrect
8331
     *
8332
     * @return array
8333
     */
8334
    public function getAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0, $onlyCorrect = true)
8335
    {
8336
        $attempts = Event::getExerciseResultsByUser(
8337
            api_get_user_id(),
8338
            $this->id,
8339
            api_get_course_int_id(),
8340
            api_get_session_id(),
8341
            $learnPathId,
8342
            $learnPathItemId,
8343
            'DESC'
8344
        );
8345
8346
        $list = [];
8347
        foreach ($attempts as $attempt) {
8348
            foreach ($attempt['question_list'] as $answers) {
8349
                foreach ($answers as $answer) {
8350
                    $objAnswer = new Answer($answer['question_id']);
8351
                    if ($onlyCorrect) {
8352
                        switch ($objAnswer->getQuestionType()) {
8353
                            case FILL_IN_BLANKS:
8354
                                $isCorrect = FillBlanks::isCorrect($answer['answer']);
8355
                                break;
8356
                            case MATCHING:
8357
                            case DRAGGABLE:
8358
                            case MATCHING_DRAGGABLE:
8359
                                $isCorrect = Matching::isCorrect(
8360
                                    $answer['position'],
8361
                                    $answer['answer'],
8362
                                    $answer['question_id']
8363
                                );
8364
                                break;
8365
                            case ORAL_EXPRESSION:
8366
                                $isCorrect = false;
8367
                                break;
8368
                            default:
8369
                                $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
8370
                        }
8371
                        if ($isCorrect) {
8372
                            $list[$answer['question_id']][] = $answer;
8373
                        }
8374
                    } else {
8375
                        $list[$answer['question_id']][] = $answer;
8376
                    }
8377
                }
8378
            }
8379
8380
            if (false === $onlyCorrect) {
8381
                // Only take latest attempt
8382
                break;
8383
            }
8384
        }
8385
8386
        return $list;
8387
    }
8388
8389
    /**
8390
     * Get the correct answers in all attempts.
8391
     *
8392
     * @param int $learnPathId
8393
     * @param int $learnPathItemId
8394
     *
8395
     * @return array
8396
     */
8397
    public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0)
8398
    {
8399
        return $this->getAnswersInAllAttempts($learnPathId, $learnPathItemId);
8400
    }
8401
8402
    /**
8403
     * @return bool
8404
     */
8405
    public function showPreviousButton()
8406
    {
8407
        $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting');
8408
        if (false === $allow) {
8409
            return true;
8410
        }
8411
8412
        return $this->showPreviousButton;
8413
    }
8414
8415
    public function getPreventBackwards()
8416
    {
8417
        $allow = api_get_configuration_value('quiz_prevent_backwards_move');
8418
        if (false === $allow) {
8419
            return 0;
8420
        }
8421
8422
        return (int) $this->preventBackwards;
8423
    }
8424
8425
    /**
8426
     * @return int
8427
     */
8428
    public function getExerciseCategoryId()
8429
    {
8430
        if (empty($this->exerciseCategoryId)) {
8431
            return null;
8432
        }
8433
8434
        return (int) $this->exerciseCategoryId;
8435
    }
8436
8437
    /**
8438
     * @param int $value
8439
     */
8440
    public function setExerciseCategoryId($value)
8441
    {
8442
        if (!empty($value)) {
8443
            $this->exerciseCategoryId = (int) $value;
8444
        }
8445
    }
8446
8447
    /**
8448
     * @param array $values
8449
     */
8450
    public function setPageResultConfiguration($values)
8451
    {
8452
        $pageConfig = api_get_configuration_value('allow_quiz_results_page_config');
8453
        if ($pageConfig) {
8454
            $params = [
8455
                'hide_expected_answer' => $values['hide_expected_answer'] ?? '',
8456
                'hide_question_score' => $values['hide_question_score'] ?? '',
8457
                'hide_total_score' => $values['hide_total_score'] ?? '',
8458
                'hide_category_table' => $values['hide_category_table'] ?? '',
8459
                'hide_correct_answered_questions' => $values['hide_correct_answered_questions'] ?? '',
8460
            ];
8461
            $type = Type::getType('array');
8462
            $platform = Database::getManager()->getConnection()->getDatabasePlatform();
8463
            $this->pageResultConfiguration = $type->convertToDatabaseValue($params, $platform);
8464
        }
8465
    }
8466
8467
    /**
8468
     * @param array $defaults
8469
     */
8470
    public function setPageResultConfigurationDefaults(&$defaults)
8471
    {
8472
        $configuration = $this->getPageResultConfiguration();
8473
        if (!empty($configuration) && !empty($defaults)) {
8474
            $defaults = array_merge($defaults, $configuration);
8475
        }
8476
    }
8477
8478
    /**
8479
     * @return array
8480
     */
8481
    public function getPageResultConfiguration()
8482
    {
8483
        $pageConfig = api_get_configuration_value('allow_quiz_results_page_config');
8484
        if ($pageConfig) {
8485
            $type = Type::getType('array');
8486
            $platform = Database::getManager()->getConnection()->getDatabasePlatform();
8487
8488
            return $type->convertToPHPValue($this->pageResultConfiguration, $platform);
8489
        }
8490
8491
        return [];
8492
    }
8493
8494
    /**
8495
     * @param string $attribute
8496
     *
8497
     * @return mixed|null
8498
     */
8499
    public function getPageConfigurationAttribute($attribute)
8500
    {
8501
        $result = $this->getPageResultConfiguration();
8502
8503
        if (!empty($result)) {
8504
            return isset($result[$attribute]) ? $result[$attribute] : null;
8505
        }
8506
8507
        return null;
8508
    }
8509
8510
    /**
8511
     * @param bool $showPreviousButton
8512
     *
8513
     * @return Exercise
8514
     */
8515
    public function setShowPreviousButton($showPreviousButton)
8516
    {
8517
        $this->showPreviousButton = $showPreviousButton;
8518
8519
        return $this;
8520
    }
8521
8522
    /**
8523
     * @param array $notifications
8524
     */
8525
    public function setNotifications($notifications)
8526
    {
8527
        $this->notifications = $notifications;
8528
    }
8529
8530
    /**
8531
     * @return array
8532
     */
8533
    public function getNotifications()
8534
    {
8535
        return $this->notifications;
8536
    }
8537
8538
    /**
8539
     * @return bool
8540
     */
8541
    public function showExpectedChoice()
8542
    {
8543
        return api_get_configuration_value('show_exercise_expected_choice');
8544
    }
8545
8546
    /**
8547
     * @return bool
8548
     */
8549
    public function showExpectedChoiceColumn()
8550
    {
8551
        if (true === $this->forceShowExpectedChoiceColumn) {
8552
            return true;
8553
        }
8554
8555
        if ($this->hideExpectedAnswer) {
8556
            return false;
8557
        }
8558
8559
        if (!in_array($this->results_disabled, [
8560
            RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
8561
        ])
8562
        ) {
8563
            $hide = (int) $this->getPageConfigurationAttribute('hide_expected_answer');
8564
            if (1 === $hide) {
8565
                return false;
8566
            }
8567
8568
            return true;
8569
        }
8570
8571
        return false;
8572
    }
8573
8574
    /**
8575
     * @param string $class
8576
     * @param string $scoreLabel
8577
     * @param string $result
8578
     * @param array
8579
     *
8580
     * @return string
8581
     */
8582
    public function getQuestionRibbon($class, $scoreLabel, $result, $array)
8583
    {
8584
        $hide = (int) $this->getPageConfigurationAttribute('hide_question_score');
8585
        if (1 === $hide) {
8586
            return '';
8587
        }
8588
8589
        if ($this->showExpectedChoice()) {
8590
            $html = null;
8591
            $hideLabel = api_get_configuration_value('exercise_hide_label');
8592
            $label = '<div class="rib rib-'.$class.'">
8593
                        <h3>'.$scoreLabel.'</h3>
8594
                      </div>';
8595
            if (!empty($result)) {
8596
                $label .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
8597
            }
8598
            if (true === $hideLabel) {
8599
                $answerUsed = (int) $array['used'];
8600
                $answerMissing = (int) $array['missing'] - $answerUsed;
8601
                for ($i = 1; $i <= $answerUsed; $i++) {
8602
                    $html .= '<span class="score-img">'.
8603
                        Display::return_icon('attempt-check.png', null, null, ICON_SIZE_SMALL).
8604
                        '</span>';
8605
                }
8606
                for ($i = 1; $i <= $answerMissing; $i++) {
8607
                    $html .= '<span class="score-img">'.
8608
                        Display::return_icon('attempt-nocheck.png', null, null, ICON_SIZE_SMALL).
8609
                        '</span>';
8610
                }
8611
                $label = '<div class="score-title">'.get_lang('CorrectAnswers').': '.$result.'</div>';
8612
                $label .= '<div class="score-limits">';
8613
                $label .= $html;
8614
                $label .= '</div>';
8615
            }
8616
8617
            return '<div class="ribbon">
8618
                '.$label.'
8619
                </div>'
8620
                ;
8621
        } else {
8622
            $html = '<div class="ribbon">
8623
                        <div class="rib rib-'.$class.'">
8624
                            <h3>'.$scoreLabel.'</h3>
8625
                        </div>';
8626
            if (!empty($result)) {
8627
                $html .= '<h4>'.get_lang('Score').': '.$result.'</h4>';
8628
            }
8629
            $html .= '</div>';
8630
8631
            return $html;
8632
        }
8633
    }
8634
8635
    /**
8636
     * @return int
8637
     */
8638
    public function getAutoLaunch()
8639
    {
8640
        return $this->autolaunch;
8641
    }
8642
8643
    /**
8644
     * Clean auto launch settings for all exercise in course/course-session.
8645
     */
8646
    public function enableAutoLaunch()
8647
    {
8648
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
8649
        $sql = "UPDATE $table SET autolaunch = 1
8650
                WHERE iid = ".$this->iId;
8651
        Database::query($sql);
8652
    }
8653
8654
    /**
8655
     * Clean auto launch settings for all exercise in course/course-session.
8656
     */
8657
    public function cleanCourseLaunchSettings()
8658
    {
8659
        $table = Database::get_course_table(TABLE_QUIZ_TEST);
8660
        $sql = "UPDATE $table SET autolaunch = 0
8661
                WHERE c_id = ".$this->course_id." AND session_id = ".$this->sessionId;
8662
        Database::query($sql);
8663
    }
8664
8665
    /**
8666
     * Get the title without HTML tags.
8667
     *
8668
     * @return string
8669
     */
8670
    public function getUnformattedTitle()
8671
    {
8672
        return strip_tags(api_html_entity_decode($this->title));
8673
    }
8674
8675
    /**
8676
     * Get the question IDs from quiz_rel_question for the current quiz,
8677
     * using the parameters as the arguments to the SQL's LIMIT clause.
8678
     * Because the exercise_id is known, it also comes with a filter on
8679
     * the session, so sessions are not specified here.
8680
     *
8681
     * @param int $start  At which question do we want to start the list
8682
     * @param int $length Up to how many results we want
8683
     *
8684
     * @return array A list of question IDs
8685
     */
8686
    public function getQuestionForTeacher($start = 0, $length = 10)
8687
    {
8688
        $start = (int) $start;
8689
        if ($start < 0) {
8690
            $start = 0;
8691
        }
8692
8693
        $length = (int) $length;
8694
8695
        $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8696
        $question = Database::get_course_table(TABLE_QUIZ_QUESTION);
8697
        $sql = "SELECT DISTINCT e.question_id
8698
                FROM $quizRelQuestion e
8699
                INNER JOIN $question q
8700
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8701
                WHERE
8702
                    e.c_id = {$this->course_id} AND
8703
                    e.exercice_id = '".$this->id."'
8704
                ORDER BY question_order
8705
                LIMIT $start, $length
8706
            ";
8707
        $result = Database::query($sql);
8708
        $questionList = [];
8709
        while ($object = Database::fetch_object($result)) {
8710
            $questionList[] = $object->question_id;
8711
        }
8712
8713
        return $questionList;
8714
    }
8715
8716
    /**
8717
     * @param int   $exerciseId
8718
     * @param array $courseInfo
8719
     * @param int   $sessionId
8720
     *
8721
     * @return bool
8722
     */
8723
    public function generateStats($exerciseId, $courseInfo, $sessionId)
8724
    {
8725
        $allowStats = api_get_configuration_value('allow_gradebook_stats');
8726
        if (!$allowStats) {
8727
            return false;
8728
        }
8729
8730
        if (empty($courseInfo)) {
8731
            return false;
8732
        }
8733
8734
        $courseId = $courseInfo['real_id'];
8735
8736
        $sessionId = (int) $sessionId;
8737
        $exerciseId = (int) $exerciseId;
8738
8739
        $result = $this->read($exerciseId);
8740
8741
        if (empty($result)) {
8742
            api_not_allowed(true);
8743
        }
8744
8745
        $statusToFilter = empty($sessionId) ? STUDENT : 0;
8746
8747
        $studentList = CourseManager::get_user_list_from_course_code(
8748
            $courseInfo['code'],
8749
            $sessionId,
8750
            null,
8751
            null,
8752
            $statusToFilter
8753
        );
8754
8755
        if (empty($studentList)) {
8756
            Display::addFlash(Display::return_message(get_lang('NoUsersInCourse')));
8757
            header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq());
8758
            exit;
8759
        }
8760
8761
        $tblStats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8762
8763
        $studentIdList = [];
8764
        if (!empty($studentList)) {
8765
            $studentIdList = array_column($studentList, 'user_id');
8766
        }
8767
8768
        if (false == $this->exercise_was_added_in_lp) {
8769
            $sql = "SELECT * FROM $tblStats
8770
                        WHERE
8771
                            exe_exo_id = $exerciseId AND
8772
                            orig_lp_id = 0 AND
8773
                            orig_lp_item_id = 0 AND
8774
                            status <> 'incomplete' AND
8775
                            session_id = $sessionId AND
8776
                            c_id = $courseId
8777
                        ";
8778
        } else {
8779
            $lpId = null;
8780
            if (!empty($this->lpList)) {
8781
                // Taking only the first LP
8782
                $lpId = $this->getLpBySession($sessionId);
8783
                $lpId = $lpId['lp_id'];
8784
            }
8785
8786
            $sql = "SELECT *
8787
                        FROM $tblStats
8788
                        WHERE
8789
                            exe_exo_id = $exerciseId AND
8790
                            orig_lp_id = $lpId AND
8791
                            status <> 'incomplete' AND
8792
                            session_id = $sessionId AND
8793
                            c_id = $courseId ";
8794
        }
8795
8796
        $sql .= ' ORDER BY exe_id DESC';
8797
8798
        $studentCount = 0;
8799
        $sum = 0;
8800
        $bestResult = 0;
8801
        $sumResult = 0;
8802
        $result = Database::query($sql);
8803
        $students = [];
8804
        while ($data = Database::fetch_array($result, 'ASSOC')) {
8805
            // Only take into account users in the current student list.
8806
            if (!empty($studentIdList)) {
8807
                if (!in_array($data['exe_user_id'], $studentIdList)) {
8808
                    continue;
8809
                }
8810
            }
8811
8812
            if (!isset($students[$data['exe_user_id']])) {
8813
                if ($data['exe_weighting'] != 0) {
8814
                    $students[$data['exe_user_id']] = $data['exe_result'];
8815
                    if ($data['exe_result'] > $bestResult) {
8816
                        $bestResult = $data['exe_result'];
8817
                    }
8818
                    $sumResult += $data['exe_result'];
8819
                }
8820
            }
8821
        }
8822
8823
        $count = count($studentList);
8824
        $average = $sumResult / $count;
8825
        $em = Database::getManager();
8826
8827
        $links = AbstractLink::getGradebookLinksFromItem(
8828
            $this->id,
8829
            LINK_EXERCISE,
8830
            $courseInfo['code'],
8831
            $sessionId
8832
        );
8833
8834
        if (empty($links)) {
8835
            $links = AbstractLink::getGradebookLinksFromItem(
8836
                $this->iId,
8837
                LINK_EXERCISE,
8838
                $courseInfo['code'],
8839
                $sessionId
8840
            );
8841
        }
8842
8843
        if (!empty($links)) {
8844
            $repo = $em->getRepository('ChamiloCoreBundle:GradebookLink');
8845
8846
            foreach ($links as $link) {
8847
                $linkId = $link['id'];
8848
                /** @var GradebookLink $exerciseLink */
8849
                $exerciseLink = $repo->find($linkId);
8850
                if ($exerciseLink) {
8851
                    $exerciseLink
8852
                        ->setUserScoreList($students)
8853
                        ->setBestScore($bestResult)
8854
                        ->setAverageScore($average)
8855
                        ->setScoreWeight($this->get_max_score());
8856
                    $em->persist($exerciseLink);
8857
                    $em->flush();
8858
                }
8859
            }
8860
        }
8861
    }
8862
8863
    /**
8864
     * Return an HTML table of exercises for on-screen printing, including
8865
     * action icons. If no exercise is present and the user can edit the
8866
     * course, show a "create test" button.
8867
     *
8868
     * @param int    $categoryId
8869
     * @param string $keyword
8870
     * @param int    $userId
8871
     * @param int    $courseId
8872
     * @param int    $sessionId
8873
     * @param bool   $returnData
8874
     * @param int    $minCategoriesInExercise
8875
     * @param int    $filterByResultDisabled
8876
     * @param int    $filterByAttempt
8877
     *
8878
     * @return string|SortableTableFromArrayConfig
8879
     */
8880
    public static function exerciseGrid(
8881
        $categoryId,
8882
        $keyword = '',
8883
        $userId = 0,
8884
        $courseId = 0,
8885
        $sessionId = 0,
8886
        $returnData = false,
8887
        $minCategoriesInExercise = 0,
8888
        $filterByResultDisabled = 0,
8889
        $filterByAttempt = 0,
8890
        $myActions = null,
8891
        $returnTable = false
8892
    ) {
8893
        //$allowDelete = Exercise::allowAction('delete');
8894
        $allowClean = self::allowAction('clean_results');
8895
8896
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
8897
        $TBL_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
8898
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8899
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
8900
        $TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8901
8902
        $categoryId = (int) $categoryId;
8903
        $keyword = Database::escape_string($keyword);
8904
        $learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : null;
8905
        $learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : null;
8906
8907
        $autoLaunchAvailable = false;
8908
        if (api_get_course_setting('enable_exercise_auto_launch') == 1 &&
8909
            api_get_configuration_value('allow_exercise_auto_launch')
8910
        ) {
8911
            $autoLaunchAvailable = true;
8912
        }
8913
8914
        $is_allowedToEdit = api_is_allowed_to_edit(null, true);
8915
        $courseInfo = $courseId ? api_get_course_info_by_id($courseId) : api_get_course_info();
8916
        $sessionId = $sessionId ? (int) $sessionId : api_get_session_id();
8917
        $courseId = $courseInfo['real_id'];
8918
        $tableRows = [];
8919
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
8920
        $exercisePath = api_get_self();
8921
        $origin = api_get_origin();
8922
        $userInfo = $userId ? api_get_user_info($userId) : api_get_user_info();
8923
        $charset = 'utf-8';
8924
        $token = Security::get_token();
8925
        $userId = $userId ? (int) $userId : api_get_user_id();
8926
        $isDrhOfCourse = CourseManager::isUserSubscribedInCourseAsDrh($userId, $courseInfo);
8927
        $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
8928
        $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
8929
8930
        // Condition for the session
8931
        $condition_session = api_get_session_condition($sessionId, true, true, 'e.session_id');
8932
        $content = '';
8933
        $column = 0;
8934
        if ($is_allowedToEdit) {
8935
            $column = 1;
8936
        }
8937
8938
        $table = new SortableTableFromArrayConfig(
8939
            [],
8940
            $column,
8941
            self::PAGINATION_ITEMS_PER_PAGE,
8942
            'exercises_cat_'.$categoryId
8943
        );
8944
8945
        $limit = $table->per_page;
8946
        $page = $table->page_nr;
8947
        $from = $limit * ($page - 1);
8948
8949
        $categoryCondition = '';
8950
        if (api_get_configuration_value('allow_exercise_categories')) {
8951
            if (!empty($categoryId)) {
8952
                $categoryCondition = " AND exercise_category_id = $categoryId ";
8953
            } else {
8954
                $categoryCondition = ' AND exercise_category_id IS NULL ';
8955
            }
8956
        }
8957
8958
        $keywordCondition = '';
8959
        if (!empty($keyword)) {
8960
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8961
        }
8962
8963
        $filterByResultDisabledCondition = '';
8964
        $filterByResultDisabled = (int) $filterByResultDisabled;
8965
        if (!empty($filterByResultDisabled)) {
8966
            $filterByResultDisabledCondition = ' AND e.results_disabled = '.$filterByResultDisabled;
8967
        }
8968
        $filterByAttemptCondition = '';
8969
        $filterByAttempt = (int) $filterByAttempt;
8970
        if (!empty($filterByAttempt)) {
8971
            $filterByAttemptCondition = ' AND e.max_attempt = '.$filterByAttempt;
8972
        }
8973
8974
        // Only for administrators
8975
        if ($is_allowedToEdit) {
8976
            $total_sql = "SELECT count(iid) as count
8977
                          FROM $TBL_EXERCISES e
8978
                          WHERE
8979
                                c_id = $courseId AND
8980
                                active <> -1
8981
                                $condition_session
8982
                                $categoryCondition
8983
                                $keywordCondition
8984
                                $filterByResultDisabledCondition
8985
                                $filterByAttemptCondition
8986
                                ";
8987
            $sql = "SELECT * FROM $TBL_EXERCISES e
8988
                    WHERE
8989
                        c_id = $courseId AND
8990
                        active <> -1
8991
                        $condition_session
8992
                        $categoryCondition
8993
                        $keywordCondition
8994
                        $filterByResultDisabledCondition
8995
                        $filterByAttemptCondition
8996
                    ORDER BY title
8997
                    LIMIT $from , $limit";
8998
        } else {
8999
            // Only for students
9000
            if (empty($sessionId)) {
9001
                $condition_session = ' AND ( session_id = 0 OR session_id IS NULL) ';
9002
                $total_sql = "SELECT count(DISTINCT(e.iid)) as count
9003
                              FROM $TBL_EXERCISES e
9004
                              WHERE
9005
                                    e.c_id = $courseId AND
9006
                                    e.active = 1
9007
                                    $condition_session
9008
                                    $categoryCondition
9009
                                    $keywordCondition
9010
                              ";
9011
9012
                $sql = "SELECT DISTINCT e.* FROM $TBL_EXERCISES e
9013
                        WHERE
9014
                             e.c_id = $courseId AND
9015
                             e.active = 1
9016
                             $condition_session
9017
                             $categoryCondition
9018
                             $keywordCondition
9019
                        ORDER BY title
9020
                        LIMIT $from , $limit";
9021
            } else {
9022
                $invisibleSql = "SELECT e.iid
9023
                                  FROM $TBL_EXERCISES e
9024
                                  INNER JOIN $TBL_ITEM_PROPERTY ip
9025
                                  ON (e.id = ip.ref AND e.c_id = ip.c_id)
9026
                                  WHERE
9027
                                        ip.tool = '".TOOL_QUIZ."' AND
9028
                                        e.c_id = $courseId AND
9029
                                        e.active = 1 AND
9030
                                        ip.visibility = 0 AND
9031
                                        ip.session_id = $sessionId
9032
                                        $categoryCondition
9033
                                        $keywordCondition
9034
                                  ";
9035
9036
                $result = Database::query($invisibleSql);
9037
                $result = Database::store_result($result);
9038
                $hiddenFromSessionCondition = ' 1=1 ';
9039
                if (!empty($result)) {
9040
                    $hiddenFromSession = implode("','", array_column($result, 'iid'));
9041
                    $hiddenFromSessionCondition = " e.iid not in ('$hiddenFromSession')";
9042
                }
9043
9044
                $condition_session = " AND (
9045
                        (e.session_id = $sessionId OR e.session_id = 0 OR e.session_id IS NULL) AND
9046
                        $hiddenFromSessionCondition
9047
                )
9048
                ";
9049
9050
                // Only for students
9051
                $total_sql = "SELECT count(DISTINCT(e.iid)) as count
9052
                              FROM $TBL_EXERCISES e
9053
                              WHERE
9054
                                    e.c_id = $courseId AND
9055
                                    e.active = 1
9056
                                    $condition_session
9057
                                    $categoryCondition
9058
                                    $keywordCondition
9059
                              ";
9060
                $sql = "SELECT DISTINCT e.* FROM $TBL_EXERCISES e
9061
                        WHERE
9062
                             e.c_id = $courseId AND
9063
                             e.active = 1
9064
                             $condition_session
9065
                             $categoryCondition
9066
                             $keywordCondition
9067
                        ORDER BY title
9068
                        LIMIT $from , $limit";
9069
            }
9070
        }
9071
9072
        $result = Database::query($sql);
9073
        $result_total = Database::query($total_sql);
9074
9075
        $total_exercises = 0;
9076
        if (Database::num_rows($result_total)) {
9077
            $result_total = Database::fetch_array($result_total);
9078
            $total_exercises = $result_total['count'];
9079
        }
9080
9081
        //get HotPotatoes files (active and inactive)
9082
        if ($is_allowedToEdit) {
9083
            $sql = "SELECT * FROM $TBL_DOCUMENT
9084
                    WHERE
9085
                        c_id = $courseId AND
9086
                        path LIKE '".Database::escape_string($uploadPath.'/%/%')."'";
9087
            $res = Database::query($sql);
9088
            $hp_count = Database :: num_rows($res);
9089
        } else {
9090
            $sql = "SELECT * FROM $TBL_DOCUMENT d
9091
                    INNER JOIN $TBL_ITEM_PROPERTY ip
9092
                    ON (d.id = ip.ref AND d.c_id = ip.c_id)
9093
                    WHERE
9094
                        ip.tool = '".TOOL_DOCUMENT."' AND
9095
                        d.path LIKE '".Database::escape_string($uploadPath.'/%/%')."' AND
9096
                        ip.visibility = 1 AND
9097
                        d.c_id = $courseId AND
9098
                        ip.c_id  = $courseId";
9099
            $res = Database::query($sql);
9100
            $hp_count = Database::num_rows($res);
9101
        }
9102
9103
        $total = $total_exercises + $hp_count;
9104
        $exerciseList = [];
9105
        while ($row = Database::fetch_array($result, 'ASSOC')) {
9106
            $exerciseList[] = $row;
9107
        }
9108
9109
        if (!empty($exerciseList) &&
9110
            api_get_setting('exercise_invisible_in_session') === 'true'
9111
        ) {
9112
            if (!empty($sessionId)) {
9113
                $changeDefaultVisibility = true;
9114
                if (api_get_setting('configure_exercise_visibility_in_course') === 'true') {
9115
                    $changeDefaultVisibility = false;
9116
                    if (api_get_course_setting('exercise_invisible_in_session') == 1) {
9117
                        $changeDefaultVisibility = true;
9118
                    }
9119
                }
9120
9121
                if ($changeDefaultVisibility) {
9122
                    // Check exercise
9123
                    foreach ($exerciseList as $exercise) {
9124
                        if ($exercise['session_id'] == 0) {
9125
                            $visibilityInfo = api_get_item_property_info(
9126
                                $courseId,
9127
                                TOOL_QUIZ,
9128
                                $exercise['iid'],
9129
                                $sessionId
9130
                            );
9131
9132
                            if (empty($visibilityInfo)) {
9133
                                // Create a record for this
9134
                                api_item_property_update(
9135
                                    $courseInfo,
9136
                                    TOOL_QUIZ,
9137
                                    $exercise['iid'],
9138
                                    'invisible',
9139
                                    api_get_user_id(),
9140
                                    0,
9141
                                    null,
9142
                                    '',
9143
                                    '',
9144
                                    $sessionId
9145
                                );
9146
                            }
9147
                        }
9148
                    }
9149
                }
9150
            }
9151
        }
9152
9153
        $webPath = api_get_path(WEB_CODE_PATH);
9154
9155
        if (!empty($exerciseList)) {
9156
            if ($origin !== 'learnpath') {
9157
                $visibilitySetting = api_get_configuration_value('show_hidden_exercise_added_to_lp');
9158
                //avoid sending empty parameters
9159
                $mylpid = empty($learnpath_id) ? '' : '&learnpath_id='.$learnpath_id;
9160
                $mylpitemid = empty($learnpath_item_id) ? '' : '&learnpath_item_id='.$learnpath_item_id;
9161
                foreach ($exerciseList as $row) {
9162
                    $currentRow = [];
9163
                    $my_exercise_id = $row['id'];
9164
                    $attempt_text = '';
9165
                    $actions = '';
9166
                    $exercise = new Exercise($returnData ? $courseId : 0);
9167
                    $exercise->read($my_exercise_id, false);
9168
9169
                    if (empty($exercise->id)) {
9170
                        continue;
9171
                    }
9172
9173
                    $locked = $exercise->is_gradebook_locked;
9174
                    // Validation when belongs to a session
9175
                    $session_img = api_get_session_image($row['session_id'], $userInfo['status']);
9176
9177
                    $time_limits = false;
9178
                    if (!empty($row['start_time']) || !empty($row['end_time'])) {
9179
                        $time_limits = true;
9180
                    }
9181
9182
                    $is_actived_time = false;
9183
                    if ($time_limits) {
9184
                        // check if start time
9185
                        $start_time = false;
9186
                        if (!empty($row['start_time'])) {
9187
                            $start_time = api_strtotime($row['start_time'], 'UTC');
9188
                        }
9189
                        $end_time = false;
9190
                        if (!empty($row['end_time'])) {
9191
                            $end_time = api_strtotime($row['end_time'], 'UTC');
9192
                        }
9193
                        $now = time();
9194
9195
                        //If both "clocks" are enable
9196
                        if ($start_time && $end_time) {
9197
                            if ($now > $start_time && $end_time > $now) {
9198
                                $is_actived_time = true;
9199
                            }
9200
                        } else {
9201
                            //we check the start and end
9202
                            if ($start_time) {
9203
                                if ($now > $start_time) {
9204
                                    $is_actived_time = true;
9205
                                }
9206
                            }
9207
                            if ($end_time) {
9208
                                if ($end_time > $now) {
9209
                                    $is_actived_time = true;
9210
                                }
9211
                            }
9212
                        }
9213
                    }
9214
9215
                    // Blocking empty start times see BT#2800
9216
                    global $_custom;
9217
                    if (isset($_custom['exercises_hidden_when_no_start_date']) &&
9218
                        $_custom['exercises_hidden_when_no_start_date']
9219
                    ) {
9220
                        if (empty($row['start_time'])) {
9221
                            $time_limits = true;
9222
                            $is_actived_time = false;
9223
                        }
9224
                    }
9225
9226
                    $cut_title = $exercise->getCutTitle();
9227
                    $alt_title = '';
9228
                    if ($cut_title != $row['title']) {
9229
                        $alt_title = ' title = "'.$exercise->getUnformattedTitle().'" ';
9230
                    }
9231
9232
                    // Teacher only
9233
                    if ($is_allowedToEdit) {
9234
                        $lp_blocked = null;
9235
                        if ($exercise->exercise_was_added_in_lp == true) {
9236
                            $lp_blocked = Display::div(
9237
                                get_lang('AddedToLPCannotBeAccessed'),
9238
                                ['class' => 'lp_content_type_label']
9239
                            );
9240
                        }
9241
9242
                        // Get visibility in base course
9243
                        $visibility = api_get_item_visibility(
9244
                            $courseInfo,
9245
                            TOOL_QUIZ,
9246
                            $my_exercise_id,
9247
                            0
9248
                        );
9249
9250
                        if (!empty($sessionId)) {
9251
                            // If we are in a session, the test is invisible
9252
                            // in the base course, it is included in a LP
9253
                            // *and* the setting to show it is *not*
9254
                            // specifically set to true, then hide it.
9255
                            if ($visibility == 0) {
9256
                                if (!$visibilitySetting) {
9257
                                    if ($exercise->exercise_was_added_in_lp == true) {
9258
                                        continue;
9259
                                    }
9260
                                }
9261
                            }
9262
9263
                            $visibility = api_get_item_visibility(
9264
                                $courseInfo,
9265
                                TOOL_QUIZ,
9266
                                $my_exercise_id,
9267
                                $sessionId
9268
                            );
9269
                        }
9270
9271
                        if ($row['active'] == 0 || $visibility == 0) {
9272
                            $title = Display::tag('font', $cut_title, ['style' => 'color:grey']);
9273
                        } else {
9274
                            $title = $cut_title;
9275
                        }
9276
9277
                        /*$count_exercise_not_validated = (int) Event::count_exercise_result_not_validated(
9278
                            $my_exercise_id,
9279
                            $courseId,
9280
                            $sessionId
9281
                        );*/
9282
                        $move = null;
9283
                        $class_tip = '';
9284
                        /*if (!empty($count_exercise_not_validated)) {
9285
                            $results_text = $count_exercise_not_validated == 1 ? get_lang('ResultNotRevised') : get_lang('ResultsNotRevised');
9286
                            $title .= '<span class="exercise_tooltip" style="display: none;">'.$count_exercise_not_validated.' '.$results_text.' </span>';
9287
                        }*/
9288
                        $overviewUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php';
9289
                        $url = $move.
9290
                            '<a
9291
                                '.$alt_title.'
9292
                                class="'.$class_tip.'"
9293
                                id="tooltip_'.$row['id'].'"
9294
                                href="'.$overviewUrl.'?'.api_get_cidreq().$mylpid.$mylpitemid.'&exerciseId='.$row['id'].'"
9295
                            >
9296
                             '.Display::return_icon('quiz.png', $row['title']).'
9297
                             '.$title.'
9298
                             </a>'.PHP_EOL;
9299
9300
                        if (ExerciseLib::isQuizEmbeddable($row)) {
9301
                            $embeddableIcon = Display::return_icon('om_integration.png', get_lang('ThisQuizCanBeEmbeddable'));
9302
                            $url .= Display::div($embeddableIcon, ['class' => 'pull-right']);
9303
                        }
9304
9305
                        $currentRow['title'] = $url.' '.$session_img.$lp_blocked;
9306
9307
                        // Count number exercise - teacher
9308
                        $sql = "SELECT count(*) count FROM $TBL_EXERCISE_QUESTION
9309
                                WHERE c_id = $courseId AND exercice_id = $my_exercise_id";
9310
                        $sqlresult = Database::query($sql);
9311
                        $rowi = (int) Database::result($sqlresult, 0, 0);
9312
9313
                        if ($sessionId == $row['session_id']) {
9314
                            // Questions list
9315
                            $actions = Display::url(
9316
                                Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL),
9317
                                'admin.php?'.api_get_cidreq().'&exerciseId='.$row['id']
9318
                            );
9319
9320
                            // Test settings
9321
                            $settings = Display::url(
9322
                                Display::return_icon('settings.png', get_lang('Configure'), '', ICON_SIZE_SMALL),
9323
                                'exercise_admin.php?'.api_get_cidreq().'&exerciseId='.$row['id']
9324
                            );
9325
9326
                            if ($limitTeacherAccess && !api_is_platform_admin()) {
9327
                                $settings = '';
9328
                            }
9329
                            $actions .= $settings;
9330
9331
                            // Exercise results
9332
                            $resultsLink = '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['id'].'">'.
9333
                                Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
9334
9335
                            if ($limitTeacherAccess) {
9336
                                if (api_is_platform_admin()) {
9337
                                    $actions .= $resultsLink;
9338
                                }
9339
                            } else {
9340
                                // Exercise results
9341
                                $actions .= $resultsLink;
9342
                            }
9343
9344
                            // Auto launch
9345
                            if ($autoLaunchAvailable) {
9346
                                $autoLaunch = $exercise->getAutoLaunch();
9347
                                if (empty($autoLaunch)) {
9348
                                    $actions .= Display::url(
9349
                                        Display::return_icon(
9350
                                            'launch_na.png',
9351
                                            get_lang('Enable'),
9352
                                            '',
9353
                                            ICON_SIZE_SMALL
9354
                                        ),
9355
                                        'exercise.php?'.api_get_cidreq().'&choice=enable_launch&sec_token='.$token.'&exerciseId='.$row['id']
9356
                                    );
9357
                                } else {
9358
                                    $actions .= Display::url(
9359
                                        Display::return_icon(
9360
                                            'launch.png',
9361
                                            get_lang('Disable'),
9362
                                            '',
9363
                                            ICON_SIZE_SMALL
9364
                                        ),
9365
                                        'exercise.php?'.api_get_cidreq().'&choice=disable_launch&sec_token='.$token.'&exerciseId='.$row['id']
9366
                                    );
9367
                                }
9368
                            }
9369
9370
                            // Export
9371
                            $actions .= Display::url(
9372
                                Display::return_icon('cd.png', get_lang('CopyExercise')),
9373
                                '',
9374
                                [
9375
                                    'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;",
9376
                                    'href' => 'exercise.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['id'],
9377
                                ]
9378
                            );
9379
9380
                            // Clean exercise
9381
                            $clean = '';
9382
                            if (true === $allowClean) {
9383
                                if (false == $locked) {
9384
                                    $clean = Display::url(
9385
                                        Display::return_icon(
9386
                                            'clean.png',
9387
                                            get_lang('CleanStudentResults'),
9388
                                            '',
9389
                                            ICON_SIZE_SMALL
9390
                                        ),
9391
                                        '',
9392
                                        [
9393
                                            'onclick' => "javascript:if(!confirm('".addslashes(
9394
                                                    api_htmlentities(
9395
                                                        get_lang('AreYouSureToDeleteResults'),
9396
                                                        ENT_QUOTES,
9397
                                                        $charset
9398
                                                    )
9399
                                                )." ".addslashes($row['title'])."?"."')) return false;",
9400
                                            'href' => 'exercise.php?'.api_get_cidreq(
9401
                                                ).'&choice=clean_results&sec_token='.$token.'&exerciseId='.$row['id'],
9402
                                        ]
9403
                                    );
9404
                                } else {
9405
                                    $clean = Display::return_icon(
9406
                                        'clean_na.png',
9407
                                        get_lang('ResourceLockedByGradebook'),
9408
                                        '',
9409
                                        ICON_SIZE_SMALL
9410
                                    );
9411
                                }
9412
                            }
9413
9414
                            $actions .= $clean;
9415
9416
                            // Visible / invisible
9417
                            // Check if this exercise was added in a LP
9418
                            if ($exercise->exercise_was_added_in_lp == true) {
9419
                                $visibility = Display::return_icon(
9420
                                    'invisible.png',
9421
                                    get_lang('AddedToLPCannotBeAccessed'),
9422
                                    '',
9423
                                    ICON_SIZE_SMALL
9424
                                );
9425
                            } else {
9426
                                if ($row['active'] == 0 || $visibility == 0) {
9427
                                    $visibility = Display::url(
9428
                                        Display::return_icon(
9429
                                            'invisible.png',
9430
                                            get_lang('Activate'),
9431
                                            '',
9432
                                            ICON_SIZE_SMALL
9433
                                        ),
9434
                                        'exercise.php?'.api_get_cidreq().'&choice=enable&sec_token='.$token.'&exerciseId='.$row['id']
9435
                                    );
9436
                                } else {
9437
                                    // else if not active
9438
                                    $visibility = Display::url(
9439
                                        Display::return_icon(
9440
                                            'visible.png',
9441
                                            get_lang('Deactivate'),
9442
                                            '',
9443
                                            ICON_SIZE_SMALL
9444
                                        ),
9445
                                        'exercise.php?'.api_get_cidreq().'&choice=disable&sec_token='.$token.'&exerciseId='.$row['id']
9446
                                    );
9447
                                }
9448
                            }
9449
9450
                            if ($limitTeacherAccess && !api_is_platform_admin()) {
9451
                                $visibility = '';
9452
                            }
9453
9454
                            $actions .= $visibility;
9455
9456
                            // Export qti ...
9457
                            $export = Display::url(
9458
                                Display::return_icon(
9459
                                    'export_qti2.png',
9460
                                    'IMS/QTI',
9461
                                    '',
9462
                                    ICON_SIZE_SMALL
9463
                                ),
9464
                                'exercise.php?action=exportqti2&exerciseId='.$row['id'].'&'.api_get_cidreq()
9465
                            );
9466
9467
                            if ($limitTeacherAccess && !api_is_platform_admin()) {
9468
                                $export = '';
9469
                            }
9470
9471
                            $actions .= $export;
9472
                        } else {
9473
                            // not session
9474
                            $actions = Display::return_icon(
9475
                                'edit_na.png',
9476
                                get_lang('ExerciseEditionNotAvailableInSession')
9477
                            );
9478
9479
                            // Check if this exercise was added in a LP
9480
                            if ($exercise->exercise_was_added_in_lp == true) {
9481
                                $visibility = Display::return_icon(
9482
                                    'invisible.png',
9483
                                    get_lang('AddedToLPCannotBeAccessed'),
9484
                                    '',
9485
                                    ICON_SIZE_SMALL
9486
                                );
9487
                            } else {
9488
                                if ($row['active'] == 0 || $visibility == 0) {
9489
                                    $visibility = Display::url(
9490
                                        Display::return_icon(
9491
                                            'invisible.png',
9492
                                            get_lang('Activate'),
9493
                                            '',
9494
                                            ICON_SIZE_SMALL
9495
                                        ),
9496
                                        'exercise.php?'.api_get_cidreq().'&choice=enable&sec_token='.$token.'&exerciseId='.$row['id']
9497
                                    );
9498
                                } else {
9499
                                    // else if not active
9500
                                    $visibility = Display::url(
9501
                                        Display::return_icon(
9502
                                            'visible.png',
9503
                                            get_lang('Deactivate'),
9504
                                            '',
9505
                                            ICON_SIZE_SMALL
9506
                                        ),
9507
                                        'exercise.php?'.api_get_cidreq().'&choice=disable&sec_token='.$token.'&exerciseId='.$row['id']
9508
                                    );
9509
                                }
9510
                            }
9511
9512
                            if ($limitTeacherAccess && !api_is_platform_admin()) {
9513
                                $visibility = '';
9514
                            }
9515
9516
                            $actions .= $visibility;
9517
                            $actions .= '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['id'].'">'.
9518
                                Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
9519
                            $actions .= Display::url(
9520
                                Display::return_icon('cd.gif', get_lang('CopyExercise')),
9521
                                '',
9522
                                [
9523
                                    'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;",
9524
                                    'href' => 'exercise.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['id'],
9525
                                ]
9526
                            );
9527
                        }
9528
9529
                        // Delete
9530
                        $delete = '';
9531
                        if ($sessionId == $row['session_id']) {
9532
                            if ($locked == false) {
9533
                                $delete = Display::url(
9534
                                    Display::return_icon(
9535
                                        'delete.png',
9536
                                        get_lang('Delete'),
9537
                                        '',
9538
                                        ICON_SIZE_SMALL
9539
                                    ),
9540
                                    '',
9541
                                    [
9542
                                        'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDeleteJS'), ENT_QUOTES, $charset))." ".addslashes($exercise->getUnformattedTitle())."?"."')) return false;",
9543
                                        'href' => 'exercise.php?'.api_get_cidreq().'&choice=delete&sec_token='.$token.'&exerciseId='.$row['id'],
9544
                                    ]
9545
                                );
9546
                            } else {
9547
                                $delete = Display::return_icon(
9548
                                    'delete_na.png',
9549
                                    get_lang('ResourceLockedByGradebook'),
9550
                                    '',
9551
                                    ICON_SIZE_SMALL
9552
                                );
9553
                            }
9554
                        }
9555
9556
                        if ($limitTeacherAccess && !api_is_platform_admin()) {
9557
                            $delete = '';
9558
                        }
9559
9560
                        if (!empty($minCategoriesInExercise)) {
9561
                            $cats = TestCategory::getListOfCategoriesForTest($exercise);
9562
                            if (!(count($cats) >= $minCategoriesInExercise)) {
9563
                                continue;
9564
                            }
9565
                        }
9566
9567
                        $actions .= $delete;
9568
9569
                        // Number of questions
9570
                        $random_label = null;
9571
                        if ($row['random'] > 0 || $row['random'] == -1) {
9572
                            // if random == -1 means use random questions with all questions
9573
                            $random_number_of_question = $row['random'];
9574
                            if ($random_number_of_question == -1) {
9575
                                $random_number_of_question = $rowi;
9576
                            }
9577
                            if ($row['random_by_category'] > 0) {
9578
                                $nbQuestionsTotal = TestCategory::getNumberOfQuestionRandomByCategory(
9579
                                    $my_exercise_id,
9580
                                    $random_number_of_question
9581
                                );
9582
                                $number_of_questions = $nbQuestionsTotal.' ';
9583
                                $number_of_questions .= ($nbQuestionsTotal > 1) ? get_lang('QuestionsLowerCase') : get_lang('QuestionLowerCase');
9584
                                $number_of_questions .= ' - ';
9585
                                $number_of_questions .= min(
9586
                                        TestCategory::getNumberMaxQuestionByCat($my_exercise_id), $random_number_of_question
9587
                                    ).' '.get_lang('QuestionByCategory');
9588
                            } else {
9589
                                $random_label = ' ('.get_lang('Random').') ';
9590
                                $number_of_questions = $random_number_of_question.' '.$random_label.' / '.$rowi;
9591
                                // Bug if we set a random value bigger than the real number of questions
9592
                                if ($random_number_of_question > $rowi) {
9593
                                    $number_of_questions = $rowi.' '.$random_label;
9594
                                }
9595
                            }
9596
                        } else {
9597
                            $number_of_questions = $rowi;
9598
                        }
9599
9600
                        $currentRow['count_questions'] = $number_of_questions;
9601
                    } else {
9602
                        // Student only.
9603
                        $visibility = api_get_item_visibility(
9604
                            $courseInfo,
9605
                            TOOL_QUIZ,
9606
                            $my_exercise_id,
9607
                            $sessionId
9608
                        );
9609
9610
                        if ($visibility == 0) {
9611
                            continue;
9612
                        }
9613
9614
                        $url = '<a '.$alt_title.'  href="overview.php?'.api_get_cidreq().$mylpid.$mylpitemid.'&exerciseId='.$row['id'].'">'.
9615
                            $cut_title.'</a>';
9616
9617
                        // Link of the exercise.
9618
                        $currentRow['title'] = $url.' '.$session_img;
9619
9620
                        if ($returnData) {
9621
                            $currentRow['title'] = $exercise->getUnformattedTitle();
9622
                        }
9623
9624
                        // This query might be improved later on by ordering by the new "tms" field rather than by exe_id
9625
                        // Don't remove this marker: note-query-exe-results
9626
                        $sql = "SELECT * FROM $TBL_TRACK_EXERCISES
9627
                                WHERE
9628
                                    exe_exo_id = ".$row['id']." AND
9629
                                    exe_user_id = $userId AND
9630
                                    c_id = ".api_get_course_int_id()." AND
9631
                                    status <> 'incomplete' AND
9632
                                    orig_lp_id = 0 AND
9633
                                    orig_lp_item_id = 0 AND
9634
                                    session_id =  '".api_get_session_id()."'
9635
                                ORDER BY exe_id DESC";
9636
9637
                        $qryres = Database::query($sql);
9638
                        $num = Database :: num_rows($qryres);
9639
9640
                        // Hide the results.
9641
                        $my_result_disabled = $row['results_disabled'];
9642
9643
                        $attempt_text = '-';
9644
                        // Time limits are on
9645
                        if ($time_limits) {
9646
                            // Exam is ready to be taken
9647
                            if ($is_actived_time) {
9648
                                // Show results
9649
                                if (
9650
                                in_array(
9651
                                    $my_result_disabled,
9652
                                    [
9653
                                        RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
9654
                                        RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
9655
                                        RESULT_DISABLE_SHOW_SCORE_ONLY,
9656
                                        RESULT_DISABLE_RANKING,
9657
                                    ]
9658
                                )
9659
                                ) {
9660
                                    // More than one attempt
9661
                                    if ($num > 0) {
9662
                                        $row_track = Database :: fetch_array($qryres);
9663
                                        $attempt_text = get_lang('LatestAttempt').' : ';
9664
                                        $attempt_text .= ExerciseLib::show_score(
9665
                                            $row_track['exe_result'],
9666
                                            $row_track['exe_weighting']
9667
                                        );
9668
                                    } else {
9669
                                        //No attempts
9670
                                        $attempt_text = get_lang('NotAttempted');
9671
                                    }
9672
                                } else {
9673
                                    $attempt_text = '-';
9674
                                }
9675
                            } else {
9676
                                // Quiz not ready due to time limits
9677
                                //@todo use the is_visible function
9678
                                if (!empty($row['start_time']) && !empty($row['end_time'])) {
9679
                                    $today = time();
9680
                                    $start_time = api_strtotime($row['start_time'], 'UTC');
9681
                                    $end_time = api_strtotime($row['end_time'], 'UTC');
9682
                                    if ($today < $start_time) {
9683
                                        $attempt_text = sprintf(
9684
                                            get_lang('ExerciseWillBeActivatedFromXToY'),
9685
                                            api_convert_and_format_date($row['start_time']),
9686
                                            api_convert_and_format_date($row['end_time'])
9687
                                        );
9688
                                    } else {
9689
                                        if ($today > $end_time) {
9690
                                            $attempt_text = sprintf(
9691
                                                get_lang('ExerciseWasActivatedFromXToY'),
9692
                                                api_convert_and_format_date($row['start_time']),
9693
                                                api_convert_and_format_date($row['end_time'])
9694
                                            );
9695
                                        }
9696
                                    }
9697
                                } else {
9698
                                    //$attempt_text = get_lang('ExamNotAvailableAtThisTime');
9699
                                    if (!empty($row['start_time'])) {
9700
                                        $attempt_text = sprintf(
9701
                                            get_lang('ExerciseAvailableFromX'),
9702
                                            api_convert_and_format_date($row['start_time'])
9703
                                        );
9704
                                    }
9705
                                    if (!empty($row['end_time'])) {
9706
                                        $attempt_text = sprintf(
9707
                                            get_lang('ExerciseAvailableUntilX'),
9708
                                            api_convert_and_format_date($row['end_time'])
9709
                                        );
9710
                                    }
9711
                                }
9712
                            }
9713
                        } else {
9714
                            // Normal behaviour.
9715
                            // Show results.
9716
                            if (
9717
                            in_array(
9718
                                $my_result_disabled,
9719
                                [
9720
                                    RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
9721
                                    RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
9722
                                    RESULT_DISABLE_SHOW_SCORE_ONLY,
9723
                                    RESULT_DISABLE_RANKING,
9724
                                    RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
9725
                                ]
9726
                            )
9727
                            ) {
9728
                                if ($num > 0) {
9729
                                    $row_track = Database :: fetch_array($qryres);
9730
                                    $attempt_text = get_lang('LatestAttempt').' : ';
9731
                                    $attempt_text .= ExerciseLib::show_score(
9732
                                        $row_track['exe_result'],
9733
                                        $row_track['exe_weighting']
9734
                                    );
9735
                                } else {
9736
                                    $attempt_text = get_lang('NotAttempted');
9737
                                }
9738
                            }
9739
                        }
9740
9741
                        if ($returnData) {
9742
                            $attempt_text = $num;
9743
                        }
9744
                    }
9745
9746
                    $currentRow['attempt'] = $attempt_text;
9747
9748
                    if ($is_allowedToEdit) {
9749
                        $additionalActions = ExerciseLib::getAdditionalTeacherActions($row['id']);
9750
9751
                        if (!empty($additionalActions)) {
9752
                            $actions .= $additionalActions.PHP_EOL;
9753
                        }
9754
9755
                        // Replace with custom actions.
9756
                        if (!empty($myActions) && is_callable($myActions)) {
9757
                            $actions = $myActions($row);
9758
                        }
9759
9760
                        $currentRow = [
9761
                            $row['id'],
9762
                            $currentRow['title'],
9763
                            $currentRow['count_questions'],
9764
                            $actions,
9765
                        ];
9766
                    } else {
9767
                        $currentRow = [
9768
                            $currentRow['title'],
9769
                            $currentRow['attempt'],
9770
                        ];
9771
9772
                        if ($isDrhOfCourse) {
9773
                            $currentRow[] = '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['id'].'">'.
9774
                                Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
9775
                        }
9776
9777
                        if ($returnData) {
9778
                            $currentRow['id'] = $exercise->id;
9779
                            $currentRow['url'] = $webPath.'exercise/overview.php?'
9780
                                .api_get_cidreq_params($courseInfo['code'], $sessionId).'&'
9781
                                ."$mylpid$mylpitemid&exerciseId={$row['id']}";
9782
                            $currentRow['name'] = $currentRow[0];
9783
                        }
9784
                    }
9785
9786
                    $tableRows[] = $currentRow;
9787
                }
9788
            }
9789
        }
9790
9791
        // end exercise list
9792
        // Hotpotatoes results
9793
        if ($is_allowedToEdit) {
9794
            $sql = "SELECT d.iid, d.path as path, d.comment as comment
9795
                    FROM $TBL_DOCUMENT d
9796
                    WHERE
9797
                        d.c_id = $courseId AND
9798
                        (d.path LIKE '%htm%') AND
9799
                        d.path  LIKE '".Database :: escape_string($uploadPath.'/%/%')."'
9800
                    LIMIT $from , $limit"; // only .htm or .html files listed
9801
        } else {
9802
            $sql = "SELECT d.iid, d.path as path, d.comment as comment
9803
                    FROM $TBL_DOCUMENT d
9804
                    WHERE
9805
                        d.c_id = $courseId AND
9806
                        (d.path LIKE '%htm%') AND
9807
                        d.path  LIKE '".Database :: escape_string($uploadPath.'/%/%')."'
9808
                    LIMIT $from , $limit";
9809
        }
9810
9811
        $result = Database::query($sql);
9812
        $attributes = [];
9813
        while ($row = Database::fetch_array($result, 'ASSOC')) {
9814
            $attributes[$row['iid']] = $row;
9815
        }
9816
9817
        $nbrActiveTests = 0;
9818
        if (!empty($attributes)) {
9819
            foreach ($attributes as $item) {
9820
                $id = $item['iid'];
9821
                $path = $item['path'];
9822
9823
                $title = GetQuizName($path, $documentPath);
9824
                if ($title == '') {
9825
                    $title = basename($path);
9826
                }
9827
9828
                // prof only
9829
                if ($is_allowedToEdit) {
9830
                    $visibility = api_get_item_visibility(
9831
                        ['real_id' => $courseId],
9832
                        TOOL_DOCUMENT,
9833
                        $id,
9834
                        0
9835
                    );
9836
9837
                    if (!empty($sessionId)) {
9838
                        if (0 == $visibility) {
9839
                            continue;
9840
                        }
9841
9842
                        $visibility = api_get_item_visibility(
9843
                            ['real_id' => $courseId],
9844
                            TOOL_DOCUMENT,
9845
                            $id,
9846
                            $sessionId
9847
                        );
9848
                    }
9849
9850
                    $title =
9851
                        implode(PHP_EOL, [
9852
                            Display::return_icon('hotpotatoes_s.png', 'HotPotatoes'),
9853
                            Display::url(
9854
                                $title,
9855
                                'showinframes.php?'.api_get_cidreq().'&'.http_build_query([
9856
                                    'file' => $path,
9857
                                    'uid' => $userId,
9858
                                ]),
9859
                                ['class' => $visibility == 0 ? 'text-muted' : null]
9860
                            ),
9861
                        ]);
9862
9863
                    $actions = Display::url(
9864
                        Display::return_icon(
9865
                            'edit.png',
9866
                            get_lang('Edit'),
9867
                            '',
9868
                            ICON_SIZE_SMALL
9869
                        ),
9870
                        'adminhp.php?'.api_get_cidreq().'&hotpotatoesName='.$path
9871
                    );
9872
9873
                    $actions .= '<a href="hotpotatoes_exercise_report.php?'.api_get_cidreq().'&path='.$path.'">'.
9874
                        Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).
9875
                        '</a>';
9876
9877
                    // if active
9878
                    if ($visibility != 0) {
9879
                        $nbrActiveTests = $nbrActiveTests + 1;
9880
                        $actions .= '      <a href="'.$exercisePath.'?'.api_get_cidreq().'&hpchoice=disable&file='.$path.'">'.
9881
                            Display::return_icon('visible.png', get_lang('Deactivate'), '', ICON_SIZE_SMALL).'</a>';
9882
                    } else { // else if not active
9883
                        $actions .= '    <a href="'.$exercisePath.'?'.api_get_cidreq().'&hpchoice=enable&file='.$path.'">'.
9884
                            Display::return_icon('invisible.png', get_lang('Activate'), '', ICON_SIZE_SMALL).'</a>';
9885
                    }
9886
                    $actions .= '<a href="'.$exercisePath.'?'.api_get_cidreq().'&hpchoice=delete&file='.$path.'" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('AreYouSureToDeleteJS'), ENT_QUOTES, $charset)).'\')) return false;">'.
9887
                        Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_SMALL).'</a>';
9888
9889
                    $currentRow = [
9890
                        '',
9891
                        $title,
9892
                        '',
9893
                        $actions,
9894
                    ];
9895
                } else {
9896
                    $visibility = api_get_item_visibility(
9897
                        ['real_id' => $courseId],
9898
                        TOOL_DOCUMENT,
9899
                        $id,
9900
                        $sessionId
9901
                    );
9902
9903
                    if (0 == $visibility) {
9904
                        continue;
9905
                    }
9906
9907
                    // Student only
9908
                    $attempt = ExerciseLib::getLatestHotPotatoResult(
9909
                        $path,
9910
                        $userId,
9911
                        api_get_course_int_id(),
9912
                        api_get_session_id()
9913
                    );
9914
9915
                    $nbrActiveTests = $nbrActiveTests + 1;
9916
                    $title = Display::url(
9917
                        $title,
9918
                        'showinframes.php?'.api_get_cidreq().'&'.http_build_query(
9919
                            [
9920
                                'file' => $path,
9921
                                'cid' => api_get_course_id(),
9922
                                'uid' => $userId,
9923
                            ]
9924
                        )
9925
                    );
9926
9927
                    if (!empty($attempt)) {
9928
                        $actions = '<a href="hotpotatoes_exercise_report.php?'.api_get_cidreq().'&path='.$path.'&filter_by_user='.$userId.'">'.Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
9929
                        $attemptText = get_lang('LatestAttempt').' : ';
9930
                        $attemptText .= ExerciseLib::show_score(
9931
                                $attempt['exe_result'],
9932
                                $attempt['exe_weighting']
9933
                            ).' ';
9934
                        $attemptText .= $actions;
9935
                    } else {
9936
                        // No attempts.
9937
                        $attemptText = get_lang('NotAttempted').' ';
9938
                    }
9939
9940
                    $currentRow = [
9941
                        $title,
9942
                        $attemptText,
9943
                    ];
9944
9945
                    if ($isDrhOfCourse) {
9946
                        $currentRow[] = '<a href="hotpotatoes_exercise_report.php?'.api_get_cidreq().'&path='.$path.'">'.
9947
                            Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
9948
                    }
9949
                }
9950
9951
                $tableRows[] = $currentRow;
9952
            }
9953
        }
9954
9955
        if ($returnData) {
9956
            return $tableRows;
9957
        }
9958
9959
        if (empty($tableRows) && empty($categoryId)) {
9960
            if ($is_allowedToEdit && $origin !== 'learnpath') {
9961
                $content .= '<div id="no-data-view">';
9962
                $content .= '<h3>'.get_lang('Quiz').'</h3>';
9963
                $content .= Display::return_icon('quiz.png', '', [], 64);
9964
                $content .= '<div class="controls">';
9965
                $content .= Display::url(
9966
                    '<em class="fa fa-plus"></em> '.get_lang('NewEx'),
9967
                    'exercise_admin.php?'.api_get_cidreq(),
9968
                    ['class' => 'btn btn-primary']
9969
                );
9970
                $content .= '</div>';
9971
                $content .= '</div>';
9972
            }
9973
        } else {
9974
            if (empty($tableRows)) {
9975
                return '';
9976
            }
9977
            $table->setTableData($tableRows);
9978
            $table->setTotalNumberOfItems($total);
9979
            $table->set_additional_parameters([
9980
                'cidReq' => api_get_course_id(),
9981
                'id_session' => api_get_session_id(),
9982
                'category_id' => $categoryId,
9983
            ]);
9984
9985
            if ($is_allowedToEdit) {
9986
                $formActions = [];
9987
                $formActions['visible'] = get_lang('Activate');
9988
                $formActions['invisible'] = get_lang('Deactivate');
9989
                $formActions['delete'] = get_lang('Delete');
9990
                $table->set_form_actions($formActions);
9991
            }
9992
9993
            $i = 0;
9994
            if ($is_allowedToEdit) {
9995
                $table->set_header($i++, '', false, 'width="18px"');
9996
            }
9997
            $table->set_header($i++, get_lang('ExerciseName'), false);
9998
9999
            if ($is_allowedToEdit) {
10000
                $table->set_header($i++, get_lang('QuantityQuestions'), false);
10001
                $table->set_header($i++, get_lang('Actions'), false);
10002
            } else {
10003
                $table->set_header($i++, get_lang('Status'), false);
10004
                if ($isDrhOfCourse) {
10005
                    $table->set_header($i++, get_lang('Actions'), false);
10006
                }
10007
            }
10008
10009
            if ($returnTable) {
10010
                return $table;
10011
            }
10012
10013
            $content .= $table->return_table();
10014
        }
10015
10016
        return $content;
10017
    }
10018
10019
    /**
10020
     * @return int value in minutes
10021
     */
10022
    public function getResultAccess()
10023
    {
10024
        $extraFieldValue = new ExtraFieldValue('exercise');
10025
        $value = $extraFieldValue->get_values_by_handler_and_field_variable(
10026
            $this->iId,
10027
            'results_available_for_x_minutes'
10028
        );
10029
10030
        if (!empty($value) && isset($value['value'])) {
10031
            return (int) $value['value'];
10032
        }
10033
10034
        return 0;
10035
    }
10036
10037
    /**
10038
     * @param array $exerciseResultInfo
10039
     *
10040
     * @return bool
10041
     */
10042
    public function getResultAccessTimeDiff($exerciseResultInfo)
10043
    {
10044
        $value = $this->getResultAccess();
10045
        if (!empty($value)) {
10046
            $endDate = new DateTime($exerciseResultInfo['exe_date'], new DateTimeZone('UTC'));
10047
            $endDate->add(new DateInterval('PT'.$value.'M'));
10048
            $now = time();
10049
            if ($endDate->getTimestamp() > $now) {
10050
                return (int) $endDate->getTimestamp() - $now;
10051
            }
10052
        }
10053
10054
        return 0;
10055
    }
10056
10057
    /**
10058
     * @param array $exerciseResultInfo
10059
     *
10060
     * @return bool
10061
     */
10062
    public function hasResultsAccess($exerciseResultInfo)
10063
    {
10064
        $diff = $this->getResultAccessTimeDiff($exerciseResultInfo);
10065
        if (0 === $diff) {
10066
            return false;
10067
        }
10068
10069
        return true;
10070
    }
10071
10072
    /**
10073
     * @return int
10074
     */
10075
    public function getResultsAccess()
10076
    {
10077
        $extraFieldValue = new ExtraFieldValue('exercise');
10078
        $value = $extraFieldValue->get_values_by_handler_and_field_variable(
10079
            $this->iId,
10080
            'results_available_for_x_minutes'
10081
        );
10082
        if (!empty($value)) {
10083
            return (int) $value;
10084
        }
10085
10086
        return 0;
10087
    }
10088
10089
    /**
10090
     * @param int   $questionId
10091
     * @param bool  $show_results
10092
     * @param array $question_result
10093
     */
10094
    public function getDelineationResult(Question $objQuestionTmp, $questionId, $show_results, $question_result)
10095
    {
10096
        $id = (int) $objQuestionTmp->id;
10097
        $questionId = (int) $questionId;
10098
10099
        $final_overlap = $question_result['extra']['final_overlap'];
10100
        $final_missing = $question_result['extra']['final_missing'];
10101
        $final_excess = $question_result['extra']['final_excess'];
10102
10103
        $overlap_color = $question_result['extra']['overlap_color'];
10104
        $missing_color = $question_result['extra']['missing_color'];
10105
        $excess_color = $question_result['extra']['excess_color'];
10106
10107
        $threadhold1 = $question_result['extra']['threadhold1'];
10108
        $threadhold2 = $question_result['extra']['threadhold2'];
10109
        $threadhold3 = $question_result['extra']['threadhold3'];
10110
10111
        if ($show_results) {
10112
            if ($overlap_color) {
10113
                $overlap_color = 'green';
10114
            } else {
10115
                $overlap_color = 'red';
10116
            }
10117
10118
            if ($missing_color) {
10119
                $missing_color = 'green';
10120
            } else {
10121
                $missing_color = 'red';
10122
            }
10123
            if ($excess_color) {
10124
                $excess_color = 'green';
10125
            } else {
10126
                $excess_color = 'red';
10127
            }
10128
10129
            if (!is_numeric($final_overlap)) {
10130
                $final_overlap = 0;
10131
            }
10132
10133
            if (!is_numeric($final_missing)) {
10134
                $final_missing = 0;
10135
            }
10136
            if (!is_numeric($final_excess)) {
10137
                $final_excess = 0;
10138
            }
10139
10140
            if ($final_excess > 100) {
10141
                $final_excess = 100;
10142
            }
10143
10144
            $table_resume = '
10145
                    <table class="table table-hover table-striped data_table">
10146
                        <tr class="row_odd" >
10147
                            <td>&nbsp;</td>
10148
                            <td><b>'.get_lang('Requirements').'</b></td>
10149
                            <td><b>'.get_lang('YourAnswer').'</b></td>
10150
                        </tr>
10151
                        <tr class="row_even">
10152
                            <td><b>'.get_lang('Overlap').'</b></td>
10153
                            <td>'.get_lang('Min').' '.$threadhold1.'</td>
10154
                            <td>
10155
                                <div style="color:'.$overlap_color.'">
10156
                                    '.(($final_overlap < 0) ? 0 : intval($final_overlap)).'
10157
                                </div>
10158
                            </td>
10159
                        </tr>
10160
                        <tr>
10161
                            <td><b>'.get_lang('Excess').'</b></td>
10162
                            <td>'.get_lang('Max').' '.$threadhold2.'</td>
10163
                            <td>
10164
                                <div style="color:'.$excess_color.'">
10165
                                    '.(($final_excess < 0) ? 0 : intval($final_excess)).'
10166
                                </div>
10167
                            </td>
10168
                        </tr>
10169
                        <tr class="row_even">
10170
                            <td><b>'.get_lang('Missing').'</b></td>
10171
                            <td>'.get_lang('Max').' '.$threadhold3.'</td>
10172
                            <td>
10173
                                <div style="color:'.$missing_color.'">
10174
                                    '.(($final_missing < 0) ? 0 : intval($final_missing)).'
10175
                                </div>
10176
                            </td>
10177
                        </tr>
10178
                    </table>
10179
                ';
10180
10181
            $answerType = $objQuestionTmp->selectType();
10182
            if ($answerType != HOT_SPOT_DELINEATION) {
10183
                $item_list = explode('@@', $destination);
10184
                $try = $item_list[0];
10185
                $lp = $item_list[1];
10186
                $destinationid = $item_list[2];
10187
                $url = $item_list[3];
10188
                $table_resume = '';
10189
            } else {
10190
                if ($next == 0) {
10191
                    $try = $try_hotspot;
10192
                    $lp = $lp_hotspot;
10193
                    $destinationid = $select_question_hotspot;
10194
                    $url = $url_hotspot;
10195
                } else {
10196
                    //show if no error
10197
                    $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
10198
                    $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
10199
                }
10200
            }
10201
10202
            echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>';
10203
            if ($answerType == HOT_SPOT_DELINEATION) {
10204
                if ($organs_at_risk_hit > 0) {
10205
                    $message = '<br />'.get_lang('ResultIs').' <b>'.$result_comment.'</b><br />';
10206
                    $message .= '<p style="color:#DC0A0A;"><b>'.get_lang('OARHit').'</b></p>';
10207
                } else {
10208
                    $message = '<p>'.get_lang('YourDelineation').'</p>';
10209
                    $message .= $table_resume;
10210
                    $message .= '<br />'.get_lang('ResultIs').' <b>'.$result_comment.'</b><br />';
10211
                }
10212
                $message .= '<p>'.$comment.'</p>';
10213
                echo $message;
10214
            } else {
10215
                echo '<p>'.$comment.'</p>';
10216
            }
10217
10218
            // Showing the score
10219
            /*$queryfree = "SELECT marks FROM $TBL_TRACK_ATTEMPT
10220
                          WHERE exe_id = $id AND question_id =  $questionId";
10221
            $resfree = Database::query($queryfree);
10222
            $questionScore = Database::result($resfree, 0, 'marks');
10223
            $totalScore += $questionScore;*/
10224
            $relPath = api_get_path(REL_CODE_PATH);
10225
            echo '</table></td></tr>';
10226
            echo "
10227
                        <tr>
10228
                            <td colspan=\"2\">
10229
                                <div id=\"hotspot-solution\"></div>
10230
                                <script>
10231
                                    $(function() {
10232
                                        new HotspotQuestion({
10233
                                            questionId: $questionId,
10234
                                            exerciseId: {$this->id},
10235
                                            exeId: $id,
10236
                                            selector: '#hotspot-solution',
10237
                                            for: 'solution',
10238
                                            relPath: '$relPath'
10239
                                        });
10240
                                    });
10241
                                </script>
10242
                            </td>
10243
                        </tr>
10244
                    </table>
10245
                ";
10246
        }
10247
    }
10248
10249
    /**
10250
     * Clean exercise session variables.
10251
     */
10252
    public static function cleanSessionVariables()
10253
    {
10254
        Session::erase('objExercise');
10255
        Session::erase('exe_id');
10256
        Session::erase('calculatedAnswerId');
10257
        Session::erase('duration_time_previous');
10258
        Session::erase('duration_time');
10259
        Session::erase('objQuestion');
10260
        Session::erase('objAnswer');
10261
        Session::erase('questionList');
10262
        Session::erase('categoryList');
10263
        Session::erase('exerciseResult');
10264
        Session::erase('firstTime');
10265
        Session::erase('time_per_question');
10266
        Session::erase('question_start');
10267
        Session::erase('exerciseResultCoordinates');
10268
        Session::erase('hotspot_coord');
10269
        Session::erase('hotspot_dest');
10270
        Session::erase('hotspot_delineation_result');
10271
    }
10272
10273
    /**
10274
     * Get the first LP found matching the session ID.
10275
     *
10276
     * @param int $sessionId
10277
     *
10278
     * @return array
10279
     */
10280
    public function getLpBySession($sessionId)
10281
    {
10282
        if (!empty($this->lpList)) {
10283
            $sessionId = (int) $sessionId;
10284
10285
            foreach ($this->lpList as $lp) {
10286
                if ((int) $lp['session_id'] == $sessionId) {
10287
                    return $lp;
10288
                }
10289
            }
10290
10291
            return current($this->lpList);
10292
        }
10293
10294
        return [
10295
            'lp_id' => 0,
10296
            'max_score' => 0,
10297
            'session_id' => 0,
10298
        ];
10299
    }
10300
10301
    public static function saveExerciseInLp($safe_item_id, $safe_exe_id)
10302
    {
10303
        $lp = Session::read('oLP');
10304
10305
        $safe_exe_id = (int) $safe_exe_id;
10306
        $safe_item_id = (int) $safe_item_id;
10307
10308
        if (empty($lp) || empty($safe_exe_id) || empty($safe_item_id)) {
10309
            return false;
10310
        }
10311
10312
        $viewId = $lp->get_view_id();
10313
        $course_id = api_get_course_int_id();
10314
        $userId = (int) api_get_user_id();
10315
        $viewId = (int) $viewId;
10316
10317
        $TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
10318
        $TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
10319
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
10320
10321
        $sql = "SELECT start_date, exe_date, exe_result, exe_weighting, exe_exo_id, exe_duration
10322
                FROM $TBL_TRACK_EXERCICES
10323
                WHERE exe_id = $safe_exe_id AND exe_user_id = $userId";
10324
        $res = Database::query($sql);
10325
        $row_dates = Database::fetch_array($res);
10326
10327
        if (empty($row_dates)) {
10328
            return false;
10329
        }
10330
10331
        $duration = (int) $row_dates['exe_duration'];
10332
        $score = (float) $row_dates['exe_result'];
10333
        $max_score = (float) $row_dates['exe_weighting'];
10334
10335
        $sql = "UPDATE $TBL_LP_ITEM SET
10336
                    max_score = '$max_score'
10337
                WHERE iid = $safe_item_id";
10338
        Database::query($sql);
10339
10340
        $sql = "SELECT id FROM $TBL_LP_ITEM_VIEW
10341
                WHERE
10342
                    c_id = $course_id AND
10343
                    lp_item_id = $safe_item_id AND
10344
                    lp_view_id = $viewId
10345
                ORDER BY id DESC
10346
                LIMIT 1";
10347
        $res_last_attempt = Database::query($sql);
10348
10349
        if (Database::num_rows($res_last_attempt) && !api_is_invitee()) {
10350
            $row_last_attempt = Database::fetch_row($res_last_attempt);
10351
            $lp_item_view_id = $row_last_attempt[0];
10352
10353
            $exercise = new Exercise($course_id);
10354
            $exercise->read($row_dates['exe_exo_id']);
10355
            $status = 'completed';
10356
10357
            if (!empty($exercise->pass_percentage)) {
10358
                $status = 'failed';
10359
                $success = ExerciseLib::isSuccessExerciseResult(
10360
                    $score,
10361
                    $max_score,
10362
                    $exercise->pass_percentage
10363
                );
10364
                if ($success) {
10365
                    $status = 'passed';
10366
                }
10367
            }
10368
10369
            $sql = "UPDATE $TBL_LP_ITEM_VIEW SET
10370
                        status = '$status',
10371
                        score = $score,
10372
                        total_time = $duration
10373
                    WHERE iid = $lp_item_view_id";
10374
            Database::query($sql);
10375
10376
            $sql = "UPDATE $TBL_TRACK_EXERCICES SET
10377
                        orig_lp_item_view_id = $lp_item_view_id
10378
                    WHERE exe_id = ".$safe_exe_id;
10379
            Database::query($sql);
10380
        }
10381
    }
10382
10383
    /**
10384
     * Get the user answers saved in exercise.
10385
     *
10386
     * @param int $attemptId
10387
     *
10388
     * @return array
10389
     */
10390
    public function getUserAnswersSavedInExercise($attemptId)
10391
    {
10392
        $exerciseResult = [];
10393
        $attemptList = Event::getAllExerciseEventByExeId($attemptId);
10394
10395
        foreach ($attemptList as $questionId => $options) {
10396
            foreach ($options as $option) {
10397
                $question = Question::read($option['question_id']);
10398
                if ($question) {
10399
                    switch ($question->type) {
10400
                        case FILL_IN_BLANKS:
10401
                            $option['answer'] = $this->fill_in_blank_answer_to_string($option['answer']);
10402
                            break;
10403
                    }
10404
                }
10405
10406
                if (!empty($option['answer'])) {
10407
                    $exerciseResult[] = $questionId;
10408
10409
                    break;
10410
                }
10411
            }
10412
        }
10413
10414
        return $exerciseResult;
10415
    }
10416
10417
    /**
10418
     * Get the number of user answers saved in exercise.
10419
     *
10420
     * @param int $attemptId
10421
     *
10422
     * @return int
10423
     */
10424
    public function countUserAnswersSavedInExercise($attemptId)
10425
    {
10426
        $answers = $this->getUserAnswersSavedInExercise($attemptId);
10427
10428
        return count($answers);
10429
    }
10430
10431
    public static function allowAction($action)
10432
    {
10433
        if (api_is_platform_admin()) {
10434
            return true;
10435
        }
10436
10437
        $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
10438
        $disableClean = api_get_configuration_value('disable_clean_exercise_results_for_teachers');
10439
10440
        switch ($action) {
10441
            case 'delete':
10442
                if (api_is_allowed_to_edit(null, true)) {
10443
                    if ($limitTeacherAccess) {
10444
                        return false;
10445
                    }
10446
10447
                    return true;
10448
                }
10449
                break;
10450
            case 'clean_results':
10451
                if (api_is_allowed_to_edit(null, true)) {
10452
                    if ($limitTeacherAccess) {
10453
                        return false;
10454
                    }
10455
10456
                    if ($disableClean) {
10457
                        return false;
10458
                    }
10459
10460
                    return true;
10461
                }
10462
10463
                break;
10464
        }
10465
10466
        return false;
10467
    }
10468
10469
    public static function getLpListFromExercise($exerciseId, $courseId)
10470
    {
10471
        $tableLpItem = Database::get_course_table(TABLE_LP_ITEM);
10472
        $tblLp = Database::get_course_table(TABLE_LP_MAIN);
10473
10474
        $exerciseId = (int) $exerciseId;
10475
        $courseId = (int) $courseId;
10476
10477
        $sql = "SELECT
10478
                    lp.name,
10479
                    lpi.lp_id,
10480
                    lpi.max_score,
10481
                    lp.session_id
10482
                FROM $tableLpItem lpi
10483
                INNER JOIN $tblLp lp
10484
                ON (lpi.lp_id = lp.iid AND lpi.c_id = lp.c_id)
10485
                WHERE
10486
                    lpi.c_id = $courseId AND
10487
                    lpi.item_type = '".TOOL_QUIZ."' AND
10488
                    lpi.path = '$exerciseId'";
10489
        $result = Database::query($sql);
10490
        $lpList = [];
10491
        if (Database::num_rows($result) > 0) {
10492
            $lpList = Database::store_result($result, 'ASSOC');
10493
        }
10494
10495
        return $lpList;
10496
    }
10497
10498
    public function getReminderTable($questionList, $exercise_stat_info, $disableCheckBoxes = false)
10499
    {
10500
        $learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0;
10501
        $learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0;
10502
        $learnpath_item_view_id = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : 0;
10503
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10504
10505
        if (empty($exercise_stat_info)) {
10506
            return '';
10507
        }
10508
10509
        $remindList = $exercise_stat_info['questions_to_check'];
10510
        $remindList = explode(',', $remindList);
10511
        $exeId = $exercise_stat_info['exe_id'];
10512
        $exerciseId = $exercise_stat_info['exe_exo_id'];
10513
        $exercise_result = $this->getUserAnswersSavedInExercise($exeId);
10514
10515
        $content = Display::label(get_lang('QuestionWithNoAnswer'), 'danger');
10516
        $content .= '<div class="clear"></div><br />';
10517
        $table = '';
10518
        $counter = 0;
10519
        // Loop over all question to show results for each of them, one by one
10520
        foreach ($questionList as $questionId) {
10521
            $objQuestionTmp = Question::read($questionId);
10522
            $check_id = 'remind_list['.$questionId.']';
10523
            $attributes = [
10524
                'id' => $check_id,
10525
                'onclick' => "save_remind_item(this, '$questionId');",
10526
                'data-question-id' => $questionId,
10527
            ];
10528
            if (in_array($questionId, $remindList)) {
10529
                $attributes['checked'] = 1;
10530
            }
10531
10532
            $checkbox = Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes);
10533
            $checkbox = '<div class="pretty p-svg p-curve">
10534
                        '.$checkbox.'
10535
                        <div class="state p-primary ">
10536
                         <svg class="svg svg-icon" viewBox="0 0 20 20">
10537
                            <path d="M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z" style="stroke: white;fill:white;"></path>
10538
                         </svg>
10539
                         <label>&nbsp;</label>
10540
                        </div>
10541
                    </div>';
10542
            $counter++;
10543
            $questionTitle = $counter.'. '.strip_tags($objQuestionTmp->selectTitle());
10544
            // Check if the question doesn't have an answer.
10545
            if (!in_array($questionId, $exercise_result)) {
10546
                $questionTitle = Display::label($questionTitle, 'danger');
10547
            }
10548
            $label_attributes = [];
10549
            $label_attributes['for'] = $check_id;
10550
            if (false === $disableCheckBoxes) {
10551
                $questionTitle = Display::tag('label', $checkbox.$questionTitle, $label_attributes);
10552
            }
10553
            $table .= Display::div($questionTitle, ['class' => 'exercise_reminder_item ']);
10554
        }
10555
10556
        $content .= Display::div('', ['id' => 'message']).
10557
                    Display::div($table, ['class' => 'question-check-test']);
10558
10559
        $content .= '<script>
10560
        var lp_data = $.param({
10561
            "learnpath_id": '.$learnpath_id.',
10562
            "learnpath_item_id" : '.$learnpath_item_id.',
10563
            "learnpath_item_view_id": '.$learnpath_item_view_id.'
10564
        });
10565
10566
        function final_submit() {
10567
            // Normal inputs.
10568
            window.location = "'.api_get_path(WEB_CODE_PATH).'exercise/exercise_result.php?'.api_get_cidreq().'&exe_id='.$exeId.'&" + lp_data;
10569
        }
10570
10571
        function selectAll() {
10572
            $("input[type=checkbox]").each(function () {
10573
                $(this).prop("checked", 1);
10574
                var question_id = $(this).data("question-id");
10575
                var action = "add";
10576
                $.ajax({
10577
                    url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder",
10578
                    data: "question_id="+question_id+"&exe_id='.$exeId.'&action="+action,
10579
                    success: function(returnValue) {
10580
                    }
10581
                });
10582
            });
10583
        }
10584
10585
        function changeOptionStatus(status)
10586
        {
10587
            $("input[type=checkbox]").each(function () {
10588
                $(this).prop("checked", status);
10589
            });
10590
            var action = "";
10591
            var option = "remove_all";
10592
            if (status == 1) {
10593
                option = "add_all";
10594
            }
10595
            $.ajax({
10596
                url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder",
10597
                data: "option="+option+"&exe_id='.$exeId.'&action="+action,
10598
                success: function(returnValue) {
10599
                }
10600
            });
10601
        }
10602
10603
        function reviewQuestions() {
10604
            var isChecked = 1;
10605
            $("input[type=checkbox]").each(function () {
10606
                if ($(this).prop("checked")) {
10607
                    isChecked = 2;
10608
                    return false;
10609
                }
10610
            });
10611
10612
            if (isChecked == 1) {
10613
                $("#message").addClass("warning-message");
10614
                $("#message").html("'.addslashes(get_lang('SelectAQuestionToReview')).'");
10615
            } else {
10616
                window.location = "exercise_submit.php?'.api_get_cidreq().'&category_id='.$categoryId.'&exerciseId='.$exerciseId.'&reminder=2&" + lp_data;
10617
            }
10618
        }
10619
10620
        function save_remind_item(obj, question_id) {
10621
            var action = "";
10622
            if ($(obj).prop("checked")) {
10623
                action = "add";
10624
            } else {
10625
                action = "delete";
10626
            }
10627
            $.ajax({
10628
                url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder",
10629
                data: "question_id="+question_id+"&exe_id='.$exeId.'&action="+action,
10630
                success: function(returnValue) {
10631
                }
10632
            });
10633
        }
10634
        </script>';
10635
10636
        return $content;
10637
    }
10638
10639
    public function getRadarsFromUsers($userList, $exercises, $dataSetLabels, $courseId, $sessionId)
10640
    {
10641
        $dataSet = [];
10642
        $labels = [];
10643
        $labelsWithId = [];
10644
        /** @var Exercise $exercise */
10645
        foreach ($exercises as $exercise) {
10646
            if (empty($labels)) {
10647
                $categoryNameList = TestCategory::getListOfCategoriesNameForTest($exercise->iId);
10648
                if (!empty($categoryNameList)) {
10649
                    $labelsWithId = array_column($categoryNameList, 'title', 'id');
10650
                    asort($labelsWithId);
10651
                    $labels = array_values($labelsWithId);
10652
                }
10653
            }
10654
10655
            foreach ($userList as $userId) {
10656
                $results = Event::getExerciseResultsByUser(
10657
                    $userId,
10658
                    $exercise->iId,
10659
                    $courseId,
10660
                    $sessionId
10661
                );
10662
10663
                if ($results) {
10664
                    $firstAttempt = end($results);
10665
                    $exeId = $firstAttempt['exe_id'];
10666
10667
                    ob_start();
10668
                    $stats = ExerciseLib::displayQuestionListByAttempt(
10669
                        $exercise,
10670
                        $exeId,
10671
                        false
10672
                    );
10673
                    ob_end_clean();
10674
10675
                    $categoryList = $stats['category_list'];
10676
                    $tempResult = [];
10677
                    foreach ($labelsWithId as $category_id => $title) {
10678
                        if (isset($categoryList[$category_id])) {
10679
                            $category_item = $categoryList[$category_id];
10680
                            $tempResult[] = round($category_item['score'] / $category_item['total'] * 10);
10681
                        } else {
10682
                            $tempResult[] = 0;
10683
                        }
10684
                    }
10685
                    $dataSet[] = $tempResult;
10686
                }
10687
            }
10688
        }
10689
10690
        return $this->getRadar($labels, $dataSet, $dataSetLabels);
10691
    }
10692
10693
    public function getAverageRadarsFromUsers($userList, $exercises, $dataSetLabels, $courseId, $sessionId)
10694
    {
10695
        $dataSet = [];
10696
        $labels = [];
10697
        $labelsWithId = [];
10698
10699
        $tempResult = [];
10700
        /** @var Exercise $exercise */
10701
        foreach ($exercises as $exercise) {
10702
            $exerciseId = $exercise->iId;
10703
            if (empty($labels)) {
10704
                $categoryNameList = TestCategory::getListOfCategoriesNameForTest($exercise->iId);
10705
                if (!empty($categoryNameList)) {
10706
                    $labelsWithId = array_column($categoryNameList, 'title', 'id');
10707
                    asort($labelsWithId);
10708
                    $labels = array_values($labelsWithId);
10709
                }
10710
            }
10711
10712
            foreach ($userList as $userId) {
10713
                $results = Event::getExerciseResultsByUser(
10714
                    $userId,
10715
                    $exerciseId,
10716
                    $courseId,
10717
                    $sessionId
10718
                );
10719
10720
                if ($results) {
10721
                    $firstAttempt = end($results);
10722
                    $exeId = $firstAttempt['exe_id'];
10723
10724
                    ob_start();
10725
                    $stats = ExerciseLib::displayQuestionListByAttempt(
10726
                        $exercise,
10727
                        $exeId,
10728
                        false
10729
                    );
10730
                    ob_end_clean();
10731
10732
                    $categoryList = $stats['category_list'];
10733
                    foreach ($labelsWithId as $category_id => $title) {
10734
                        if (isset($categoryList[$category_id])) {
10735
                            $category_item = $categoryList[$category_id];
10736
                            if (!isset($tempResult[$exerciseId][$category_id])) {
10737
                                $tempResult[$exerciseId][$category_id] = 0;
10738
                            }
10739
                            $tempResult[$exerciseId][$category_id] += $category_item['score'] / $category_item['total'] * 10;
10740
                        }
10741
                    }
10742
                }
10743
            }
10744
        }
10745
10746
        $totalUsers = count($userList);
10747
10748
        foreach ($exercises as $exercise) {
10749
            $exerciseId = $exercise->iId;
10750
            $data = [];
10751
            foreach ($labelsWithId as $category_id => $title) {
10752
                if (isset($tempResult[$exerciseId]) && isset($tempResult[$exerciseId][$category_id])) {
10753
                    $data[] = round($tempResult[$exerciseId][$category_id] / $totalUsers);
10754
                } else {
10755
                    $data[] = 0;
10756
                }
10757
            }
10758
            $dataSet[] = $data;
10759
        }
10760
10761
        return $this->getRadar($labels, $dataSet, $dataSetLabels);
10762
    }
10763
10764
    public function getRadar($labels, $dataSet, $dataSetLabels = [])
10765
    {
10766
        if (empty($labels) || empty($dataSet)) {
10767
            return '';
10768
        }
10769
10770
        $displayLegend = 0;
10771
        if (!empty($dataSetLabels)) {
10772
            $displayLegend = 1;
10773
        }
10774
10775
        $labels = json_encode($labels);
10776
10777
        $colorList = ChamiloApi::getColorPalette(true, true);
10778
10779
        $dataSetToJson = [];
10780
        $counter = 0;
10781
        foreach ($dataSet as $index => $resultsArray) {
10782
            $color = isset($colorList[$counter]) ? $colorList[$counter] : 'rgb('.rand(0, 255).', '.rand(0, 255).', '.rand(0, 255).', 1.0)';
10783
10784
            $label = isset($dataSetLabels[$index]) ? $dataSetLabels[$index] : '';
10785
            $background = str_replace('1.0', '0.2', $color);
10786
            $dataSetToJson[] = [
10787
                'fill' => false,
10788
                'label' => $label,
10789
                'backgroundColor' => $background,
10790
                'borderColor' => $color,
10791
                'pointBackgroundColor' => $color,
10792
                'pointBorderColor' => '#fff',
10793
                'pointHoverBackgroundColor' => '#fff',
10794
                'pointHoverBorderColor' => $color,
10795
                'pointRadius' => 6,
10796
                'pointBorderWidth' => 3,
10797
                'pointHoverRadius' => 10,
10798
                'data' => $resultsArray,
10799
            ];
10800
            $counter++;
10801
        }
10802
        $resultsToJson = json_encode($dataSetToJson);
10803
10804
        return "
10805
                <canvas id='categoryRadar' height='200'></canvas>
10806
                <script>
10807
                    var data = {
10808
                        labels: $labels,
10809
                        datasets: $resultsToJson
10810
                    }
10811
                    var options = {
10812
                        responsive: true,
10813
                        scale: {
10814
                            angleLines: {
10815
                                display: false
10816
                            },
10817
                            ticks: {
10818
                                beginAtZero: true,
10819
                                min: 0,
10820
                                max: 10,
10821
                                stepSize: 1,
10822
                            },
10823
                            pointLabels: {
10824
                              fontSize: 14,
10825
                              //fontStyle: 'bold'
10826
                            },
10827
                        },
10828
                        elements: {
10829
                            line: {
10830
                                tension: 0,
10831
                                borderWidth: 3
10832
                            }
10833
                        },
10834
                        legend: {
10835
                            //position: 'bottom'
10836
                            display: $displayLegend
10837
                        },
10838
                        animation: {
10839
                            animateScale: true,
10840
                            animateRotate: true
10841
                        },
10842
                    };
10843
                    var ctx = document.getElementById('categoryRadar').getContext('2d');
10844
                    var myRadarChart = new Chart(ctx, {
10845
                        type: 'radar',
10846
                        data: data,
10847
                        options: options
10848
                    });
10849
                </script>
10850
                ";
10851
    }
10852
10853
    /**
10854
     * When a student takes an exam, and he gets an acceptable grade, he is enrolled in a series of courses that
10855
     * represent the next level BT#18165.
10856
     *
10857
     * @param int   $userId
10858
     * @param int   $sessionId
10859
     * @param array $attemps
10860
     */
10861
    public function advancedCourseList($userId = 0, $sessionId = 0)
10862
    {
10863
        $userId = (int) $userId;
10864
        $sessionId = (int) $sessionId;
10865
        $pluginRemedial = api_get_plugin_setting('remedial_course', 'enabled') === 'true';
10866
        if (!$pluginRemedial) {
10867
            return null;
10868
        }
10869
        $field = new ExtraField('exercise');
10870
        $advancedCourseField = $field->get_handler_field_info_by_field_variable('advancedcourselist');
10871
10872
        if (false === $advancedCourseField) {
10873
            return null;
10874
        }
10875
        $userId = empty($userId) ? api_get_user_id() : (int) $userId;
10876
        $extraMessage = null;
10877
        $bestAttempt = Event::get_best_attempt_exercise_results_per_user(
10878
            $userId,
10879
            $this->id,
10880
            $this->course_id,
10881
            $sessionId
10882
        );
10883
        if (!isset($bestAttempt['exe_result'])) {
10884
            // In the case that the result is 0, get_best_attempt_exercise_results_per_user does not return data,
10885
            // for that this block is used
10886
            $exerciseStatInfo = Event::getExerciseResultsByUser(
10887
                $userId,
10888
                $this->id,
10889
                $this->course_id,
10890
                $sessionId
10891
            );
10892
            $bestAttempt['exe_result'] = 0;
10893
            foreach ($exerciseStatInfo as $attempt) {
10894
                if ($attempt['exe_result'] >= $bestAttempt['exe_result']) {
10895
                    $bestAttempt = $attempt;
10896
                }
10897
            }
10898
        }
10899
        if (
10900
            !isset($bestAttempt['exe_result'])
10901
            || !isset($bestAttempt['exe_id'])
10902
            || !isset($bestAttempt['exe_weighting'])
10903
        ) {
10904
            // No try, No exercise id, no defined total
10905
            return null;
10906
        }
10907
10908
        $percentSuccess = $this->selectPassPercentage();
10909
        $pass = ExerciseLib::isPassPercentageAttemptPassed(
10910
            $this,
10911
            $bestAttempt['exe_result'],
10912
            $bestAttempt['exe_weighting']
10913
        );
10914
        if (0 == $percentSuccess && false == $pass) {
10915
            return null;
10916
        }
10917
        $canRemedial = false === $pass;
10918
        // Advance Course
10919
        $extraFieldValue = new ExtraFieldValue('exercise');
10920
        $advanceCourseExcerciseField = $extraFieldValue->get_values_by_handler_and_field_variable(
10921
            $this->iId,
10922
            'advancedcourselist'
10923
        );
10924
10925
        if (false === $canRemedial && isset($advanceCourseExcerciseField['value'])) {
10926
            $coursesIds = explode(';', $advanceCourseExcerciseField['value']);
10927
            if ('' == $advanceCourseExcerciseField['value'] || count($coursesIds) == 0) {
10928
                return null;
10929
            }
10930
            $isInASession = (0 == $sessionId) ? false : true;
10931
            $courses = [];
10932
            foreach ($coursesIds as $course) {
10933
                $courseData = api_get_course_info_by_id($course);
10934
                if (!empty($courseData) && isset($courseData['real_id'])) {
10935
                    // if session is 0, always will be true
10936
                    $courseExistsInSession = true;
10937
                    if ($isInASession) {
10938
                        $courseExistsInSession = SessionManager::sessionHasCourse($sessionId, $courseData['code']);
10939
                    }
10940
                    if ($courseExistsInSession) {
10941
                        $isSubscribed = CourseManager::is_user_subscribed_in_course(
10942
                            $userId,
10943
                            $courseData['code'],
10944
                            $isInASession,
10945
                            $sessionId
10946
                        );
10947
10948
                        if (!$isSubscribed) {
10949
                            CourseManager::subscribeUser(
10950
                                $userId,
10951
                                $courseData['code'],
10952
                                STUDENT,
10953
                                $sessionId
10954
                            );
10955
                        }
10956
                        $courses[] = $courseData['title'];
10957
                    }
10958
                }
10959
            }
10960
            if (0 != count($courses)) {
10961
                $extraMessage = sprintf(
10962
                    get_plugin_lang('SubscriptionToXAdvancedCourses', RemedialCoursePlugin::class),
10963
                    implode(' - ', $courses)
10964
                );
10965
            }
10966
        }
10967
10968
        return $extraMessage;
10969
    }
10970
10971
    /**
10972
     * Returns true if the exercise is locked by percentage. an exercise attempt must be passed.
10973
     *
10974
     * @param array $attempt
10975
     *
10976
     * @return bool
10977
     */
10978
    public function isBlockedByPercentage($attempt = [])
10979
    {
10980
        if (empty($attempt)) {
10981
            return false;
10982
        }
10983
        $extraFieldValue = new ExtraFieldValue('exercise');
10984
        $blockExercise = $extraFieldValue->get_values_by_handler_and_field_variable(
10985
            $this->iId,
10986
            'blocking_percentage'
10987
        );
10988
10989
        if (empty($blockExercise['value'])) {
10990
            return false;
10991
        }
10992
10993
        $blockPercentage = (int) $blockExercise['value'];
10994
10995
        if (0 === $blockPercentage) {
10996
            return false;
10997
        }
10998
10999
        $percentage = 0;
11000
11001
        if (isset($attempt['exe_result']) && isset($attempt['exe_weighting'])) {
11002
            $weight = (int) $attempt['exe_weighting'];
11003
            $weight = (0 == $weight) ? 1 : $weight;
11004
            $percentage = float_format(
11005
                ($attempt['exe_result'] / $weight) * 100,
11006
                1
11007
            );
11008
        }
11009
        if ($percentage <= $blockPercentage && 0 != $percentage) {
11010
            return true;
11011
        }
11012
11013
        return false;
11014
    }
11015
11016
    /**
11017
     * Gets the question list ordered by the question_order setting (drag and drop).
11018
     *
11019
     * @param bool $adminView Optional.
11020
     *
11021
     * @return array
11022
     */
11023
    public function getQuestionOrderedList($adminView = false)
11024
    {
11025
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
11026
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
11027
11028
        // Getting question_order to verify that the question
11029
        // list is correct and all question_order's were set
11030
        $sql = "SELECT DISTINCT count(e.question_order) as count
11031
                FROM $TBL_EXERCICE_QUESTION e
11032
                INNER JOIN $TBL_QUESTIONS q
11033
                ON (e.question_id = q.id AND e.c_id = q.c_id)
11034
                WHERE
11035
                  e.c_id = {$this->course_id} AND
11036
                  e.exercice_id	= ".$this->id;
11037
11038
        $result = Database::query($sql);
11039
        $row = Database::fetch_array($result);
11040
        $count_question_orders = $row['count'];
11041
11042
        // Getting question list from the order (question list drag n drop interface).
11043
        $sql = "SELECT DISTINCT e.question_id, e.question_order
11044
                FROM $TBL_EXERCICE_QUESTION e
11045
                INNER JOIN $TBL_QUESTIONS q
11046
                ON (e.question_id = q.id AND e.c_id = q.c_id)
11047
                WHERE
11048
                    e.c_id = {$this->course_id} AND
11049
                    e.exercice_id = '".$this->id."'
11050
                ORDER BY question_order";
11051
        $result = Database::query($sql);
11052
11053
        // Fills the array with the question ID for this exercise
11054
        // the key of the array is the question position
11055
        $temp_question_list = [];
11056
        $counter = 1;
11057
        $questionList = [];
11058
        while ($new_object = Database::fetch_object($result)) {
11059
            if (!$adminView) {
11060
                // Correct order.
11061
                $questionList[$new_object->question_order] = $new_object->question_id;
11062
            } else {
11063
                $questionList[$counter] = $new_object->question_id;
11064
            }
11065
11066
            // Just in case we save the order in other array
11067
            $temp_question_list[$counter] = $new_object->question_id;
11068
            $counter++;
11069
        }
11070
11071
        if (!empty($temp_question_list)) {
11072
            /* If both array don't match it means that question_order was not correctly set
11073
               for all questions using the default mysql order */
11074
            if (count($temp_question_list) != $count_question_orders) {
11075
                $questionList = $temp_question_list;
11076
            }
11077
        }
11078
11079
        return $questionList;
11080
    }
11081
11082
    /**
11083
     * Get number of questions in exercise by user attempt.
11084
     *
11085
     * @return int
11086
     */
11087
    private function countQuestionsInExercise()
11088
    {
11089
        $lpId = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0;
11090
        $lpItemId = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0;
11091
        $lpItemViewId = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : 0;
11092
11093
        $trackInfo = $this->get_stat_track_exercise_info($lpId, $lpItemId, $lpItemViewId);
11094
11095
        if (!empty($trackInfo)) {
11096
            $questionIds = explode(',', $trackInfo['data_tracking']);
11097
11098
            return count($questionIds);
11099
        }
11100
11101
        return $this->getQuestionCount();
11102
    }
11103
11104
    /**
11105
     * Select N values from the questions per category array.
11106
     *
11107
     * @param array $categoriesAddedInExercise
11108
     * @param array $question_list
11109
     * @param array $questions_by_category
11110
     * @param bool  $flatResult
11111
     * @param bool  $randomizeQuestions
11112
     * @param array $questionsByCategoryMandatory
11113
     *
11114
     * @return array
11115
     */
11116
    private function pickQuestionsPerCategory(
11117
        $categoriesAddedInExercise,
11118
        $question_list,
11119
        &$questions_by_category,
11120
        $flatResult = true,
11121
        $randomizeQuestions = false,
11122
        $questionsByCategoryMandatory = []
11123
    ) {
11124
        $addAll = true;
11125
        $categoryCountArray = [];
11126
11127
        // Getting how many questions will be selected per category.
11128
        if (!empty($categoriesAddedInExercise)) {
11129
            $addAll = false;
11130
            // Parsing question according the category rel exercise settings
11131
            foreach ($categoriesAddedInExercise as $category_info) {
11132
                $category_id = $category_info['category_id'];
11133
                if (isset($questions_by_category[$category_id])) {
11134
                    // How many question will be picked from this category.
11135
                    $count = $category_info['count_questions'];
11136
                    // -1 means all questions
11137
                    $categoryCountArray[$category_id] = $count;
11138
                    if (-1 == $count) {
11139
                        $categoryCountArray[$category_id] = 999;
11140
                    }
11141
                }
11142
            }
11143
        }
11144
11145
        if (!empty($questions_by_category)) {
11146
            $temp_question_list = [];
11147
            foreach ($questions_by_category as $category_id => &$categoryQuestionList) {
11148
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
11149
                    $numberOfQuestions = 0;
11150
                    if (isset($categoryCountArray[$category_id])) {
11151
                        $numberOfQuestions = $categoryCountArray[$category_id];
11152
                    }
11153
                }
11154
11155
                if ($addAll) {
11156
                    $numberOfQuestions = 999;
11157
                }
11158
                if (!empty($numberOfQuestions)) {
11159
                    $mandatoryQuestions = [];
11160
                    if (isset($questionsByCategoryMandatory[$category_id])) {
11161
                        $mandatoryQuestions = $questionsByCategoryMandatory[$category_id];
11162
                    }
11163
11164
                    $elements = TestCategory::getNElementsFromArray(
11165
                        $categoryQuestionList,
11166
                        $numberOfQuestions,
11167
                        $randomizeQuestions,
11168
                        $mandatoryQuestions
11169
                    );
11170
                    if (!empty($elements)) {
11171
                        $temp_question_list[$category_id] = $elements;
11172
                        $categoryQuestionList = $elements;
11173
                    }
11174
                }
11175
            }
11176
11177
            if (!empty($temp_question_list)) {
11178
                if ($flatResult) {
11179
                    $temp_question_list = array_flatten($temp_question_list);
11180
                }
11181
                $question_list = $temp_question_list;
11182
            }
11183
        }
11184
11185
        return $question_list;
11186
    }
11187
11188
    /**
11189
     * Changes the exercise id.
11190
     *
11191
     * @param int $id - exercise id
11192
     */
11193
    private function updateId($id)
11194
    {
11195
        $this->id = $id;
11196
    }
11197
11198
    /**
11199
     * Sends a notification when a user ends an examn.
11200
     *
11201
     * @param array  $question_list_answers
11202
     * @param string $origin
11203
     * @param array  $user_info
11204
     * @param string $url_email
11205
     * @param array  $teachers
11206
     */
11207
    private function sendNotificationForOpenQuestions(
11208
        $question_list_answers,
11209
        $origin,
11210
        $user_info,
11211
        $url_email,
11212
        $teachers
11213
    ) {
11214
        // Email configuration settings
11215
        $courseCode = api_get_course_id();
11216
        $courseInfo = api_get_course_info($courseCode);
11217
        $sessionId = api_get_session_id();
11218
        $sessionData = '';
11219
        if (!empty($sessionId)) {
11220
            $sessionInfo = api_get_session_info($sessionId);
11221
            if (!empty($sessionInfo)) {
11222
                $sessionData = '<tr>'
11223
                    .'<td><em>'.get_lang('SessionName').'</em></td>'
11224
                    .'<td>&nbsp;<b>'.$sessionInfo['name'].'</b></td>'
11225
                    .'</tr>';
11226
            }
11227
        }
11228
11229
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
11230
            .get_lang('AttemptDetails').' : <br /><br />'
11231
            .'<table>'
11232
            .'<tr>'
11233
            .'<td><em>'.get_lang('CourseName').'</em></td>'
11234
            .'<td>&nbsp;<b>#course#</b></td>'
11235
            .'</tr>'
11236
            .$sessionData
11237
            .'<tr>'
11238
            .'<td>'.get_lang('TestAttempted').'</td>'
11239
            .'<td>&nbsp;#exercise#</td>'
11240
            .'</tr>'
11241
            .'<tr>'
11242
            .'<td>'.get_lang('StudentName').'</td>'
11243
            .'<td>&nbsp;#firstName# #lastName#</td>'
11244
            .'</tr>'
11245
            .'<tr>'
11246
            .'<td>'.get_lang('StudentEmail').'</td>'
11247
            .'<td>&nbsp;#mail#</td>'
11248
            .'</tr>'
11249
            .'</table>';
11250
11251
        $open_question_list = null;
11252
        foreach ($question_list_answers as $item) {
11253
            $question = $item['question'];
11254
            $answer = $item['answer'];
11255
            $answer_type = $item['answer_type'];
11256
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
11257
                $open_question_list .=
11258
                    '<tr>'
11259
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
11260
                    .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
11261
                    .'</tr>'
11262
                    .'<tr>'
11263
                    .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
11264
                    .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
11265
                    .'</tr>';
11266
            }
11267
        }
11268
11269
        if (!empty($open_question_list)) {
11270
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
11271
                '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
11272
            $msg .= $open_question_list;
11273
            $msg .= '</table><br />';
11274
11275
            $msg = str_replace('#exercise#', $this->exercise, $msg);
11276
            $msg = str_replace('#firstName#', $user_info['firstname'], $msg);
11277
            $msg = str_replace('#lastName#', $user_info['lastname'], $msg);
11278
            $msg = str_replace('#mail#', $user_info['email'], $msg);
11279
            $msg = str_replace(
11280
                '#course#',
11281
                Display::url($courseInfo['title'], $courseInfo['course_public_url'].'?id_session='.$sessionId),
11282
                $msg
11283
            );
11284
11285
            if ($origin != 'learnpath') {
11286
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
11287
            }
11288
            $msg = str_replace('#url#', $url_email, $msg);
11289
            $subject = get_lang('OpenQuestionsAttempted');
11290
11291
            if (!empty($teachers)) {
11292
                foreach ($teachers as $user_id => $teacher_data) {
11293
                    MessageManager::send_message_simple(
11294
                        $user_id,
11295
                        $subject,
11296
                        $msg
11297
                    );
11298
                }
11299
            }
11300
        }
11301
    }
11302
11303
    /**
11304
     * Send notification for oral questions.
11305
     *
11306
     * @param array  $question_list_answers
11307
     * @param string $origin
11308
     * @param int    $exe_id
11309
     * @param array  $user_info
11310
     * @param string $url_email
11311
     * @param array  $teachers
11312
     */
11313
    private function sendNotificationForOralQuestions(
11314
        $question_list_answers,
11315
        $origin,
11316
        $exe_id,
11317
        $user_info,
11318
        $url_email,
11319
        $teachers
11320
    ) {
11321
        // Email configuration settings
11322
        $courseCode = api_get_course_id();
11323
        $courseInfo = api_get_course_info($courseCode);
11324
        $oral_question_list = null;
11325
        foreach ($question_list_answers as $item) {
11326
            $question = $item['question'];
11327
            $file = $item['generated_oral_file'];
11328
            $answer = $item['answer'];
11329
            if (0 == $answer) {
11330
                $answer = '';
11331
            }
11332
            $answer_type = $item['answer_type'];
11333
            if (!empty($question) && (!empty($answer) || !empty($file)) && $answer_type == ORAL_EXPRESSION) {
11334
                if (!empty($file)) {
11335
                    $file = Display::url($file, $file);
11336
                }
11337
                $oral_question_list .= '<br />
11338
                    <table width="730" height="136" border="0" cellpadding="3" cellspacing="3">
11339
                    <tr>
11340
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>
11341
                        <td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>
11342
                    </tr>
11343
                    <tr>
11344
                        <td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>
11345
                        <td valign="top" bgcolor="#F3F3F3">'.$answer.$file.'</td>
11346
                    </tr></table>';
11347
            }
11348
        }
11349
11350
        if (!empty($oral_question_list)) {
11351
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
11352
                    '.get_lang('AttemptDetails').' : <br /><br />
11353
                    <table>
11354
                        <tr>
11355
                            <td><em>'.get_lang('CourseName').'</em></td>
11356
                            <td>&nbsp;<b>#course#</b></td>
11357
                        </tr>
11358
                        <tr>
11359
                            <td>'.get_lang('TestAttempted').'</td>
11360
                            <td>&nbsp;#exercise#</td>
11361
                        </tr>
11362
                        <tr>
11363
                            <td>'.get_lang('StudentName').'</td>
11364
                            <td>&nbsp;#firstName# #lastName#</td>
11365
                        </tr>
11366
                        <tr>
11367
                            <td>'.get_lang('StudentEmail').'</td>
11368
                            <td>&nbsp;#mail#</td>
11369
                        </tr>
11370
                    </table>';
11371
            $msg .= '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'), $oral_question_list).'<br />';
11372
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
11373
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
11374
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
11375
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
11376
            $msg = str_replace("#course#", $courseInfo['name'], $msg1);
11377
11378
            if (!in_array($origin, ['learnpath', 'embeddable'])) {
11379
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
11380
            }
11381
            $msg1 = str_replace("#url#", $url_email, $msg);
11382
            $mail_content = $msg1;
11383
            $subject = get_lang('OralQuestionsAttempted');
11384
11385
            if (!empty($teachers)) {
11386
                foreach ($teachers as $user_id => $teacher_data) {
11387
                    MessageManager::send_message_simple(
11388
                        $user_id,
11389
                        $subject,
11390
                        $mail_content
11391
                    );
11392
                }
11393
            }
11394
        }
11395
    }
11396
11397
    /**
11398
     * Returns an array with the media list.
11399
     *
11400
     * @param array question list
11401
     *
11402
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
11403
     * <code>
11404
     * array (size=2)
11405
     *  999 =>
11406
     *    array (size=3)
11407
     *      0 => int 7
11408
     *      1 => int 6
11409
     *      2 => int 3254
11410
     *  100 =>
11411
     *   array (size=1)
11412
     *      0 => int 5
11413
     *  </code>
11414
     */
11415
    private function setMediaList($questionList)
11416
    {
11417
        $mediaList = [];
11418
        /*
11419
         * Media feature is not activated in 1.11.x
11420
        if (!empty($questionList)) {
11421
            foreach ($questionList as $questionId) {
11422
                $objQuestionTmp = Question::read($questionId, $this->course_id);
11423
                // If a media question exists
11424
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
11425
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
11426
                } else {
11427
                    // Always the last item
11428
                    $mediaList[999][] = $objQuestionTmp->id;
11429
                }
11430
            }
11431
        }*/
11432
11433
        $this->mediaList = $mediaList;
11434
    }
11435
11436
    /**
11437
     * @return HTML_QuickForm_group
11438
     */
11439
    private function setResultDisabledGroup(FormValidator $form)
11440
    {
11441
        $resultDisabledGroup = [];
11442
11443
        $resultDisabledGroup[] = $form->createElement(
11444
            'radio',
11445
            'results_disabled',
11446
            null,
11447
            get_lang('ShowScoreAndRightAnswer'),
11448
            RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS,
11449
            ['id' => 'result_disabled_0']
11450
        );
11451
11452
        $warning = sprintf(
11453
            get_lang('TheSettingXWillChangeToX'),
11454
            get_lang('FeedbackType'),
11455
            get_lang('NoFeedback')
11456
        );
11457
11458
        $resultDisabledGroup[] = $form->createElement(
11459
            'radio',
11460
            'results_disabled',
11461
            null,
11462
            get_lang('DoNotShowScoreNorRightAnswer'),
11463
            RESULT_DISABLE_NO_SCORE_AND_EXPECTED_ANSWERS,
11464
            [
11465
                'id' => 'result_disabled_1',
11466
                //'onclick' => 'check_results_disabled()'
11467
                'onclick' => 'javascript:if(confirm('."'".addslashes($warning)."'".')) { check_results_disabled(); } else { return false;} ',
11468
            ]
11469
        );
11470
11471
        $resultDisabledGroup[] = $form->createElement(
11472
            'radio',
11473
            'results_disabled',
11474
            null,
11475
            get_lang('OnlyShowScore'),
11476
            RESULT_DISABLE_SHOW_SCORE_ONLY,
11477
            [
11478
                'id' => 'result_disabled_2',
11479
                //'onclick' => 'check_results_disabled()'
11480
                'onclick' => 'javascript:if(confirm('."'".addslashes($warning)."'".')) { check_results_disabled(); } else { return false;} ',
11481
            ]
11482
        );
11483
11484
        if (in_array($this->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
11485
            return $form->addGroup(
11486
                $resultDisabledGroup,
11487
                null,
11488
                get_lang('ShowResultsToStudents')
11489
            );
11490
        }
11491
11492
        $resultDisabledGroup[] = $form->createElement(
11493
            'radio',
11494
            'results_disabled',
11495
            null,
11496
            get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
11497
            RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT,
11498
            ['id' => 'result_disabled_4']
11499
        );
11500
11501
        $resultDisabledGroup[] = $form->createElement(
11502
            'radio',
11503
            'results_disabled',
11504
            null,
11505
            get_lang('DontShowScoreOnlyWhenUserFinishesAllAttemptsButShowFeedbackEachAttempt'),
11506
            RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK,
11507
            [
11508
                'id' => 'result_disabled_5',
11509
                //'onclick' => 'check_results_disabled()'
11510
                'onclick' => 'javascript:if(confirm('."'".addslashes($warning)."'".')) { check_results_disabled(); } else { return false;} ',
11511
            ]
11512
        );
11513
11514
        $resultDisabledGroup[] = $form->createElement(
11515
            'radio',
11516
            'results_disabled',
11517
            null,
11518
            get_lang('ExerciseRankingMode'),
11519
            RESULT_DISABLE_RANKING,
11520
            ['id' => 'result_disabled_6']
11521
        );
11522
11523
        $resultDisabledGroup[] = $form->createElement(
11524
            'radio',
11525
            'results_disabled',
11526
            null,
11527
            get_lang('ExerciseShowOnlyGlobalScoreAndCorrectAnswers'),
11528
            RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
11529
            ['id' => 'result_disabled_7']
11530
        );
11531
11532
        $resultDisabledGroup[] = $form->createElement(
11533
            'radio',
11534
            'results_disabled',
11535
            null,
11536
            get_lang('ExerciseAutoEvaluationAndRankingMode'),
11537
            RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
11538
            ['id' => 'result_disabled_8']
11539
        );
11540
11541
        $resultDisabledGroup[] = $form->createElement(
11542
            'radio',
11543
            'results_disabled',
11544
            null,
11545
            get_lang('ExerciseCategoriesRadarMode'),
11546
            RESULT_DISABLE_RADAR,
11547
            ['id' => 'result_disabled_9']
11548
        );
11549
11550
        $resultDisabledGroup[] = $form->createElement(
11551
            'radio',
11552
            'results_disabled',
11553
            null,
11554
            get_lang('ShowScoreEveryAttemptShowAnswersLastAttemptNoFeedback'),
11555
            RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK,
11556
            ['id' => 'result_disabled_10']
11557
        );
11558
11559
        return $form->addGroup(
11560
            $resultDisabledGroup,
11561
            null,
11562
            get_lang('ShowResultsToStudents')
11563
        );
11564
    }
11565
}
11566