Passed
Push — master ( 271415...ff556a )
by Julito
09:26
created

Exercise   F

Complexity

Total Complexity 1304

Size/Duplication

Total Lines 10090
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 5585
dl 0
loc 10090
rs 0.8
c 0
b 0
f 0
wmc 1304

167 Methods

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