Passed
Push — 1.11.x ( afc62b...1b1584 )
by Julito
09:56
created

Exercise::saveExerciseInLp()   B

Complexity

Conditions 9

Size

Total Lines 79
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 55
nop 2
dl 0
loc 79
rs 7.4262
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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