Passed
Push — master ( b5271b...194f14 )
by Julito
17:56
created

Exercise   F

Complexity

Total Complexity 1403

Size/Duplication

Total Lines 10885
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5909
dl 0
loc 10885
rs 0.8
c 1
b 0
f 0
wmc 1403

180 Methods

Rating   Name   Duplication   Size   Complexity  
A getCutTitle() 0 5 1
A selectSound() 0 3 1
A getModelType() 0 3 1
A getFeedbackType() 0 3 1
A selectTimeLimit() 0 3 1
A selectDescription() 0 3 1
A selectEndButton() 0 3 1
A selectTitle() 0 7 2
A selectDisplayCategoryName() 0 3 1
A selectType() 0 3 1
A selectPassPercentage() 0 3 1
A selectAttempts() 0 3 1
A getQuestionOrderedListByName() 0 25 4
A getQuestionCount() 0 20 2
A __construct() 0 49 2
A countQuestionsInExercise() 0 15 5
A addToList() 0 18 4
A save_categories_in_exercise() 0 16 5
A mediaIsActivated() 0 18 6
A getRandomByCategory() 0 3 1
A updateAttempts() 0 3 1
A setRandom() 0 3 1
A updateCategories() 0 5 2
A removeFromList() 0 20 6
A updateResultsDisabled() 0 3 1
A getQuestionForTeacher() 0 28 3
D getDelineationResult() 0 145 16
A getHideQuestionTitle() 0 3 1
C transformQuestionListWithMedias() 0 44 12
C progressExercisePaginationBarWithCategories() 0 120 15
A setPageResultConfigurationDefaults() 0 5 3
A hasQuestionWithTypeNotInList() 0 22 2
A cleanCourseLaunchSettings() 0 6 1
C renderQuestionList() 0 113 14
A enable_results() 0 3 1
D renderQuestion() 0 182 19
B search_engine_edit() 0 75 9
A update_question_positions() 0 25 4
A updateTitle() 0 3 1
A addAllQuestionToRemind() 0 17 2
A save_stat_track_exercise_info() 0 41 2
A getPreventBackwards() 0 8 2
A updateDescription() 0 3 1
A getResultsAccess() 0 12 2
A countUserAnswersSavedInExercise() 0 5 1
B getQuestionRibbon() 0 49 8
A showTimeControlJS() 0 59 3
A fill_in_blank_answer_to_array() 0 10 2
A getMediaList() 0 3 1
A getCorrectAnswersInAllAttempts() 0 3 1
A format_title_variable() 0 3 1
A enableAutoLaunch() 0 6 1
A selectResultsDisabled() 0 3 1
F manage_answer() 0 2602 430
A enable() 0 3 1
B get_validated_question_list() 0 62 8
B progressExercisePaginationBar() 0 58 7
B get_exercise_result() 0 37 6
A get_stat_track_exercise_info_by_exe_id() 0 19 5
A updateType() 0 3 1
A setNotifications() 0 3 1
F read() 0 106 21
D processCreation() 0 99 11
A setGlobalCategoryId() 0 6 3
C pickQuestionsPerCategory() 0 71 16
A updatePassPercentage() 0 3 1
A setHideQuestionTitle() 0 3 1
A updateExpiredTime() 0 3 1
A getUserAnswersSavedInExercise() 0 27 6
A getAutoLaunch() 0 3 1
A updateRandomAnswers() 0 3 1
A updateDisplayCategoryName() 0 3 1
A getRandomList() 0 35 6
A getQuestionWithCategories() 0 24 2
A disable() 0 3 1
A hasQuestion() 0 18 1
A getResultAccess() 0 13 3
C sendNotificationForOralQuestions() 0 83 12
A getCategoriesInExercise() 0 18 4
F show_button() 0 171 35
A setScoreTypeModel() 0 3 1
A get_question_list() 0 6 1
B setResultFeedbackGroup() 0 87 6
A setModelType() 0 3 1
A setExerciseCategoryId() 0 4 2
B allowAction() 0 36 9
D generateStats() 0 134 19
F exerciseGridResource() 0 805 109
F getQuestionListWithCategoryListFilteredByCategorySettings() 0 239 22
B updateSound() 0 29 8
B selectQuestionList() 0 36 7
A hasQuestionWithType() 0 18 1
A updateTextWhenFinished() 0 3 1
C editQuestionToRemind() 0 52 13
A isRandomByCat() 0 10 3
A setPageResultConfiguration() 0 13 6
B getReminderTable() 0 122 8
A setCategoriesGrouping() 0 3 1
F is_visible() 0 242 43
C getNextQuestionId() 0 70 16
A getQuestionListWithMediasCompressed() 0 3 1
A cleanSessionVariables() 0 20 1
B cleanResults() 0 71 7
A updateEndButton() 0 3 1
B delete() 0 52 8
C get_max_score() 0 54 14
A getResultAccessTimeDiff() 0 13 3
A getQuestionOrderedList() 0 57 5
A showSimpleTimeControl() 0 42 1
A getPageResultConfiguration() 0 11 2
A updateStatus() 0 3 1
A getGlobalCategoryId() 0 3 1
A setShowPreviousButton() 0 5 1
A updateRandomByCat() 0 12 2
A setNotifyUserByEmail() 0 3 1
A added_in_lp() 0 14 2
C showExerciseResultHeader() 0 112 13
A fill_in_blank_answer_to_string() 0 19 4
A getQuestionSelectionType() 0 3 1
A selectPropagateNeg() 0 3 1
A getNotifications() 0 3 1
B copyExercise() 0 55 11
A getRandomAnswers() 0 3 1
A getExercisesByCourseSession() 0 22 2
A getExerciseAndResult() 0 40 5
A updateEmailNotificationTemplate() 0 3 1
A selectNbrQuestions() 0 3 1
A getQuestionListWithMediasUncompressed() 0 3 1
A getPositionInCompressedQuestionList() 0 23 6
A setMediaList() 0 19 1
A getLpListFromExercise() 0 27 2
A removeAllQuestionToRemind() 0 13 2
B search_engine_save() 0 55 8
A updatePropagateNegative() 0 3 1
A getScoreTypeModel() 0 3 1
A getUnformattedTitle() 0 3 1
B getRadarsFromUsers() 0 52 8
A updateFeedbackType() 0 3 1
A selectExpiredTime() 0 3 1
A selectStatus() 0 3 1
A setEmailNotificationTemplateToUser() 0 3 1
A getQuestionList() 0 3 1
A getPageConfigurationAttribute() 0 9 3
B saveExerciseInLp() 0 79 9
A getShuffle() 0 3 1
A get_formated_title_variable() 0 3 1
C transform_question_list_with_medias() 0 42 12
C sendNotificationForOpenQuestions() 0 93 11
A showExpectedChoiceColumn() 0 21 4
A setOnSuccessMessage() 0 3 1
A setOnFailedMessage() 0 3 1
C getAverageRadarsFromUsers() 0 69 13
A disable_results() 0 3 1
A updateSaveCorrectAnswers() 0 3 1
A setQuestionList() 0 9 1
C getAnswersInAllAttempts() 0 56 12
F createForm() 0 651 41
F save() 0 134 22
A getSaveCorrectAnswers() 0 3 1
F send_mail_notification_for_exam() 0 193 30
A getTextWhenFinished() 0 3 1
B search_engine_delete() 0 39 6
A getExerciseCategoryId() 0 7 2
A format_title() 0 3 1
A getId() 0 3 1
A setQuestionSelectionType() 0 3 1
A isInList() 0 8 2
A get_stat_track_exercise_info() 0 35 5
A updateReviewAnswers() 0 3 3
A showPreviousButton() 0 8 2
A get_formated_title() 0 6 2
C getQuestionListPagination() 0 106 16
A getNumberQuestionExerciseCategory() 0 16 3
A showExpectedChoice() 0 3 1
B setResultDisabledGroup() 0 116 2
A isRandom() 0 9 3
A hasResultsAccess() 0 8 2
A getLpBySession() 0 18 4
B getRadar() 0 78 7
A returnTimeLeftDiv() 0 20 1

How to fix   Complexity   

Complex Class

Complex classes like Exercise often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Exercise, and based on these observations, apply Extract Interface, too.

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

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

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

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

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