Completed
Push — 1.10.x ( a788c0...4b0069 )
by Angel Fernando Quiroz
44:46
created

Exercise::renderQuestion()   F

Complexity

Conditions 22
Paths 1923

Size

Total Lines 127
Code Lines 74

Duplication

Lines 10
Ratio 7.87 %
Metric Value
cc 22
eloc 74
nc 1923
nop 9
dl 10
loc 127
rs 2

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use ChamiloSession as Session;
5
6
/**
7
 * Class Exercise
8
 *
9
 * Allows to instantiate an object of type Exercise
10
 * @package chamilo.exercise
11
 * @author Olivier Brouckaert
12
 * @author Julio Montoya Cleaning exercises
13
 * Modified by Hubert Borderiou #294
14
 */
15
class Exercise
16
{
17
    public $id;
18
    public $name;
19
    public $title;
20
    public $exercise;
21
    public $description;
22
    public $sound;
23
    public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE
24
    public $random;
25
    public $random_answers;
26
    public $active;
27
    public $timeLimit;
28
    public $attempts;
29
    public $feedback_type;
30
    public $end_time;
31
    public $start_time;
32
    public $questionList;  // array with the list of this exercise's questions
33
    /* including question list of the media */
34
    public $questionListUncompressed;
35
    public $results_disabled;
36
    public $expired_time;
37
    public $course;
38
    public $course_id;
39
    public $propagate_neg;
40
    public $review_answers;
41
    public $randomByCat;
42
    public $text_when_finished;
43
    public $display_category_name;
44
    public $pass_percentage;
45
    public $edit_exercise_in_lp = false;
46
    public $is_gradebook_locked = false;
47
    public $exercise_was_added_in_lp = false;
48
    public $lpList = array();
49
    public $force_edit_exercise_in_lp = false;
50
    public $categories;
51
    public $categories_grouping = true;
52
    public $endButton = 0;
53
    public $categoryWithQuestionList;
54
    public $mediaList;
55
    public $loadQuestionAJAX = false;
56
    // Notification send to the teacher.
57
    public $emailNotificationTemplate = null;
58
    // Notification send to the student.
59
    public $emailNotificationTemplateToUser = null;
60
    public $countQuestions = 0;
61
    public $fastEdition = false;
62
    public $modelType = 1;
63
    public $questionSelectionType = EX_Q_SELECTION_ORDERED;
64
    public $hideQuestionTitle = 0;
65
    public $scoreTypeModel = 0;
66
    public $categoryMinusOne = true; // Shows the category -1: See BT#6540
67
    public $globalCategoryId = null;
68
    public $onSuccessMessage = null;
69
    public $onFailedMessage = null;
70
    public $emailAlert;
71
    public $notifyUserByEmail = 0;
72
    public $sessionId = 0;
73
    public $specialCategoryOrders = false;
74
    public $quizRelCategoryTable = false;
75
    // CREATE TABLE c_quiz_rel_category (iid BIGINT AUTO_INCREMENT NOT NULL, c_id INT NOT NULL, category_id INT NOT NULL, exercise_id INT NOT NULL, count_questions INT NOT NULL, PRIMARY KEY(iid));
76
    // ALTER TABLE c_quiz ADD COLUMN question_selection_type INT;
77
78
    /**
79
     * Constructor of the class
80
     *
81
     * @author Olivier Brouckaert
82
     */
83
    public function __construct($course_id = null)
84
    {
85
        $this->id = 0;
86
        $this->exercise = '';
87
        $this->description = '';
88
        $this->sound = '';
89
        $this->type = ALL_ON_ONE_PAGE;
90
        $this->random = 0;
91
        $this->random_answers = 0;
92
        $this->active = 1;
93
        $this->questionList = array();
94
        $this->timeLimit = 0;
95
        $this->end_time = '0000-00-00 00:00:00';
96
        $this->start_time = '0000-00-00 00:00:00';
97
        $this->results_disabled = 1;
98
        $this->expired_time = '0000-00-00 00:00:00';
99
        $this->propagate_neg = 0;
100
        $this->review_answers = false;
101
        $this->randomByCat = 0;
102
        $this->text_when_finished = '';
103
        $this->display_category_name = 0;
104
        $this->pass_percentage = '';
105
106
        $this->modelType = 1;
107
        $this->questionSelectionType = EX_Q_SELECTION_ORDERED;
108
        $this->endButton = 0;
109
        $this->scoreTypeModel = 0;
110
        $this->globalCategoryId = null;
111
112
        if (!empty($course_id)) {
113
            $course_info = api_get_course_info_by_id($course_id);
114
        } else {
115
            $course_info = api_get_course_info();
116
        }
117
        $this->course_id = $course_info['real_id'];
118
        $this->course = $course_info;
119
        $this->specialCategoryOrders = api_get_configuration_value('exercise_enable_category_order');
120
    }
121
122
    /**
123
     * Reads exercise information from the data base
124
     *
125
     * @author Olivier Brouckaert
126
     * @param integer $id - exercise Id
127
     *
128
     * @return boolean - true if exercise exists, otherwise false
129
     */
130
    public function read($id, $parseQuestionList = true)
131
    {
132
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
133
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
134
135
        $id  = intval($id);
136
        if (empty($this->course_id)) {
137
138
            return false;
139
        }
140
        $sql = "SELECT * FROM $TBL_EXERCISES WHERE c_id = ".$this->course_id." AND id = ".$id;
141
        $result = Database::query($sql);
142
143
        // if the exercise has been found
144
        if ($object = Database::fetch_object($result)) {
145
            $this->id = $id;
146
            $this->exercise = $object->title;
147
            $this->name = $object->title;
148
            $this->title = $object->title;
149
            $this->description = $object->description;
150
            $this->sound = $object->sound;
151
            $this->type = $object->type;
152
            if (empty($this->type)) {
153
                $this->type = ONE_PER_PAGE;
154
            }
155
            $this->random = $object->random;
156
            $this->random_answers = $object->random_answers;
157
            $this->active = $object->active;
158
            $this->results_disabled = $object->results_disabled;
159
            $this->attempts = $object->max_attempt;
160
            $this->feedback_type = $object->feedback_type;
161
            $this->propagate_neg = $object->propagate_neg;
162
            $this->randomByCat = $object->random_by_category;
163
            $this->text_when_finished = $object->text_when_finished;
164
            $this->display_category_name = $object->display_category_name;
165
            $this->pass_percentage = $object->pass_percentage;
166
            $this->sessionId = $object->session_id;
167
            $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE);
168
            $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false;
169
            $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null;
170
            $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null;
171
172
            $sql = "SELECT lp_id, max_score
173
                    FROM $table_lp_item
174
                    WHERE   c_id = {$this->course_id} AND
175
                            item_type = '".TOOL_QUIZ."' AND
176
                            path = '".$id."'";
177
            $result = Database::query($sql);
178
179
            if (Database::num_rows($result) > 0) {
180
                $this->exercise_was_added_in_lp = true;
181
                $this->lpList = Database::store_result($result, 'ASSOC');
182
            }
183
184
            $this->force_edit_exercise_in_lp = api_get_configuration_value('force_edit_exercise_in_lp');
185
186
            if ($this->exercise_was_added_in_lp) {
187
                $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true;
188
            } else {
189
                $this->edit_exercise_in_lp = true;
190
            }
191
192
            if ($object->end_time != '0000-00-00 00:00:00') {
193
                $this->end_time = $object->end_time;
194
            }
195
            if ($object->start_time != '0000-00-00 00:00:00') {
196
                $this->start_time = $object->start_time;
197
            }
198
199
            //control time
200
            $this->expired_time = $object->expired_time;
201
202
            //Checking if question_order is correctly set
203
204
            //$this->questionList     = $this->selectQuestionList(true);
205
            if ($parseQuestionList) {
206
                $this->setQuestionList();
207
            }
208
209
            //overload questions list with recorded questions list
210
            //load questions only for exercises of type 'one question per page'
211
            //this is needed only is there is no questions
212
            /*
213
			// @todo not sure were in the code this is used somebody mess with the exercise tool
214
			// @todo don't know who add that config and why $_configuration['live_exercise_tracking']
215
			global $_configuration, $questionList;
216
			if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST' && defined('QUESTION_LIST_ALREADY_LOGGED') &&
217
			isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']) {
218
				$this->questionList = $questionList;
219
			}*/
220
            return true;
221
        }
222
223
        return false;
224
    }
225
226
    /**
227
     * @return string
228
     */
229
    public function getCutTitle()
230
    {
231
        return cut($this->exercise, EXERCISE_MAX_NAME_SIZE);
232
    }
233
234
    /**
235
     * returns the exercise ID
236
     *
237
     * @author Olivier Brouckaert
238
     * @return int - exercise ID
239
     */
240
    public function selectId()
241
    {
242
        return $this->id;
243
    }
244
245
    /**
246
     * returns the exercise title
247
     *
248
     * @author Olivier Brouckaert
249
     * @return string - exercise title
250
     */
251
    public function selectTitle()
252
    {
253
        return $this->exercise;
254
    }
255
256
    /**
257
     * returns the number of attempts setted
258
     *
259
     * @return int - exercise attempts
260
     */
261
    public function selectAttempts()
262
    {
263
        return $this->attempts;
264
    }
265
266
    /** returns the number of FeedbackType  *
267
     *  0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback
268
     * @return int - exercise attempts
269
     */
270
    public function selectFeedbackType()
271
    {
272
        return $this->feedback_type;
273
    }
274
275
    /**
276
     * returns the time limit
277
     */
278
    public function selectTimeLimit()
279
    {
280
        return $this->timeLimit;
281
    }
282
283
    /**
284
     * returns the exercise description
285
     *
286
     * @author Olivier Brouckaert
287
     * @return string - exercise description
288
     */
289
    public function selectDescription()
290
    {
291
        return $this->description;
292
    }
293
294
    /**
295
     * returns the exercise sound file
296
     *
297
     * @author Olivier Brouckaert
298
     * @return string - exercise description
299
     */
300
    public function selectSound()
301
    {
302
        return $this->sound;
303
    }
304
305
    /**
306
     * returns the exercise type
307
     *
308
     * @author Olivier Brouckaert
309
     * @return integer - exercise type
310
     */
311
    public function selectType()
312
    {
313
        return $this->type;
314
    }
315
316
    /**
317
     * @return int
318
     */
319
    public function getModelType()
320
    {
321
        return $this->modelType;
322
    }
323
324
    /**
325
     * @return int
326
     */
327
    public function selectEndButton()
328
    {
329
        return $this->endButton;
330
    }
331
332
    /**
333
     * @return string
334
     */
335
    public function getOnSuccessMessage()
336
    {
337
        return $this->onSuccessMessage;
338
    }
339
340
    /**
341
     * @return string
342
     */
343
    public function getOnFailedMessage()
344
    {
345
        return $this->onFailedMessage;
346
    }
347
348
    /**
349
     * @author hubert borderiou 30-11-11
350
     * @return integer : do we display the question category name for students
351
     */
352
    public function selectDisplayCategoryName()
353
    {
354
        return $this->display_category_name;
355
    }
356
357
    /**
358
     * @return int
359
     */
360
    public function selectPassPercentage()
361
    {
362
        return $this->pass_percentage;
363
    }
364
365
    /**
366
     *
367
     * Modify object to update the switch display_category_name
368
     * @author hubert borderiou 30-11-11
369
     * @param int $in_txt is an integer 0 or 1
370
     */
371
    public function updateDisplayCategoryName($in_txt)
372
    {
373
        $this->display_category_name = $in_txt;
374
    }
375
376
    /**
377
     * @author hubert borderiou 28-11-11
378
     * @return string html text : the text to display ay the end of the test.
379
     */
380
    public function selectTextWhenFinished()
381
    {
382
        return $this->text_when_finished;
383
    }
384
385
    /**
386
     * @author hubert borderiou 28-11-11
387
     * @return string  html text : update the text to display ay the end of the test.
388
     */
389
    public function updateTextWhenFinished($in_txt)
390
    {
391
        $this->text_when_finished = $in_txt;
392
    }
393
394
    /**
395
     * return 1 or 2 if randomByCat
396
     * @author hubert borderiou
397
     * @return integer - quiz random by category
398
     */
399
    public function selectRandomByCat()
400
    {
401
        return $this->randomByCat;
402
    }
403
404
    /**
405
     * return 0 if no random by cat
406
     * return 1 if random by cat, categories shuffled
407
     * return 2 if random by cat, categories sorted by alphabetic order
408
     * @author hubert borderiou
409
     * @return integer - quiz random by category
410
     */
411
    public function isRandomByCat()
412
    {
413
        /*$res = 0;
414
        if ($this->randomByCat == 1) {
415
            $res = 1;
416
        } else if ($this->randomByCat == 2) {
417
            $res = 2;
418
        }
419
        */
420
421
        $res = EXERCISE_CATEGORY_RANDOM_DISABLED;
422
        if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) {
423
            $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED;
424
        } else if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) {
425
            $res = EXERCISE_CATEGORY_RANDOM_ORDERED;
426
        }
427
428
        return $res;
429
    }
430
431
    /**
432
     * return nothing
433
     * update randomByCat value for object
434
     * @param int $random
435
     *
436
     * @author hubert borderiou
437
     */
438
    public function updateRandomByCat($random)
439
    {
440
        if ($this->specialCategoryOrders) {
441
            if (in_array($random, array(
442
                    EXERCISE_CATEGORY_RANDOM_SHUFFLED,
443
                    EXERCISE_CATEGORY_RANDOM_ORDERED,
444
                    EXERCISE_CATEGORY_RANDOM_DISABLED
445
                )
446
            )) {
447
                $this->randomByCat = $random;
448
            } else {
449
                $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED;
450
            }
451
        } else {
452
            if ($random == 1) {
453
                $this->randomByCat = 1;
454
            } else if ($random == 2) {
455
                $this->randomByCat = 2;
456
            } else {
457
                $this->randomByCat = 0;
458
            }
459
        }
460
    }
461
462
    /**
463
     * Tells if questions are selected randomly, and if so returns the draws
464
     *
465
     * @author Carlos Vargas
466
     * @return integer - results disabled exercise
467
     */
468
    public function selectResultsDisabled()
469
    {
470
        return $this->results_disabled;
471
    }
472
473
    /**
474
     * tells if questions are selected randomly, and if so returns the draws
475
     *
476
     * @author Olivier Brouckaert
477
     * @return integer - 0 if not random, otherwise the draws
478
     */
479
    public function isRandom()
480
    {
481
        if($this->random > 0 || $this->random == -1) {
482
            return true;
483
        } else {
484
            return false;
485
        }
486
    }
487
488
    /**
489
     * returns random answers status.
490
     *
491
     * @author Juan Carlos Rana
492
     */
493
    public function selectRandomAnswers()
494
    {
495
        return $this->random_answers;
496
    }
497
498
    /**
499
     * Same as isRandom() but has a name applied to values different than 0 or 1
500
     */
501
    public function getShuffle()
502
    {
503
        return $this->random;
504
    }
505
506
    /**
507
     * returns the exercise status (1 = enabled ; 0 = disabled)
508
     *
509
     * @author Olivier Brouckaert
510
     * @return boolean - true if enabled, otherwise false
511
     */
512
    public function selectStatus()
513
    {
514
        return $this->active;
515
    }
516
517
    /**
518
     * If false the question list will be managed as always if true the question will be filtered
519
     * depending of the exercise settings (table c_quiz_rel_category)
520
     * @param bool active or inactive grouping
521
     **/
522
    public function setCategoriesGrouping($status)
523
    {
524
        $this->categories_grouping = (bool) $status;
525
    }
526
527
    /**
528
     * @return int
529
     */
530
    public function getHideQuestionTitle()
531
    {
532
        return $this->hideQuestionTitle;
533
    }
534
535
    /**
536
     * @param $value
537
     */
538
    public function setHideQuestionTitle($value)
539
    {
540
        $this->hideQuestionTitle = intval($value);
541
    }
542
543
    /**
544
     * @return int
545
     */
546
    public function getScoreTypeModel()
547
    {
548
        return $this->scoreTypeModel;
549
    }
550
551
    /**
552
     * @param int $value
553
     */
554
    public function setScoreTypeModel($value)
555
    {
556
        $this->scoreTypeModel = intval($value);
557
    }
558
559
    /**
560
     * @return int
561
     */
562
    public function getGlobalCategoryId()
563
    {
564
        return $this->globalCategoryId;
565
    }
566
567
    /**
568
     * @param int $value
569
     */
570
    public function setGlobalCategoryId($value)
571
    {
572
        if (is_array($value) && isset($value[0])) {
573
            $value = $value[0];
574
        }
575
        $this->globalCategoryId = intval($value);
576
    }
577
578
    /**
579
     *
580
     * @param int $start
581
     * @param int $limit
582
     * @param int $sidx
583
     * @param string $sord
584
     * @param array $where_condition
585
     * @param array $extraFields
586
     */
587
    public function getQuestionListPagination($start, $limit, $sidx, $sord, $where_condition = array(), $extraFields = array())
588
    {
589
        if (!empty($this->id)) {
590
            $category_list = TestCategory::getListOfCategoriesNameForTest($this->id, false);
591
            $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
592
            $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
593
594
            $sql = "SELECT q.iid
595
                    FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS  q
596
                        ON (e.question_id = q.iid AND e.c_id = ".$this->course_id." )
597
					WHERE e.exercice_id	= '".Database::escape_string($this->id)."'
598
					";
599
600
            $orderCondition = "ORDER BY question_order";
601
602
            if (!empty($sidx) && !empty($sord)) {
603
                if ($sidx == 'question') {
604
605
                    if (in_array(strtolower($sord), array('desc', 'asc'))) {
606
                        $orderCondition = " ORDER BY q.$sidx $sord";
607
                    }
608
                }
609
            }
610
611
            $sql .= $orderCondition;
612
613
            $limitCondition = null;
614
615
            if (isset($start) && isset($limit)) {
616
                $start = intval($start);
617
                $limit = intval($limit);
618
                $limitCondition = " LIMIT $start, $limit";
619
            }
620
            $sql .= $limitCondition;
621
            $result = Database::query($sql);
622
            $questions = array();
623
            if (Database::num_rows($result)) {
624
                if (!empty($extraFields)) {
625
                    $extraFieldValue = new ExtraFieldValue('question');
626
                }
627
                while ($question = Database::fetch_array($result, 'ASSOC')) {
628
                    /** @var Question $objQuestionTmp */
629
                    $objQuestionTmp = Question::read($question['iid']);
630
                    $category_labels = TestCategory::return_category_labels($objQuestionTmp->category_list, $category_list);
631
632
                    if (empty($category_labels)) {
633
                        $category_labels = "-";
634
                    }
635
636
                    // Question type
637
                    list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html();
638
639
                    $question_media = null;
640
                    if (!empty($objQuestionTmp->parent_id)) {
641
                        $objQuestionMedia = Question::read($objQuestionTmp->parent_id);
642
                        $question_media  = Question::getMediaLabel($objQuestionMedia->question);
643
                    }
644
645
                    $questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM).$question_media);
646
647
                    $question = array(
648
                        'id' => $question['iid'],
649
                        'question' => $objQuestionTmp->selectTitle(),
650
                        'type' => $questionType,
651
                        'category' => Display::tag('div', '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>'),
652
                        'score' => $objQuestionTmp->selectWeighting(),
653
                        'level' => $objQuestionTmp->level
654
                    );
655
                    if (!empty($extraFields)) {
656
                        foreach ($extraFields as $extraField) {
657
                            $value = $extraFieldValue->get_values_by_handler_and_field_id($question['id'], $extraField['id']);
658
                            $stringValue = null;
659
                            if ($value) {
660
                                $stringValue = $value['field_value'];
661
                            }
662
                            $question[$extraField['field_variable']] = $stringValue;
663
                        }
664
                    }
665
                    $questions[] = $question;
666
                }
667
            }
668
            return $questions;
669
        }
670
    }
671
672
    /**
673
     * Get question count per exercise from DB (any special treatment)
674
     * @return int
675
     */
676
    public function getQuestionCount()
677
    {
678
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
679
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
680
        $sql = "SELECT count(q.id) as count
681
                FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
682
                    ON (e.question_id = q.id)
683
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= ".Database::escape_string($this->id);
684
        $result = Database::query($sql);
685
686
        $count = 0;
687
        if (Database::num_rows($result)) {
688
            $row = Database::fetch_array($result);
689
            $count = $row['count'];
690
        }
691
692
        return $count;
693
    }
694
695
    /**
696
     * @return array
697
     */
698
    public function getQuestionOrderedListByName()
699
    {
700
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
701
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
702
703
        // Getting question list from the order (question list drag n drop interface ).
704
        $sql = "SELECT e.question_id
705
                FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q
706
                    ON (e.question_id= q.id)
707
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
708
                ORDER BY q.question";
709
        $result = Database::query($sql);
710
        $list = array();
711
        if (Database::num_rows($result)) {
712
            $list = Database::store_result($result, 'ASSOC');
713
        }
714
        return $list;
715
    }
716
717
    /**
718
     * Gets the question list ordered by the question_order setting (drag and drop)
719
     * @return array
720
     */
721
    private function getQuestionOrderedList()
722
    {
723
        $questionList = array();
724
725
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
726
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
727
728
        // Getting question_order to verify that the question
729
        // list is correct and all question_order's were set
730
        $sql = "SELECT DISTINCT e.question_order
731
                FROM $TBL_EXERCICE_QUESTION e
732
                INNER JOIN $TBL_QUESTIONS q
733
                    ON (e.question_id = q.id)
734
                WHERE
735
                  e.c_id = {$this->course_id} AND
736
                  e.exercice_id	= ".Database::escape_string($this->id);
737
738
        $result = Database::query($sql);
739
740
        $count_question_orders = Database::num_rows($result);
741
742
        // Getting question list from the order (question list drag n drop interface ).
743
        $sql = "SELECT DISTINCT e.question_id, e.question_order
744
                FROM $TBL_EXERCICE_QUESTION e
745
                INNER JOIN $TBL_QUESTIONS q
746
                    ON (e.question_id= q.id)
747
                WHERE
748
                    e.c_id = {$this->course_id} AND
749
                    e.exercice_id	= '".Database::escape_string($this->id)."'
750
                ORDER BY question_order";
751
752
        $result = Database::query($sql);
753
754
        // Fills the array with the question ID for this exercise
755
        // the key of the array is the question position
756
        $temp_question_list = array();
757
758
        $counter = 1;
759
        while ($new_object = Database::fetch_object($result)) {
760
            // Correct order.
761
            $questionList[$new_object->question_order] = $new_object->question_id;
762
            // Just in case we save the order in other array
763
            $temp_question_list[$counter] = $new_object->question_id;
764
            $counter++;
765
        }
766
767
        if (!empty($temp_question_list)) {
768
            /* If both array don't match it means that question_order was not correctly set
769
               for all questions using the default mysql order */
770
            if (count($temp_question_list) != $count_question_orders) {
771
                $questionList = $temp_question_list;
772
            }
773
        }
774
775
        return $questionList;
776
    }
777
778
    /**
779
     * Select N values from the questions per category array
780
     *
781
     * @param array $categoriesAddedInExercise
782
     * @param array $question_list
783
     * @param array $questions_by_category per category
784
     * @param bool $flatResult
785
     * @param bool $randomizeQuestions
786
     *
787
     * @return array
788
     */
789
    private function pickQuestionsPerCategory(
790
        $categoriesAddedInExercise,
791
        $question_list,
792
        & $questions_by_category,
793
        $flatResult = true,
794
        $randomizeQuestions = false
795
    ) {
796
        $addAll = true;
797
        $categoryCountArray = array();
798
799
        // Getting how many questions will be selected per category.
800
        if (!empty($categoriesAddedInExercise)) {
801
            $addAll = false;
802
            // Parsing question according the category rel exercise settings
803
            foreach ($categoriesAddedInExercise as $category_info) {
804
                $category_id = $category_info['category_id'];
805
                if (isset($questions_by_category[$category_id])) {
806
                    // How many question will be picked from this category.
807
                    $count = $category_info['count_questions'];
808
                    // -1 means all questions
809
                    if ($count == -1) {
810
                        $categoryCountArray[$category_id] = 999;
811
                    } else {
812
                        $categoryCountArray[$category_id] = $count;
813
                    }
814
                }
815
            }
816
        }
817
818
        if (!empty($questions_by_category)) {
819
            $temp_question_list = array();
820
821
            foreach ($questions_by_category as $category_id => & $categoryQuestionList) {
822
                if (isset($categoryCountArray) && !empty($categoryCountArray)) {
823
                    if (isset($categoryCountArray[$category_id])) {
824
                        $numberOfQuestions = $categoryCountArray[$category_id];
825
                    } else {
826
                        $numberOfQuestions = 0;
827
                    }
828
                }
829
830
                if ($addAll) {
831
                    $numberOfQuestions = 999;
832
                }
833
834
                if (!empty($numberOfQuestions)) {
835
                    $elements = TestCategory::getNElementsFromArray(
836
                        $categoryQuestionList,
837
                        $numberOfQuestions,
838
                        $randomizeQuestions
839
                    );
840
841
                    if (!empty($elements)) {
842
                        $temp_question_list[$category_id] = $elements;
843
                        $categoryQuestionList = $elements;
844
                    }
845
                }
846
            }
847
848
            if (!empty($temp_question_list)) {
849
                if ($flatResult) {
850
                    $temp_question_list = array_flatten($temp_question_list);
851
                }
852
                $question_list = $temp_question_list;
853
            }
854
        }
855
856
        return $question_list;
857
    }
858
859
    /**
860
     * Selecting question list depending in the exercise-category
861
     * relationship (category table in exercise settings)
862
     *
863
     * @param array $question_list
864
     * @param int $questionSelectionType
865
     * @return array
866
     */
867
    public function getQuestionListWithCategoryListFilteredByCategorySettings($question_list, $questionSelectionType)
868
    {
869
        $result = array(
870
            'question_list' => array(),
871
            'category_with_questions_list' => array()
872
        );
873
874
        // Order/random categories
875
        $cat = new TestCategory();
876
877
        // Setting category order.
878
879
        switch ($questionSelectionType) {
880
            case EX_Q_SELECTION_ORDERED: // 1
881
            case EX_Q_SELECTION_RANDOM:  // 2
882
                // This options are not allowed here.
883
                break;
884
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3
885
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
886
                    $this,
887
                    $this->course['real_id'],
888
                    'title ASC',
889
                    false,
890
                    true
891
                );
892
893
                $questions_by_category = TestCategory::getQuestionsByCat(
894
                    $this->id,
895
                    $question_list,
896
                    $categoriesAddedInExercise
897
                );
898
899
900
                $question_list = $this->pickQuestionsPerCategory(
901
                    $categoriesAddedInExercise,
902
                    $question_list,
903
                    $questions_by_category,
904
                    true,
905
                    false
906
                );
907
                break;
908
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4
909
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
910
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
911
                    $this,
912
                    $this->course['real_id'],
913
                    null,
914
                    true,
915
                    true
916
                );
917
                $questions_by_category = TestCategory::getQuestionsByCat(
918
                    $this->id,
919
                    $question_list,
920
                    $categoriesAddedInExercise
921
                );
922
                $question_list = $this->pickQuestionsPerCategory(
923
                    $categoriesAddedInExercise,
924
                    $question_list,
925
                    $questions_by_category,
926
                    true,
927
                    false
928
                );
929
            break;
930
            case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5
931
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
932
                    $this,
933
                    $this->course['real_id'],
934
                    'title DESC',
935
                    false,
936
                    true
937
                );
938
                $questions_by_category = TestCategory::getQuestionsByCat(
939
                    $this->id,
940
                    $question_list,
941
                    $categoriesAddedInExercise
942
                );
943
                $question_list = $this->pickQuestionsPerCategory(
944
                    $categoriesAddedInExercise,
945
                    $question_list,
946
                    $questions_by_category,
947
                    true,
948
                    true
949
                );
950
                break;
951
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6
952
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED:
953
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
954
                    $this,
955
                    $this->course['real_id'],
956
                    null,
957
                    true,
958
                    true
959
                );
960
                $questions_by_category = TestCategory::getQuestionsByCat(
961
                    $this->id,
962
                    $question_list,
963
                    $categoriesAddedInExercise
964
                );
965
                $question_list = $this->pickQuestionsPerCategory(
966
                    $categoriesAddedInExercise,
967
                    $question_list,
968
                    $questions_by_category,
969
                    true,
970
                    true
971
                );
972
                break;
973
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7
974
                break;
975
            case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8
976
                break;
977
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9
978
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
979
                    $this,
980
                    $this->course['real_id'],
981
                    'root ASC, lft ASC',
982
                    false,
983
                    true
984
                );
985
                $questions_by_category = TestCategory::getQuestionsByCat(
986
                    $this->id,
987
                    $question_list,
988
                    $categoriesAddedInExercise
989
                );
990
                $question_list = $this->pickQuestionsPerCategory(
991
                    $categoriesAddedInExercise,
992
                    $question_list,
993
                    $questions_by_category,
994
                    true,
995
                    false
996
                );
997
                break;
998
            case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10
999
                $categoriesAddedInExercise = $cat->getCategoryExerciseTree(
1000
                    $this,
1001
                    $this->course['real_id'],
1002
                    'root, lft ASC',
1003
                    false,
1004
                    true
1005
                );
1006
                $questions_by_category = TestCategory::getQuestionsByCat(
1007
                    $this->id,
1008
                    $question_list,
1009
                    $categoriesAddedInExercise
1010
                );
1011
                $question_list = $this->pickQuestionsPerCategory(
1012
                    $categoriesAddedInExercise,
1013
                    $question_list,
1014
                    $questions_by_category,
1015
                    true,
1016
                    true
1017
                );
1018
                break;
1019
        }
1020
1021
        $result['question_list'] = isset($question_list) ? $question_list : array();
1022
        $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : array();
1023
1024
        // Adding category info in the category list with question list:
1025
1026
        if (!empty($questions_by_category)) {
1027
1028
            /*$em = Database::getManager();
1029
            $repo = $em->getRepository('ChamiloCoreBundle:CQuizCategory');*/
1030
1031
            $newCategoryList = array();
1032
1033
            foreach ($questions_by_category as $categoryId => $questionList) {
1034
                $cat = new TestCategory($categoryId);
1035
                $cat = (array)$cat;
1036
                $cat['iid'] = $cat['id'];
1037
                //*$cat['name'] = $cat['name'];
1038
1039
                $categoryParentInfo = null;
1040
                // Parent is not set no loop here
1041
                if (!empty($cat['parent_id'])) {
1042
                    if (!isset($parentsLoaded[$cat['parent_id']])) {
1043
                        $categoryEntity = $em->find('ChamiloCoreBundle:CQuizCategory', $cat['parent_id']);
1044
                        $parentsLoaded[$cat['parent_id']] = $categoryEntity;
1045
                    } else {
1046
                        $categoryEntity = $parentsLoaded[$cat['parent_id']];
1047
                    }
1048
                    $path = $repo->getPath($categoryEntity);
1049
                    $index = 0;
1050
                    if ($this->categoryMinusOne) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1051
                        //$index = 1;
1052
                    }
1053
                    /** @var \Chamilo\Entity\CQuizCategory $categoryParent*/
1054
1055
                    foreach ($path as $categoryParent) {
1056
                        $visibility = $categoryParent->getVisibility();
1057
1058
                        if ($visibility == 0) {
1059
                            $categoryParentId = $categoryId;
1060
                            $categoryTitle = $cat['title'];
1061
                            if (count($path) > 1) {
1062
                                continue;
1063
                            }
1064
                        } else {
1065
                            $categoryParentId = $categoryParent->getIid();
1066
                            $categoryTitle = $categoryParent->getTitle();
1067
                        }
1068
1069
                        $categoryParentInfo['id'] = $categoryParentId;
1070
                        $categoryParentInfo['iid'] = $categoryParentId;
1071
                        $categoryParentInfo['parent_path'] = null;
1072
                        $categoryParentInfo['title'] = $categoryTitle;
1073
                        $categoryParentInfo['name'] = $categoryTitle;
1074
                        $categoryParentInfo['parent_id'] = null;
1075
                        break;
1076
                    }
1077
                }
1078
                $cat['parent_info'] = $categoryParentInfo;
1079
                $newCategoryList[$categoryId] = array(
1080
                    'category' => $cat,
1081
                    'question_list' => $questionList
1082
                );
1083
            }
1084
1085
            $result['category_with_questions_list'] = $newCategoryList;
1086
        }
1087
        return $result;
1088
    }
1089
1090
    /**
1091
     * returns the array with the question ID list
1092
     *
1093
     * @author Olivier Brouckaert
1094
     * @return array - question ID list
1095
     */
1096
    public function selectQuestionList($from_db = false)
1097
    {
1098
        if ($this->specialCategoryOrders == false) {
1099
            if ($from_db && !empty($this->id)) {
1100
                $TBL_EXERCISE_QUESTION  = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1101
                $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1102
1103
                $sql = "SELECT DISTINCT e.question_order
1104
                        FROM $TBL_EXERCISE_QUESTION e
1105
                        INNER JOIN $TBL_QUESTIONS  q
1106
                        ON (e.question_id = q.id AND e.c_id = ".$this->course_id." AND q.c_id = ".$this->course_id.")
1107
					    WHERE e.exercice_id	= ".intval($this->id);
1108
                $result = Database::query($sql);
1109
1110
                $count_question_orders = Database::num_rows($result);
1111
1112
                $sql = "SELECT DISTINCT e.question_id, e.question_order
1113
                        FROM $TBL_EXERCISE_QUESTION e
1114
                        INNER JOIN $TBL_QUESTIONS  q
1115
                        ON (e.question_id= q.id AND e.c_id = ".$this->course_id." AND q.c_id = ".$this->course_id.")
1116
					    WHERE e.exercice_id	= ".intval($this->id)."
1117
					    ORDER BY question_order";
1118
                $result = Database::query($sql);
1119
1120
                // fills the array with the question ID for this exercise
1121
                // the key of the array is the question position
1122
                $temp_question_list = array();
1123
                $counter = 1;
1124
                $question_list = array();
1125
1126
                while ($new_object = Database::fetch_object($result)) {
1127
                    $question_list[$new_object->question_order]=  $new_object->question_id;
1128
                    $temp_question_list[$counter] = $new_object->question_id;
1129
                    $counter++;
1130
                }
1131
1132
                if (!empty($temp_question_list)) {
1133
                    if (count($temp_question_list) != $count_question_orders) {
1134
                        $question_list = $temp_question_list;
1135
                    }
1136
                }
1137
1138
                return $question_list;
1139
            }
1140
1141
            return $this->questionList;
1142
        } else {
1143
1144
            if ($from_db && !empty($this->id)) {
1145
1146
                $nbQuestions = $this->getQuestionCount();
1147
                $questionSelectionType = $this->getQuestionSelectionType();
1148
1149
                switch ($questionSelectionType) {
1150
                    case EX_Q_SELECTION_ORDERED:
1151
                        $questionList = $this->getQuestionOrderedList();
1152
                        break;
1153
                    case EX_Q_SELECTION_RANDOM:
1154
                        // Not a random exercise, or if there are not at least 2 questions
1155
                        if ($this->random == 0 || $nbQuestions < 2) {
1156
                            $questionList = $this->getQuestionOrderedList();
1157
                        } else {
1158
                            $questionList = $this->selectRandomList();
1159
                        }
1160
                        break;
1161
                    default:
1162
                        $questionList = $this->getQuestionOrderedList();
1163
                        $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings(
1164
                            $questionList,
1165
                            $questionSelectionType
1166
                        );
1167
                        $this->categoryWithQuestionList = $result['category_with_questions_list'];
1168
                        $questionList = $result['question_list'];
1169
                        break;
1170
                }
1171
1172
                return $questionList;
1173
            }
1174
            return $this->questionList;
1175
        }
1176
    }
1177
1178
    /**
1179
     * returns the number of questions in this exercise
1180
     *
1181
     * @author Olivier Brouckaert
1182
     * @return integer - number of questions
1183
     */
1184
    public function selectNbrQuestions()
1185
    {
1186
        return sizeof($this->questionList);
1187
    }
1188
1189
    /**
1190
     * @return int
1191
     */
1192
    public function selectPropagateNeg()
1193
    {
1194
        return $this->propagate_neg;
1195
    }
1196
1197
    /**
1198
     * Selects questions randomly in the question list
1199
     *
1200
     * @author Olivier Brouckaert
1201
     * @author Hubert Borderiou 15 nov 2011
1202
     * @return array - if the exercise is not set to take questions randomly, returns the question list
1203
     *					 without randomizing, otherwise, returns the list with questions selected randomly
1204
     */
1205
    public function selectRandomList()
1206
    {
1207
        /*$nbQuestions	= $this->selectNbrQuestions();
1208
        $temp_list		= $this->questionList;
1209
1210
        //Not a random exercise, or if there are not at least 2 questions
1211
        if($this->random == 0 || $nbQuestions < 2) {
1212
            return $this->questionList;
1213
        }
1214
        if ($nbQuestions != 0) {
1215
            shuffle($temp_list);
1216
            $my_random_list = array_combine(range(1,$nbQuestions),$temp_list);
1217
            $my_question_list = array();
1218
            // $this->random == -1 if random with all questions
1219
            if ($this->random > 0) {
1220
                $i = 0;
1221
                foreach ($my_random_list as $item) {
1222
                    if ($i < $this->random) {
1223
                        $my_question_list[$i] = $item;
1224
                    } else {
1225
                        break;
1226
                    }
1227
                    $i++;
1228
                }
1229
            } else {
1230
                $my_question_list = $my_random_list;
1231
            }
1232
            return $my_question_list;
1233
        }*/
1234
1235
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1236
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1237
1238
        $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
1239
1240
        $randomLimit = "LIMIT $random";
1241
        // Random all questions so no limit
1242
        if ($random == -1) {
1243
            $randomLimit = null;
1244
        }
1245
1246
        // @todo improve this query
1247
        $sql = "SELECT e.question_id
1248
                FROM $TBL_EXERCISE_QUESTION e INNER JOIN $TBL_QUESTIONS q
1249
                    ON (e.question_id= q.iid)
1250
                WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
1251
                ORDER BY RAND()
1252
                $randomLimit ";
1253
        $result = Database::query($sql);
1254
        $questionList = array();
1255
        while ($row = Database::fetch_object($result)) {
1256
            $questionList[] = $row->question_id;
1257
        }
1258
        return $questionList;
1259
    }
1260
1261
    /**
1262
     * returns 'true' if the question ID is in the question list
1263
     *
1264
     * @author Olivier Brouckaert
1265
     * @param integer $questionId - question ID
1266
     * @return boolean - true if in the list, otherwise false
1267
     */
1268
    public function isInList($questionId)
1269
    {
1270
        if (is_array($this->questionList))
1271
            return in_array($questionId,$this->questionList);
1272
        else
1273
            return false;
1274
    }
1275
1276
    /**
1277
     * changes the exercise title
1278
     *
1279
     * @author Olivier Brouckaert
1280
     * @param string $title - exercise title
1281
     */
1282
    public function updateTitle($title)
1283
    {
1284
        $this->exercise=$title;
1285
    }
1286
1287
    /**
1288
     * changes the exercise max attempts
1289
     *
1290
     * @param int $attempts - exercise max attempts
1291
     */
1292
    public function updateAttempts($attempts)
1293
    {
1294
        $this->attempts=$attempts;
1295
    }
1296
1297
    /**
1298
     * changes the exercise feedback type
1299
     *
1300
     * @param int $feedback_type
1301
     */
1302
    public function updateFeedbackType($feedback_type)
1303
    {
1304
        $this->feedback_type=$feedback_type;
1305
    }
1306
1307
    /**
1308
     * changes the exercise description
1309
     *
1310
     * @author Olivier Brouckaert
1311
     * @param string $description - exercise description
1312
     */
1313
    public function updateDescription($description)
1314
    {
1315
        $this->description=$description;
1316
    }
1317
1318
    /**
1319
     * changes the exercise expired_time
1320
     *
1321
     * @author Isaac flores
1322
     * @param int $expired_time The expired time of the quiz
1323
     */
1324
    public function updateExpiredTime($expired_time)
1325
    {
1326
        $this->expired_time = $expired_time;
1327
    }
1328
1329
    /**
1330
     * @param $value
1331
     */
1332
    public function updatePropagateNegative($value)
1333
    {
1334
        $this->propagate_neg = $value;
1335
    }
1336
1337
    /**
1338
     * @param $value
1339
     */
1340
    public function updateReviewAnswers($value)
1341
    {
1342
        $this->review_answers = isset($value) && $value ? true : false;
1343
    }
1344
1345
    /**
1346
     * @param $value
1347
     */
1348
    public function updatePassPercentage($value)
1349
    {
1350
        $this->pass_percentage = $value;
1351
    }
1352
1353
1354
    /**
1355
     * @param string $text
1356
     */
1357
    public function updateEmailNotificationTemplate($text)
1358
    {
1359
        $this->emailNotificationTemplate = $text;
1360
    }
1361
1362
    /**
1363
     * @param string $text
1364
     */
1365
    public function updateEmailNotificationTemplateToUser($text)
1366
    {
1367
        $this->emailNotificationTemplateToUser = $text;
1368
    }
1369
1370
    /**
1371
     * @param string $value
1372
     */
1373
    public function setNotifyUserByEmail($value)
1374
    {
1375
        $this->notifyUserByEmail = $value;
1376
    }
1377
1378
1379
    /**
1380
     * @param int $value
1381
     */
1382
    public function updateEndButton($value)
1383
    {
1384
        $this->endButton = intval($value);
1385
    }
1386
1387
    /**
1388
     * @param string $value
1389
     */
1390
    public function setOnSuccessMessage($value)
1391
    {
1392
        $this->onSuccessMessage = $value;
1393
    }
1394
1395
    /**
1396
     * @param string $value
1397
     */
1398
    public function setOnFailedMessage($value)
1399
    {
1400
        $this->onFailedMessage = $value;
1401
    }
1402
1403
    /**
1404
     * @param $value
1405
     */
1406
    public function setModelType($value)
1407
    {
1408
        $this->modelType = intval($value);
1409
    }
1410
1411
    /**
1412
     * @param intval $value
1413
     */
1414
    public function setQuestionSelectionType($value)
1415
    {
1416
        $this->questionSelectionType = intval($value);
1417
    }
1418
1419
    /**
1420
     * @return int
1421
     */
1422
    public function getQuestionSelectionType()
1423
    {
1424
        return $this->questionSelectionType;
1425
    }
1426
1427
    /**
1428
     * @param array $categories
1429
     */
1430
    public function updateCategories($categories)
1431
    {
1432
        if (!empty($categories)) {
1433
            $categories = array_map('intval', $categories);
1434
            $this->categories = $categories;
1435
        }
1436
    }
1437
1438
    /**
1439
     * changes the exercise sound file
1440
     *
1441
     * @author Olivier Brouckaert
1442
     * @param string $sound - exercise sound file
1443
     * @param string $delete - ask to delete the file
1444
     */
1445
    public function updateSound($sound,$delete)
1446
    {
1447
        global $audioPath, $documentPath;
1448
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1449
1450
        if ($sound['size'] && (strstr($sound['type'],'audio') || strstr($sound['type'],'video'))) {
1451
            $this->sound=$sound['name'];
1452
1453
            if (@move_uploaded_file($sound['tmp_name'],$audioPath.'/'.$this->sound)) {
1454
                $query = "SELECT 1 FROM $TBL_DOCUMENT
1455
                        WHERE c_id = ".$this->course_id." AND path='".str_replace($documentPath,'',$audioPath).'/'.$this->sound."'";
1456
                $result=Database::query($query);
1457
1458
                if (!Database::num_rows($result)) {
1459
                    $id = add_document(
1460
                        $this->course,
1461
                        str_replace($documentPath,'',$audioPath).'/'.$this->sound,
1462
                        'file',
1463
                        $sound['size'],
1464
                        $sound['name']
1465
                    );
1466
                    api_item_property_update(
1467
                        $this->course,
1468
                        TOOL_DOCUMENT,
1469
                        $id,
1470
                        'DocumentAdded',
1471
                        api_get_user_id()
1472
                    );
1473
                    item_property_update_on_folder(
1474
                        $this->course,
1475
                        str_replace($documentPath, '', $audioPath),
1476
                        api_get_user_id()
1477
                    );
1478
                }
1479
            }
1480
        } elseif($delete && is_file($audioPath.'/'.$this->sound)) {
1481
            $this->sound='';
1482
        }
1483
    }
1484
1485
    /**
1486
     * changes the exercise type
1487
     *
1488
     * @author Olivier Brouckaert
1489
     * @param integer $type - exercise type
1490
     */
1491
    public function updateType($type)
1492
    {
1493
        $this->type=$type;
1494
    }
1495
1496
    /**
1497
     * sets to 0 if questions are not selected randomly
1498
     * if questions are selected randomly, sets the draws
1499
     *
1500
     * @author Olivier Brouckaert
1501
     * @param integer $random - 0 if not random, otherwise the draws
1502
     */
1503
    public function setRandom($random)
1504
    {
1505
        /*if ($random == 'all') {
1506
            $random = $this->selectNbrQuestions();
1507
        }*/
1508
        $this->random = $random;
1509
    }
1510
1511
    /**
1512
     * sets to 0 if answers are not selected randomly
1513
     * if answers are selected randomly
1514
     * @author Juan Carlos Rana
1515
     * @param integer $random_answers - random answers
1516
     */
1517
    public function updateRandomAnswers($random_answers)
1518
    {
1519
        $this->random_answers = $random_answers;
1520
    }
1521
1522
    /**
1523
     * enables the exercise
1524
     *
1525
     * @author Olivier Brouckaert
1526
     */
1527
    public function enable()
1528
    {
1529
        $this->active=1;
1530
    }
1531
1532
    /**
1533
     * disables the exercise
1534
     *
1535
     * @author Olivier Brouckaert
1536
     */
1537
    public function disable()
1538
    {
1539
        $this->active=0;
1540
    }
1541
1542
    /**
1543
     * Set disable results
1544
     */
1545
    public function disable_results()
1546
    {
1547
        $this->results_disabled = true;
1548
    }
1549
1550
    /**
1551
     * Enable results
1552
     */
1553
    public function enable_results()
1554
    {
1555
        $this->results_disabled = false;
1556
    }
1557
1558
    /**
1559
     * @param int $results_disabled
1560
     */
1561
    public function updateResultsDisabled($results_disabled)
1562
    {
1563
        $this->results_disabled = intval($results_disabled);
1564
    }
1565
1566
    /**
1567
     * updates the exercise in the data base
1568
     *
1569
     * @author Olivier Brouckaert
1570
     */
1571
    public function save($type_e = '')
1572
    {
1573
        $_course = $this->course;
1574
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1575
1576
        $id = $this->id;
1577
        $exercise = $this->exercise;
1578
        $description = $this->description;
1579
        $sound = $this->sound;
1580
        $type = $this->type;
1581
        $attempts = isset($this->attempts) ? $this->attempts : 0;
1582
        $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0;
1583
        $random = $this->random;
1584
        $random_answers = $this->random_answers;
1585
        $active = $this->active;
1586
        $propagate_neg = $this->propagate_neg;
1587
        $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0;
1588
        $randomByCat = intval($this->randomByCat);
1589
        $text_when_finished = $this->text_when_finished;
1590
        $display_category_name = intval($this->display_category_name);
1591
        $pass_percentage = intval($this->pass_percentage);
1592
        $session_id = $this->sessionId;
1593
1594
        //If direct we do not show results
1595
        if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
1596
            $results_disabled = 0;
1597
        } else {
1598
            $results_disabled = intval($this->results_disabled);
1599
        }
1600
1601
        $expired_time = intval($this->expired_time);
1602
1603
        // Exercise already exists
1604
        if ($id) {
1605
            // we prepare date in the database using the api_get_utc_datetime() function
1606
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
1607
                $start_time = $this->start_time;
1608
            } else {
1609
                $start_time = '0000-00-00 00:00:00';
1610
            }
1611
1612
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
1613
                $end_time = $this->end_time;
1614
            } else {
1615
                $end_time = '0000-00-00 00:00:00';
1616
            }
1617
1618
            $params = [
1619
                'title' => $exercise,
1620
                'description' => $description,
1621
            ];
1622
1623
            $paramsExtra = [];
1624
            if ($type_e != 'simple') {
1625
                $paramsExtra = [
1626
                    'sound' => $sound,
1627
                    'type' => $type,
1628
                    'random' => $random,
1629
                    'random_answers' => $random_answers,
1630
                    'active' => $active,
1631
                    'feedback_type' => $feedback_type,
1632
                    'start_time' => $start_time,
1633
                    'end_time' => $end_time,
1634
                    'max_attempt' => $attempts,
1635
                    'expired_time' => $expired_time,
1636
                    'propagate_neg' => $propagate_neg,
1637
                    'review_answers' => $review_answers,
1638
                    'random_by_category' => $randomByCat,
1639
                    'text_when_finished' => $text_when_finished,
1640
                    'display_category_name' => $display_category_name,
1641
                    'pass_percentage' => $pass_percentage,
1642
                    'results_disabled' => $results_disabled,
1643
                ];
1644
            }
1645
1646
            $params = array_merge($params, $paramsExtra);
1647
1648
            if ($this->specialCategoryOrders) {
1649
                $params['question_selection_type'] = intval($this->getQuestionSelectionType());
1650
            }
1651
1652
            Database::update(
1653
                $TBL_EXERCISES,
1654
                $params,
1655
                ['c_id = ? AND id = ?' => [$this->course_id, $id]]
1656
            );
1657
1658
            // update into the item_property table
1659
            api_item_property_update(
1660
                $_course,
1661
                TOOL_QUIZ,
1662
                $id,
1663
                'QuizUpdated',
1664
                api_get_user_id()
1665
            );
1666
1667
            if (api_get_setting('search_enabled')=='true') {
1668
                $this->search_engine_edit();
1669
            }
1670
        } else {
1671
            // Creates a new exercise
1672
1673
            // In this case of new exercise, we don't do the api_get_utc_datetime()
1674
            // for date because, bellow, we call function api_set_default_visibility()
1675
            // In this function, api_set_default_visibility,
1676
            // the Quiz is saved too, with an $id and api_get_utc_datetime() is done.
1677
            // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586)
1678
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
1679
                $start_time = $this->start_time;
1680
            } else {
1681
                $start_time = '0000-00-00 00:00:00';
1682
            }
1683
1684
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
1685
                $end_time = $this->end_time;
1686
            } else {
1687
                $end_time = '0000-00-00 00:00:00';
1688
            }
1689
1690
            $params = [
1691
                'c_id' => $this->course_id,
1692
                'start_time' => $start_time,
1693
                'end_time' => $end_time,
1694
                'title' => $exercise,
1695
                'description' => $description,
1696
                'sound' => $sound,
1697
                'type' => $type,
1698
                'random' => $random,
1699
                'random_answers' => $random_answers,
1700
                'active' => $active,
1701
                'results_disabled' => $results_disabled,
1702
                'max_attempt' => $attempts,
1703
                'feedback_type' => $feedback_type,
1704
                'expired_time' => $expired_time,
1705
                'session_id' => $session_id,
1706
                'review_answers' => $review_answers,
1707
                'random_by_category' => $randomByCat,
1708
                'text_when_finished' => $text_when_finished,
1709
                'display_category_name' => $display_category_name,
1710
                'pass_percentage' => $pass_percentage
1711
            ];
1712
1713
            $this->id = Database::insert($TBL_EXERCISES, $params);
1714
1715
            if ($this->id) {
1716
1717
                $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} ";
1718
                Database::query($sql);
1719
1720
                if ($this->specialCategoryOrders) {
1721
                    $sql = "UPDATE $TBL_EXERCISES
1722
                            SET question_selection_type= ".intval($this->getQuestionSelectionType())."
1723
                            WHERE id = ".$this->id." AND c_id = ".$this->course_id;
1724
                    Database::query($sql);
1725
                }
1726
1727
                // insert into the item_property table
1728
                api_item_property_update(
1729
                    $this->course,
1730
                    TOOL_QUIZ,
1731
                    $this->id,
1732
                    'QuizAdded',
1733
                    api_get_user_id()
1734
                );
1735
1736
                // This function save the quiz again, carefull about start_time
1737
                // and end_time if you remove this line (see above)
1738
                api_set_default_visibility(
1739
                    $this->id,
1740
                    TOOL_QUIZ,
1741
                    null,
1742
                    $this->course
1743
                );
1744
1745
                if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1746
                    $this->search_engine_save();
1747
                }
1748
            }
1749
        }
1750
1751
        $this->save_categories_in_exercise($this->categories);
1752
1753
        // Updates the question position
1754
        $this->update_question_positions();
1755
    }
1756
1757
    /**
1758
     * Updates question position
1759
     */
1760
    public function update_question_positions()
1761
    {
1762
        $quiz_question_table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1763
        //Fixes #3483 when updating order
1764
        $question_list = $this->selectQuestionList(true);
1765
        if (!empty($question_list)) {
1766
            foreach ($question_list as $position => $questionId) {
1767
                $sql = "UPDATE $quiz_question_table SET
1768
                        question_order ='".intval($position)."'
1769
                        WHERE
1770
                            c_id = ".$this->course_id." AND
1771
                            question_id = ".intval($questionId)." AND
1772
                            exercice_id=".intval($this->id);
1773
                Database::query($sql);
1774
            }
1775
        }
1776
    }
1777
1778
    /**
1779
     * Adds a question into the question list
1780
     *
1781
     * @author Olivier Brouckaert
1782
     * @param integer $questionId - question ID
1783
     * @return boolean - true if the question has been added, otherwise false
1784
     */
1785
    public function addToList($questionId)
1786
    {
1787
        // checks if the question ID is not in the list
1788
        if (!$this->isInList($questionId)) {
1789
            // selects the max position
1790
            if (!$this->selectNbrQuestions()) {
1791
                $pos = 1;
1792
            } else {
1793
                if (is_array($this->questionList)) {
1794
                    $pos = max(array_keys($this->questionList)) + 1;
1795
                }
1796
            }
1797
            $this->questionList[$pos] = $questionId;
1798
1799
            return true;
1800
        }
1801
1802
        return false;
1803
    }
1804
1805
    /**
1806
     * removes a question from the question list
1807
     *
1808
     * @author Olivier Brouckaert
1809
     * @param integer $questionId - question ID
1810
     * @return boolean - true if the question has been removed, otherwise false
1811
     */
1812
    public function removeFromList($questionId)
1813
    {
1814
        // searches the position of the question ID in the list
1815
        $pos = array_search($questionId,$this->questionList);
1816
1817
        // question not found
1818
        if ($pos === false) {
1819
            return false;
1820
        } else {
1821
            // dont reduce the number of random question if we use random by category option, or if
1822
            // random all questions
1823
            if ($this->isRandom() && $this->isRandomByCat() == 0) {
1824
                if (count($this->questionList) >= $this->random && $this->random > 0) {
1825
                    $this->random -= 1;
1826
                    $this->save();
1827
                }
1828
            }
1829
            // deletes the position from the array containing the wanted question ID
1830
            unset($this->questionList[$pos]);
1831
            return true;
1832
        }
1833
    }
1834
1835
    /**
1836
     * deletes the exercise from the database
1837
     * Notice : leaves the question in the data base
1838
     *
1839
     * @author Olivier Brouckaert
1840
     */
1841
    public function delete()
1842
    {
1843
        $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
1844
        $sql = "UPDATE $TBL_EXERCISES SET active='-1'
1845
                WHERE c_id = ".$this->course_id." AND id = ".intval($this->id)."";
1846
        Database::query($sql);
1847
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'QuizDeleted', api_get_user_id());
1848
        api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'delete', api_get_user_id());
1849
1850
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian') ) {
1851
            $this->search_engine_delete();
1852
        }
1853
    }
1854
1855
    /**
1856
     * Creates the form to create / edit an exercise
1857
     * @param FormValidator $form
1858
     */
1859
    public function createForm($form, $type='full')
1860
    {
1861
        if (empty($type)) {
1862
            $type = 'full';
1863
        }
1864
1865
        // form title
1866
        if (!empty($_GET['exerciseId'])) {
1867
            $form_title = get_lang('ModifyExercise');
1868
        } else {
1869
            $form_title = get_lang('NewEx');
1870
        }
1871
1872
        $form->addElement('header', $form_title);
1873
1874
        // Title.
1875
        $form->addElement(
1876
            'text',
1877
            'exerciseTitle',
1878
            get_lang('ExerciseName'),
1879
            array('id' => 'exercise_title')
1880
        );
1881
1882
        $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters'));
1883
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1884
1885
        $editor_config = array(
1886
            'ToolbarSet' => 'TestQuestionDescription',
1887
            'Width' => '100%',
1888
            'Height' => '150',
1889
        );
1890
        if (is_array($type)){
1891
            $editor_config = array_merge($editor_config, $type);
1892
        }
1893
1894
        $form->addHtmlEditor(
1895
            'exerciseDescription',
1896
            get_lang('ExerciseDescription'),
1897
            false,
1898
            false,
1899
            $editor_config
1900
        );
1901
1902
        if ($type == 'full') {
1903
            //Can't modify a DirectFeedback question
1904
            if ($this->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_DIRECT) {
1905
                // feedback type
1906
                $radios_feedback = array();
1907
                $radios_feedback[] = $form->createElement(
1908
                    'radio',
1909
                    'exerciseFeedbackType',
1910
                    null,
1911
                    get_lang('ExerciseAtTheEndOfTheTest'),
1912
                    '0',
1913
                    array(
1914
                        'id' => 'exerciseType_0',
1915
                        'onclick' => 'check_feedback()',
1916
                    )
1917
                );
1918
1919
                if (api_get_setting('enable_quiz_scenario') == 'true') {
1920
                    //Can't convert a question from one feedback to another if there is more than 1 question already added
1921
                    if ($this->selectNbrQuestions() == 0) {
1922
                        $radios_feedback[] = $form->createElement(
1923
                            'radio',
1924
                            'exerciseFeedbackType',
1925
                            null,
1926
                            get_lang('DirectFeedback'),
1927
                            '1',
1928
                            array(
1929
                                'id' => 'exerciseType_1',
1930
                                'onclick' => 'check_direct_feedback()',
1931
                            )
1932
                        );
1933
                    }
1934
                }
1935
1936
                $radios_feedback[] = $form->createElement(
1937
                    'radio',
1938
                    'exerciseFeedbackType',
1939
                    null,
1940
                    get_lang('NoFeedback'),
1941
                    '2',
1942
                    array('id' => 'exerciseType_2')
1943
                );
1944
                $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')), '');
1945
1946
                // Type of results display on the final page
1947
                $radios_results_disabled = array();
1948
                $radios_results_disabled[] = $form->createElement(
1949
                    'radio',
1950
                    'results_disabled',
1951
                    null,
1952
                    get_lang('ShowScoreAndRightAnswer'),
1953
                    '0',
1954
                    array('id' => 'result_disabled_0')
1955
                );
1956
                $radios_results_disabled[] = $form->createElement(
1957
                    'radio',
1958
                    'results_disabled',
1959
                    null,
1960
                    get_lang('DoNotShowScoreNorRightAnswer'),
1961
                    '1',
1962
                    array('id' => 'result_disabled_1', 'onclick' => 'check_results_disabled()')
1963
                );
1964
                $radios_results_disabled[] = $form->createElement(
1965
                    'radio',
1966
                    'results_disabled',
1967
                    null,
1968
                    get_lang('OnlyShowScore'),
1969
                    '2',
1970
                    array('id' => 'result_disabled_2')
1971
                );
1972
                //$radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ExamModeWithFinalScoreShowOnlyFinalScoreWithCategoriesIfAvailable'),  '3', array('id'=>'result_disabled_3','onclick' => 'check_results_disabled()'));
1973
1974
                $radios_results_disabled[] = $form->createElement(
1975
                    'radio',
1976
                    'results_disabled',
1977
                    null,
1978
                    get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'),
1979
                    '4',
1980
                    array('id' => 'result_disabled_4')
1981
                );
1982
1983
                $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'), '');
1984
1985
                // Type of questions disposition on page
1986
                $radios = array();
1987
1988
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
1989
                $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
1990
1991
                $form->addGroup($radios, null, get_lang('QuestionsPerPage'), '');
1992
1993
            } else {
1994
                // if is Directfeedback but has not questions we can allow to modify the question type
1995
                if ($this->selectNbrQuestions() == 0) {
1996
1997
                    // feedback type
1998
                    $radios_feedback = array();
1999
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('ExerciseAtTheEndOfTheTest'),'0',array('id' =>'exerciseType_0', 'onclick' => 'check_feedback()'));
2000
2001
                    if (api_get_setting('enable_quiz_scenario') == 'true') {
2002
                        $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('DirectFeedback'), '1', array('id' =>'exerciseType_1' , 'onclick' => 'check_direct_feedback()'));
2003
                    }
2004
                    $radios_feedback[] = $form->createElement('radio', 'exerciseFeedbackType', null, get_lang('NoFeedback'),'2',array('id' =>'exerciseType_2'));
2005
                    $form->addGroup($radios_feedback, null, array(get_lang('FeedbackType'),get_lang('FeedbackDisplayOptions')));
2006
2007
                    //$form->addElement('select', 'exerciseFeedbackType',get_lang('FeedbackType'),$feedback_option,'onchange="javascript:feedbackselection()"');
2008
                    $radios_results_disabled = array();
2009
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
2010
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
2011
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
2012
                    $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
2013
2014
                    // Type of questions disposition on page
2015
                    $radios = array();
2016
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1');
2017
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2');
2018
                    $form->addGroup($radios, null, get_lang('ExerciseType'));
2019
2020
                } else {
2021
                    //Show options freeze
2022
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('ShowScoreAndRightAnswer'), '0', array('id'=>'result_disabled_0'));
2023
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('DoNotShowScoreNorRightAnswer'),  '1',array('id'=>'result_disabled_1','onclick' => 'check_results_disabled()'));
2024
                    $radios_results_disabled[] = $form->createElement('radio', 'results_disabled', null, get_lang('OnlyShowScore'),  '2',array('id'=>'result_disabled_2','onclick' => 'check_results_disabled()'));
2025
                    $result_disable_group = $form->addGroup($radios_results_disabled, null, get_lang('ShowResultsToStudents'),'');
2026
                    $result_disable_group->freeze();
2027
2028
                    //we force the options to the DirectFeedback exercisetype
2029
                    $form->addElement('hidden', 'exerciseFeedbackType', EXERCISE_FEEDBACK_TYPE_DIRECT);
2030
                    $form->addElement('hidden', 'exerciseType', ONE_PER_PAGE);
2031
2032
                    // Type of questions disposition on page
2033
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'),    '1', array('onclick' => 'check_per_page_all()', 'id'=>'option_page_all'));
2034
                    $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SequentialExercise'),'2', array('onclick' => 'check_per_page_one()', 'id'=>'option_page_one'));
2035
2036
                    $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage'), '');
2037
                    $type_group->freeze();
2038
                }
2039
            }
2040
2041
            if ($this->specialCategoryOrders) {
2042
                $option = array(
2043
                    EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'),
2044
                    //  defined by user
2045
                    EX_Q_SELECTION_RANDOM => get_lang('Random'),
2046
                    // 1-10, All
2047
                    'per_categories' => '--------'.get_lang(
2048
                            'UsingCategories'
2049
                        ).'----------',
2050
2051
                    // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0}
2052
                    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang(
2053
                        'OrderedCategoriesAlphabeticallyWithQuestionsOrdered'
2054
                    ),
2055
                    // A 123 B 456 C 78 (0, 1, all)
2056
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang(
2057
                        'RandomCategoriesWithQuestionsOrdered'
2058
                    ),
2059
                    // C 78 B 456 A 123
2060
2061
                    EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang(
2062
                        'OrderedCategoriesAlphabeticallyWithRandomQuestions'
2063
                    ),
2064
                    // A 321 B 654 C 87
2065
                    EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang(
2066
                        'RandomCategoriesWithRandomQuestions'
2067
                    ),
2068
                    //C 87 B 654 A 321
2069
2070
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'),
2071
                    /*    B 456 C 78 A 123
2072
                            456 78 123
2073
                            123 456 78
2074
                    */
2075
                    //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'),
2076
                    /*
2077
                        A 123 B 456 C 78
2078
                        B 456 C 78 A 123
2079
                        B 654 C 87 A 321
2080
                        654 87 321
2081
                        165 842 73
2082
                    */
2083
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'),
2084
                    //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'),
2085
                );
2086
2087
                $form->addElement(
2088
                    'select',
2089
                    'question_selection_type',
2090
                    array(get_lang('QuestionSelection')),
2091
                    $option,
2092
                    array(
2093
                        'id' => 'questionSelection',
2094
                        'onclick' => 'checkQuestionSelection()'
2095
                    )
2096
                );
2097
2098
                $displayMatrix = 'none';
2099
                $displayRandom = 'none';
2100
                $selectionType = $this->getQuestionSelectionType();
2101
                switch ($selectionType) {
2102
                    case EX_Q_SELECTION_RANDOM:
2103
                        $displayRandom = 'block';
2104
                        break;
2105
                    case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED:
2106
                        $displayMatrix = 'block';
2107
                        break;
2108
                }
2109
2110
                $form->addElement(
2111
                    'html',
2112
                    '<div id="hidden_random" style="display:'.$displayRandom.'">'
2113
                );
2114
                // Number of random question.
2115
                $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10;
2116
                $option = range(0, $max);
2117
                $option[0] = get_lang('No');
2118
                $option[-1] = get_lang('AllQuestionsShort');
2119
                $form->addElement(
2120
                    'select',
2121
                    'randomQuestions',
2122
                    array(
2123
                        get_lang('RandomQuestions'),
2124
                        get_lang('RandomQuestionsHelp')
2125
                    ),
2126
                    $option,
2127
                    array('id' => 'randomQuestions')
2128
                );
2129
                $form->addElement('html', '</div>');
2130
2131
                $form->addElement(
2132
                    'html',
2133
                    '<div id="hidden_matrix" style="display:'.$displayMatrix.'">'
2134
                );
2135
2136
                // Category selection.
2137
                $cat = new TestCategory();
2138
                $cat_form = $cat->returnCategoryForm($this);
2139
                $form->addElement('label', null, $cat_form);
2140
                $form->addElement('html', '</div>');
2141
2142
                // Category name.
2143
                $radio_display_cat_name = array(
2144
                    $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'),
2145
                    $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0')
2146
                );
2147
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'), '');
2148
2149
                // Random answers.
2150
                $radios_random_answers = array(
2151
                    $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'),
2152
                    $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0')
2153
                );
2154
                $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'), '');
2155
2156
                // Hide question title.
2157
                $group = array(
2158
                    $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'),
2159
                    $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0')
2160
                );
2161
                $form->addGroup($group, null, get_lang('HideQuestionTitle'), '');
2162
            } else {
2163
2164
                // number of random question
2165
2166
                $max = ($this->id > 0) ? $this->selectNbrQuestions() : 10 ;
2167
                $option = range(0, $max);
2168
                $option[0] = get_lang('No');
2169
                $option[-1] = get_lang('AllQuestionsShort');
2170
                $form->addElement('select', 'randomQuestions',array(get_lang('RandomQuestions'), get_lang('RandomQuestionsHelp')), $option, array('id'=>'randomQuestions'));
2171
2172
                // Random answers
2173
                $radios_random_answers = array();
2174
                $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'),'1');
2175
                $radios_random_answers[] = $form->createElement('radio', 'randomAnswers', null, get_lang('No'),'0');
2176
                $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers'), '');
2177
2178
                // Random by category
2179
                $form->addElement('html','<div class="clear">&nbsp;</div>');
2180
                $radiocat = array();
2181
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesShuffled'),'1');
2182
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('YesWithCategoriesSorted'),'2');
2183
                $radiocat[] = $form->createElement('radio', 'randomByCat', null, get_lang('No'),'0');
2184
                $radioCatGroup = $form->addGroup($radiocat, null, get_lang('RandomQuestionByCategory'), '');
2185
                $form->addElement('html','<div class="clear">&nbsp;</div>');
2186
2187
                // add the radio display the category name for student
2188
                $radio_display_cat_name = array();
2189
                $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1');
2190
                $radio_display_cat_name[] = $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0');
2191
                $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName'), '');
2192
            }
2193
            // Attempts
2194
            $attempt_option = range(0, 10);
2195
            $attempt_option[0] = get_lang('Infinite');
2196
2197
            $form->addElement(
2198
                'select',
2199
                'exerciseAttempts',
2200
                get_lang('ExerciseAttempts'),
2201
                $attempt_option,
2202
                ['id' => 'exerciseAttempts']
2203
            );
2204
2205
            // Exercise time limit
2206
            $form->addElement('checkbox', 'activate_start_date_check',null, get_lang('EnableStartTime'), array('onclick' => 'activate_start_date()'));
2207
2208
            $var = Exercise::selectTimeLimit();
2209
2210
            if (($this->start_time != '0000-00-00 00:00:00'))
2211
                $form->addElement('html','<div id="start_date_div" style="display:block;">');
2212
            else
2213
                $form->addElement('html','<div id="start_date_div" style="display:none;">');
2214
2215
            $form->addElement('date_time_picker', 'start_time');
2216
2217
            $form->addElement('html','</div>');
2218
2219
            $form->addElement('checkbox', 'activate_end_date_check', null , get_lang('EnableEndTime'), array('onclick' => 'activate_end_date()'));
2220
2221
            if (($this->end_time != '0000-00-00 00:00:00'))
2222
                $form->addElement('html','<div id="end_date_div" style="display:block;">');
2223
            else
2224
                $form->addElement('html','<div id="end_date_div" style="display:none;">');
2225
2226
            $form->addElement('date_time_picker', 'end_time');
2227
            $form->addElement('html','</div>');
2228
2229
            //$check_option=$this->selectType();
2230
            $diplay = 'block';
2231
            $form->addElement('checkbox', 'propagate_neg', null, get_lang('PropagateNegativeResults'));
2232
            $form->addElement('html','<div class="clear">&nbsp;</div>');
2233
            $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers'));
2234
2235
            $form->addElement('html','<div id="divtimecontrol"  style="display:'.$diplay.';">');
2236
2237
            //Timer control
2238
            //$time_hours_option = range(0,12);
2239
            //$time_minutes_option = range(0,59);
2240
            $form->addElement(
2241
                'checkbox',
2242
                'enabletimercontrol',
2243
                null,
2244
                get_lang('EnableTimerControl'),
2245
                array(
2246
                    'onclick' => 'option_time_expired()',
2247
                    'id' => 'enabletimercontrol',
2248
                    'onload' => 'check_load_time()',
2249
                )
2250
            );
2251
            $expired_date = (int)$this->selectExpiredTime();
2252
2253
            if (($expired_date!='0')) {
2254
                $form->addElement('html','<div id="timercontrol" style="display:block;">');
2255
            } else {
2256
                $form->addElement('html','<div id="timercontrol" style="display:none;">');
2257
            }
2258
            $form->addText(
2259
                'enabletimercontroltotalminutes',
2260
                get_lang('ExerciseTotalDurationInMinutes'),
2261
                false,
2262
                [
2263
                    'id' => 'enabletimercontroltotalminutes',
2264
                    'cols-size' => [2, 2, 8]
2265
                ]
2266
            );
2267
            $form->addElement('html','</div>');
2268
2269
            $form->addElement(
2270
                'text',
2271
                'pass_percentage',
2272
                array(get_lang('PassPercentage'), null, '%'),
2273
                array('id' => 'pass_percentage')
2274
            );
2275
            $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric');
2276
            $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0);
2277
            $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100);
2278
2279
            // add the text_when_finished textbox
2280
            $form->addHtmlEditor(
2281
                'text_when_finished',
2282
                get_lang('TextWhenFinished'),
2283
                false,
2284
                false,
2285
                $editor_config
2286
            );
2287
2288
            $defaults = array();
2289
2290
            if (api_get_setting('search_enabled') === 'true') {
2291
                require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2292
2293
                $form->addElement ('checkbox', 'index_document','', get_lang('SearchFeatureDoIndexDocument'));
2294
                $form->addElement ('select_language', 'language', get_lang('SearchFeatureDocumentLanguage'));
2295
2296
                $specific_fields = get_specific_field_list();
2297
2298
                foreach ($specific_fields as $specific_field) {
2299
                    $form->addElement ('text', $specific_field['code'], $specific_field['name']);
2300
                    $filter = array(
2301
                        'c_id' => api_get_course_int_id(),
2302
                        'field_id' => $specific_field['id'],
2303
                        'ref_id' => $this->id,
2304
                        'tool_id' => "'" . TOOL_QUIZ . "'"
2305
                    );
2306
                    $values = get_specific_field_values_list($filter, array('value'));
2307
                    if ( !empty($values) ) {
2308
                        $arr_str_values = array();
2309
                        foreach ($values as $value) {
2310
                            $arr_str_values[] = $value['value'];
2311
                        }
2312
                        $defaults[$specific_field['code']] = implode(', ', $arr_str_values);
2313
                    }
2314
                }
2315
                //$form->addElement ('html','</div>');
2316
            }
2317
2318
            $form->addElement('html','</div>');  //End advanced setting
2319
            $form->addElement('html','</div>');
2320
        }
2321
2322
        // submit
2323
        if (isset($_GET['exerciseId'])) {
2324
            $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise');
2325
        } else {
2326
            $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise');
2327
        }
2328
2329
        $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required');
2330
2331
        if ($type == 'full') {
2332
            // rules
2333
            $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric');
2334
            $form->addRule('start_time', get_lang('InvalidDate'), 'datetime');
2335
            $form->addRule('end_time', get_lang('InvalidDate'), 'datetime');
2336
        }
2337
2338
        // defaults
2339
        if ($type=='full') {
2340
            if ($this->id > 0) {
2341
                if ($this->random > $this->selectNbrQuestions()) {
2342
                    $defaults['randomQuestions'] =  $this->selectNbrQuestions();
2343
                } else {
2344
                    $defaults['randomQuestions'] = $this->random;
2345
                }
2346
2347
                $defaults['randomAnswers'] = $this->selectRandomAnswers();
2348
                $defaults['exerciseType'] = $this->selectType();
2349
                $defaults['exerciseTitle'] = $this->get_formated_title();
2350
                $defaults['exerciseDescription'] = $this->selectDescription();
2351
                $defaults['exerciseAttempts'] = $this->selectAttempts();
2352
                $defaults['exerciseFeedbackType'] = $this->selectFeedbackType();
2353
                $defaults['results_disabled'] = $this->selectResultsDisabled();
2354
                $defaults['propagate_neg'] = $this->selectPropagateNeg();
2355
                $defaults['review_answers'] = $this->review_answers;
2356
                $defaults['randomByCat'] = $this->selectRandomByCat();
2357
                $defaults['text_when_finished'] = $this->selectTextWhenFinished();
2358
                $defaults['display_category_name'] = $this->selectDisplayCategoryName();
2359
                $defaults['pass_percentage'] = $this->selectPassPercentage();
2360
                $defaults['question_selection_type'] = $this->getQuestionSelectionType();
2361
2362
                if (($this->start_time != '0000-00-00 00:00:00')) {
2363
                    $defaults['activate_start_date_check'] = 1;
2364
                }
2365
                if ($this->end_time != '0000-00-00 00:00:00') {
2366
                    $defaults['activate_end_date_check'] = 1;
2367
                }
2368
2369
                $defaults['start_time'] = ($this->start_time!='0000-00-00 00:00:00') ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00');
2370
                $defaults['end_time'] = ($this->end_time!='0000-00-00 00:00:00') ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time()+84600);
2371
2372
                // Get expired time
2373
                if ($this->expired_time != '0') {
2374
                    $defaults['enabletimercontrol'] = 1;
2375
                    $defaults['enabletimercontroltotalminutes'] = $this->expired_time;
2376
                } else {
2377
                    $defaults['enabletimercontroltotalminutes'] = 0;
2378
                }
2379
            } else {
2380
                $defaults['exerciseType'] = 2;
2381
                $defaults['exerciseAttempts'] = 0;
2382
                $defaults['randomQuestions'] = 0;
2383
                $defaults['randomAnswers'] = 0;
2384
                $defaults['exerciseDescription'] = '';
2385
                $defaults['exerciseFeedbackType'] = 0;
2386
                $defaults['results_disabled'] = 0;
2387
                $defaults['randomByCat'] = 0;
2388
                $defaults['text_when_finished'] = "";
2389
                $defaults['start_time'] = date('Y-m-d 12:00:00');
2390
                $defaults['display_category_name'] = 1;
2391
                $defaults['end_time']   = date('Y-m-d 12:00:00', time()+84600);
2392
                $defaults['pass_percentage'] = '';
2393
                $defaults['end_button'] = $this->selectEndButton();
2394
                $defaults['question_selection_type'] = 1;
2395
                $defaults['hide_question_title'] = 0;
2396
                $defaults['on_success_message'] = null;
2397
                $defaults['on_failed_message'] = null;
2398
            }
2399
        } else {
2400
            $defaults['exerciseTitle'] = $this->selectTitle();
2401
            $defaults['exerciseDescription'] = $this->selectDescription();
2402
        }
2403
        if (api_get_setting('search_enabled') === 'true') {
2404
            $defaults['index_document'] = 'checked="checked"';
2405
        }
2406
        $form->setDefaults($defaults);
2407
2408
        // Freeze some elements.
2409
        if ($this->id != 0 && $this->edit_exercise_in_lp == false) {
2410
            $elementsToFreeze = array(
2411
                'randomQuestions',
2412
                //'randomByCat',
2413
                'exerciseAttempts',
2414
                'propagate_neg',
2415
                'enabletimercontrol',
2416
                'review_answers'
2417
            );
2418
2419
            foreach ($elementsToFreeze as $elementName) {
2420
                /** @var HTML_QuickForm_element $element */
2421
                $element = $form->getElement($elementName);
2422
                $element->freeze();
2423
            }
2424
2425
            //$radioCatGroup->freeze();
2426
        }
2427
    }
2428
2429
    /**
2430
     * function which process the creation of exercises
2431
     * @param FormValidator $form
2432
     * @param string
2433
     */
2434
    function processCreation($form, $type = '')
2435
    {
2436
        $this->updateTitle(Exercise::format_title_variable($form->getSubmitValue('exerciseTitle')));
2437
        $this->updateDescription($form->getSubmitValue('exerciseDescription'));
2438
        $this->updateAttempts($form->getSubmitValue('exerciseAttempts'));
2439
        $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType'));
2440
        $this->updateType($form->getSubmitValue('exerciseType'));
2441
        $this->setRandom($form->getSubmitValue('randomQuestions'));
2442
        $this->updateRandomAnswers($form->getSubmitValue('randomAnswers'));
2443
        $this->updateResultsDisabled($form->getSubmitValue('results_disabled'));
2444
        $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes'));
2445
        $this->updatePropagateNegative($form->getSubmitValue('propagate_neg'));
2446
        $this->updateRandomByCat($form->getSubmitValue('randomByCat'));
2447
        $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished'));
2448
        $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name'));
2449
        $this->updateReviewAnswers($form->getSubmitValue('review_answers'));
2450
        $this->updatePassPercentage($form->getSubmitValue('pass_percentage'));
2451
        $this->updateCategories($form->getSubmitValue('category'));
2452
        $this->updateEndButton($form->getSubmitValue('end_button'));
2453
        $this->setOnSuccessMessage($form->getSubmitValue('on_success_message'));
2454
        $this->setOnFailedMessage($form->getSubmitValue('on_failed_message'));
2455
        $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template'));
2456
        $this->updateEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user'));
2457
        $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email'));
2458
        $this->setModelType($form->getSubmitValue('model_type'));
2459
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2460
        $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title'));
2461
        $this->sessionId = api_get_session_id();
2462
        $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type'));
2463
        $this->setScoreTypeModel($form->getSubmitValue('score_type_model'));
2464
        $this->setGlobalCategoryId($form->getSubmitValue('global_category_id'));
2465
2466
        if ($form->getSubmitValue('activate_start_date_check') == 1) {
2467
            $start_time = $form->getSubmitValue('start_time');
2468
            $this->start_time = api_get_utc_datetime($start_time);
2469
        } else {
2470
            $this->start_time = '0000-00-00 00:00:00';
2471
        }
2472
2473
        if ($form->getSubmitValue('activate_end_date_check') == 1) {
2474
            $end_time = $form->getSubmitValue('end_time');
2475
            $this->end_time = api_get_utc_datetime($end_time);
2476
        } else {
2477
            $this->end_time   = '0000-00-00 00:00:00';
2478
        }
2479
2480
        if ($form->getSubmitValue('enabletimercontrol') == 1) {
2481
            $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes');
2482
            if ($this->expired_time == 0) {
2483
                $this->expired_time = $expired_total_time;
2484
            }
2485
        } else {
2486
            $this->expired_time = 0;
2487
        }
2488
2489
        if ($form->getSubmitValue('randomAnswers') == 1) {
2490
            $this->random_answers=1;
2491
        } else {
2492
            $this->random_answers=0;
2493
        }
2494
        $this->save($type);
2495
    }
2496
2497
    function search_engine_save()
2498
    {
2499
        if ($_POST['index_document'] != 1) {
2500
            return;
2501
        }
2502
        $course_id = api_get_course_id();
2503
2504
        require_once api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php';
2505
        require_once api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php';
2506
        require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
2507
2508
        $specific_fields = get_specific_field_list();
2509
        $ic_slide = new IndexableChunk();
2510
2511
        $all_specific_terms = '';
2512
        foreach ($specific_fields as $specific_field) {
2513
            if (isset($_REQUEST[$specific_field['code']])) {
2514
                $sterms = trim($_REQUEST[$specific_field['code']]);
2515
                if (!empty($sterms)) {
2516
                    $all_specific_terms .= ' '. $sterms;
2517
                    $sterms = explode(',', $sterms);
2518
                    foreach ($sterms as $sterm) {
2519
                        $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2520
                        add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2521
                    }
2522
                }
2523
            }
2524
        }
2525
2526
        // build the chunk to index
2527
        $ic_slide->addValue("title", $this->exercise);
2528
        $ic_slide->addCourseId($course_id);
2529
        $ic_slide->addToolId(TOOL_QUIZ);
2530
        $xapian_data = array(
2531
            SE_COURSE_ID => $course_id,
2532
            SE_TOOL_ID => TOOL_QUIZ,
2533
            SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2534
            SE_USER => (int)api_get_user_id(),
2535
        );
2536
        $ic_slide->xapian_data = serialize($xapian_data);
2537
        $exercise_description = $all_specific_terms .' '. $this->description;
2538
        $ic_slide->addValue("content", $exercise_description);
2539
2540
        $di = new ChamiloIndexer();
2541
        isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2542
        $di->connectDb(NULL, NULL, $lang);
2543
        $di->addChunk($ic_slide);
2544
2545
        //index and return search engine document id
2546
        $did = $di->index();
2547
        if ($did) {
2548
            // save it to db
2549
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2550
            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2551
			    VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2552
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2553
            Database::query($sql);
2554
        }
2555
    }
2556
2557
    function search_engine_edit()
2558
    {
2559
        // update search enchine and its values table if enabled
2560
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian')) {
2561
            $course_id = api_get_course_id();
2562
2563
            // actually, it consists on delete terms from db,
2564
            // insert new ones, create a new search engine document, and remove the old one
2565
            // get search_did
2566
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2567
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2568
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2569
            $res = Database::query($sql);
2570
2571
            if (Database::num_rows($res) > 0) {
2572
                require_once(api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php');
2573
                require_once(api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php');
2574
                require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
2575
2576
                $se_ref = Database::fetch_array($res);
2577
                $specific_fields = get_specific_field_list();
2578
                $ic_slide = new IndexableChunk();
2579
2580
                $all_specific_terms = '';
2581
                foreach ($specific_fields as $specific_field) {
2582
                    delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->id);
2583
                    if (isset($_REQUEST[$specific_field['code']])) {
2584
                        $sterms = trim($_REQUEST[$specific_field['code']]);
2585
                        $all_specific_terms .= ' '. $sterms;
2586
                        $sterms = explode(',', $sterms);
2587
                        foreach ($sterms as $sterm) {
2588
                            $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2589
                            add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->id, $sterm);
2590
                        }
2591
                    }
2592
                }
2593
2594
                // build the chunk to index
2595
                $ic_slide->addValue("title", $this->exercise);
2596
                $ic_slide->addCourseId($course_id);
2597
                $ic_slide->addToolId(TOOL_QUIZ);
2598
                $xapian_data = array(
2599
                    SE_COURSE_ID => $course_id,
2600
                    SE_TOOL_ID => TOOL_QUIZ,
2601
                    SE_DATA => array('type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int)$this->id),
2602
                    SE_USER => (int)api_get_user_id(),
2603
                );
2604
                $ic_slide->xapian_data = serialize($xapian_data);
2605
                $exercise_description = $all_specific_terms .' '. $this->description;
2606
                $ic_slide->addValue("content", $exercise_description);
2607
2608
                $di = new ChamiloIndexer();
2609
                isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
2610
                $di->connectDb(NULL, NULL, $lang);
2611
                $di->remove_document((int)$se_ref['search_did']);
2612
                $di->addChunk($ic_slide);
2613
2614
                //index and return search engine document id
2615
                $did = $di->index();
2616
                if ($did) {
2617
                    // save it to db
2618
                    $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\'';
2619
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2620
                    Database::query($sql);
2621
                    $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2622
                        VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2623
                    $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id, $did);
2624
                    Database::query($sql);
2625
                }
2626
            } else {
2627
                $this->search_engine_save();
2628
            }
2629
        }
2630
2631
    }
2632
2633
    function search_engine_delete()
2634
    {
2635
        // remove from search engine if enabled
2636
        if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian') ) {
2637
            $course_id = api_get_course_id();
2638
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2639
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL LIMIT 1';
2640
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2641
            $res = Database::query($sql);
2642
            if (Database::num_rows($res) > 0) {
2643
                $row = Database::fetch_array($res);
2644
                require_once(api_get_path(LIBRARY_PATH) .'search/ChamiloIndexer.class.php');
2645
                $di = new ChamiloIndexer();
2646
                $di->remove_document((int)$row['search_did']);
2647
                unset($di);
2648
                $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2649
                foreach ( $this->questionList as $question_i) {
2650
                    $sql = 'SELECT type FROM %s WHERE id=%s';
2651
                    $sql = sprintf($sql, $tbl_quiz_question, $question_i);
2652
                    $qres = Database::query($sql);
2653
                    if (Database::num_rows($qres) > 0) {
2654
                        $qrow = Database::fetch_array($qres);
2655
                        $objQuestion = Question::getInstance($qrow['type']);
2656
                        $objQuestion = Question::read((int)$question_i);
2657
                        $objQuestion->search_engine_edit($this->id, FALSE, TRUE);
2658
                        unset($objQuestion);
2659
                    }
2660
                }
2661
            }
2662
            $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL LIMIT 1';
2663
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
2664
            Database::query($sql);
2665
2666
            // remove terms from db
2667
            require_once api_get_path(LIBRARY_PATH) .'specific_fields_manager.lib.php';
2668
            delete_all_values_for_item($course_id, TOOL_QUIZ, $this->id);
2669
        }
2670
    }
2671
2672
    function selectExpiredTime()
2673
    {
2674
        return $this->expired_time;
2675
    }
2676
2677
    /**
2678
     * Cleans the student's results only for the Exercise tool (Not from the LP)
2679
     * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true
2680
     * Works with exercises in sessions
2681
     * @param bool $cleanLpTests
2682
     * @param string $cleanResultBeforeDate
2683
     *
2684
     * @return int quantity of user's exercises deleted
2685
     */
2686
    public function clean_results($cleanLpTests = false, $cleanResultBeforeDate = null)
2687
    {
2688
        $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2689
        $table_track_e_attempt   = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2690
2691
        $sql_where = '  AND
2692
                        orig_lp_id = 0 AND
2693
                        orig_lp_item_id = 0';
2694
2695
        // if we want to delete results from LP too
2696
        if ($cleanLpTests) {
2697
            $sql_where = "";
2698
        }
2699
2700
        // if we want to delete attempts before date $cleanResultBeforeDate
2701
        // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd
2702
2703
        if (!empty($cleanResultBeforeDate)) {
2704
            $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate);
2705
            if (api_is_valid_date($cleanResultBeforeDate)) {
2706
                $sql_where .= "  AND exe_date <= '$cleanResultBeforeDate' ";
2707
            } else {
2708
                return 0;
2709
            }
2710
        }
2711
2712
        $sql = "SELECT exe_id
2713
                FROM $table_track_e_exercises
2714
                WHERE
2715
                    c_id = ".api_get_course_int_id()." AND
2716
                    exe_exo_id = ".$this->id." AND
2717
                    session_id = ".api_get_session_id()." ".
2718
                    $sql_where;
2719
2720
        $result   = Database::query($sql);
2721
        $exe_list = Database::store_result($result);
2722
2723
        // deleting TRACK_E_ATTEMPT table
2724
        // check if exe in learning path or not
2725
        $i = 0;
2726
        if (is_array($exe_list) && count($exe_list) > 0) {
2727
            foreach ($exe_list as $item) {
2728
                $sql = "DELETE FROM $table_track_e_attempt
2729
                        WHERE exe_id = '".$item['exe_id']."'";
2730
                Database::query($sql);
2731
                $i++;
2732
            }
2733
        }
2734
2735
        $session_id = api_get_session_id();
2736
        // delete TRACK_E_EXERCISES table
2737
        $sql = "DELETE FROM $table_track_e_exercises
2738
                WHERE c_id = ".api_get_course_int_id()."
2739
                AND exe_exo_id = ".$this->id."
2740
                $sql_where
2741
                AND session_id = ".$session_id."";
2742
        Database::query($sql);
2743
2744
        Event::addEvent(
2745
            LOG_EXERCISE_RESULT_DELETE,
2746
            LOG_EXERCISE_ID,
2747
            $this->id,
2748
            null,
2749
            null,
2750
            api_get_course_int_id(),
2751
            $session_id
2752
        );
2753
2754
        return $i;
2755
    }
2756
2757
    /**
2758
     * Copies an exercise (duplicate all questions and answers)
2759
     */
2760
    public function copy_exercise()
2761
    {
2762
        $exercise_obj= new Exercise();
2763
        $exercise_obj = $this;
2764
2765
        // force the creation of a new exercise
2766
        $exercise_obj->updateTitle($exercise_obj->selectTitle().' - '.get_lang('Copy'));
2767
        //Hides the new exercise
2768
        $exercise_obj->updateStatus(false);
2769
        $exercise_obj->updateId(0);
2770
        $exercise_obj->save();
2771
2772
        $new_exercise_id = $exercise_obj->selectId();
2773
        $question_list 	 = $exercise_obj->selectQuestionList();
2774
2775
        if (!empty($question_list)) {
2776
            //Question creation
2777
2778
            foreach ($question_list as $old_question_id) {
2779
                $old_question_obj = Question::read($old_question_id);
2780
                $new_id = $old_question_obj->duplicate();
2781
                if ($new_id) {
2782
                    $new_question_obj = Question::read($new_id);
2783
2784
                    if (isset($new_question_obj) && $new_question_obj) {
2785
                        $new_question_obj->addToList($new_exercise_id);
2786
                        // This should be moved to the duplicate function
2787
                        $new_answer_obj = new Answer($old_question_id);
2788
                        $new_answer_obj->read();
2789
                        $new_answer_obj->duplicate($new_id);
2790
                    }
2791
                }
2792
            }
2793
        }
2794
    }
2795
2796
    /**
2797
     * Changes the exercise id
2798
     *
2799
     * @param int $id - exercise id
2800
     */
2801
    private function updateId($id)
2802
    {
2803
        $this->id = $id;
2804
    }
2805
2806
    /**
2807
     * Changes the exercise status
2808
     *
2809
     * @param string $status - exercise status
2810
     */
2811
    function updateStatus($status)
2812
    {
2813
        $this->active = $status;
2814
    }
2815
2816
    /**
2817
     * @param int $lp_id
2818
     * @param int $lp_item_id
2819
     * @param int $lp_item_view_id
2820
     * @param string $status
2821
     * @return array
2822
     */
2823
    public function get_stat_track_exercise_info(
2824
        $lp_id = 0,
2825
        $lp_item_id = 0,
2826
        $lp_item_view_id = 0,
2827
        $status = 'incomplete'
2828
    ) {
2829
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2830
        if (empty($lp_id)) {
2831
            $lp_id = 0;
2832
        }
2833
        if (empty($lp_item_id)) {
2834
            $lp_item_id   = 0;
2835
        }
2836
        if (empty($lp_item_view_id)) {
2837
            $lp_item_view_id = 0;
2838
        }
2839
        $condition = ' WHERE exe_exo_id 	= ' . "'" . $this->id . "'" .' AND
2840
					   exe_user_id 			= ' . "'" . api_get_user_id() . "'" . ' AND
2841
					   c_id                 = ' . api_get_course_int_id() . ' AND
2842
					   status 				= ' . "'" . Database::escape_string($status). "'" . ' AND
2843
					   orig_lp_id 			= ' . "'" . $lp_id . "'" . ' AND
2844
					   orig_lp_item_id 		= ' . "'" . $lp_item_id . "'" . ' AND
2845
                       orig_lp_item_view_id = ' . "'" . $lp_item_view_id . "'" . ' AND
2846
					   session_id 			= ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case
2847
2848
        $sql_track = 'SELECT * FROM '.$track_exercises.$condition;
2849
2850
        $result = Database::query($sql_track);
2851
        $new_array = array();
2852
        if (Database::num_rows($result) > 0 ) {
2853
            $new_array = Database::fetch_array($result, 'ASSOC');
2854
            $new_array['num_exe'] = Database::num_rows($result);
2855
        }
2856
        return $new_array;
2857
    }
2858
2859
    /**
2860
     * Saves a test attempt
2861
     *
2862
     * @param int  clock_expired_time
2863
     * @param int  int lp id
2864
     * @param int  int lp item id
2865
     * @param int  int lp item_view id
2866
     * @param float $weight
2867
     * @param array question list
2868
     */
2869
    public function save_stat_track_exercise_info(
2870
        $clock_expired_time = 0,
2871
        $safe_lp_id = 0,
2872
        $safe_lp_item_id = 0,
2873
        $safe_lp_item_view_id = 0,
2874
        $questionList = array(),
2875
        $weight = 0
2876
    ) {
2877
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2878
        $safe_lp_id = intval($safe_lp_id);
2879
        $safe_lp_item_id = intval($safe_lp_item_id);
2880
        $safe_lp_item_view_id = intval($safe_lp_item_view_id);
2881
2882
        if (empty($safe_lp_id)) {
2883
            $safe_lp_id = 0;
2884
        }
2885
        if (empty($safe_lp_item_id)) {
2886
            $safe_lp_item_id = 0;
2887
        }
2888
        if (empty($clock_expired_time)) {
2889
            $clock_expired_time = 0;
2890
        }
2891
2892
        $questionList = array_map('intval', $questionList);
2893
2894
        $params = array(
2895
            'exe_exo_id' => $this->id ,
2896
            'exe_user_id' => api_get_user_id(),
2897
            'c_id' => api_get_course_int_id(),
2898
            'status' =>  'incomplete',
2899
            'session_id'  => api_get_session_id(),
2900
            'data_tracking'  => implode(',', $questionList) ,
2901
            'start_date' => api_get_utc_datetime(),
2902
            'orig_lp_id' => $safe_lp_id,
2903
            'orig_lp_item_id'  => $safe_lp_item_id,
2904
            'orig_lp_item_view_id'  => $safe_lp_item_view_id,
2905
            'exe_weighting'=> $weight,
2906
            'user_ip' => api_get_real_ip()
2907
        );
2908
2909
        if ($this->expired_time != 0) {
2910
            $params['expired_time_control'] = $clock_expired_time;
2911
        }
2912
2913
        $id = Database::insert($track_exercises, $params);
2914
2915
        return $id;
2916
    }
2917
2918
    /**
2919
     * @param int $question_id
2920
     * @param int $questionNum
2921
     * @param array $questions_in_media
2922
     * @param string $currentAnswer
2923
     * @return string
2924
     */
2925
    public function show_button($question_id, $questionNum, $questions_in_media = array(), $currentAnswer = '')
2926
    {
2927
        global $origin, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id;
2928
2929
        $nbrQuestions = $this->get_count_question_list();
2930
2931
        $all_button = $html = $label = '';
2932
        $hotspot_get = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']):null;
2933
2934
        if ($this->selectFeedbackType() == EXERCISE_FEEDBACK_TYPE_DIRECT && $this->type == ONE_PER_PAGE) {
2935
            $urlTitle = get_lang('ContinueTest');
2936
2937
            if ($questionNum == count($this->questionList)) {
2938
                $urlTitle = get_lang('EndTest');
2939
            }
2940
2941
            $html .= Display::url(
2942
                $urlTitle,
2943
                'exercise_submit_modal.php?' . http_build_query([
2944
                    'learnpath_id' => $safe_lp_id,
2945
                    'learnpath_item_id' => $safe_lp_item_id,
2946
                    'learnpath_item_view_id' => $safe_lp_item_view_id,
2947
                    'origin' => $origin,
2948
                    'hotspot' => $hotspot_get,
2949
                    'nbrQuestions' => $nbrQuestions,
2950
                    'num' => $questionNum,
2951
                    'exerciseType' => $this->type,
2952
                    'exerciseId' => $this->id
2953
                ]),
2954
                [
2955
                    'class' => 'ajax btn btn-default',
2956
                    'data-title' => $urlTitle,
2957
                    'data-size' => 'md'
2958
                ]
2959
            );
2960
            $html .='<br />';
2961
        } else {
2962
            // User
2963
            if (api_is_allowed_to_session_edit()) {
2964
                if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum) {
2965
                    if ($this->review_answers) {
2966
                        $label = get_lang('ReviewQuestions');
2967
                        $class = 'btn btn-success';
2968
                    } else {
2969
                        $label = get_lang('EndTest');
2970
                        $class = 'btn btn-warning';
2971
                    }
2972
                } else {
2973
                    $label = get_lang('NextQuestion');
2974
                    $class = 'btn btn-primary';
2975
                }
2976
				$class .= ' question-validate-btn'; // used to select it with jquery
2977
                if ($this->type == ONE_PER_PAGE) {
2978
                    if ($questionNum != 1) {
2979
                        $prev_question = $questionNum - 2;
2980
                        $all_button .= '<a href="javascript://" class="btn btn-default" onclick="previous_question_and_save('.$prev_question.', '.$question_id.' ); ">'.get_lang('PreviousQuestion').'</a>';
2981
                    }
2982
2983
                    //Next question
2984
                    if (!empty($questions_in_media)) {
2985
                        $questions_in_media = "['".implode("','",$questions_in_media)."']";
2986
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_question_list('.$questions_in_media.'); ">'.$label.'</a>';
2987
                    } else {
2988
                        $all_button .= '&nbsp;<a href="javascript://" class="'.$class.'" onclick="save_now('.$question_id.', \'\', \''.$currentAnswer.'\'); ">'.$label.'</a>';
2989
                    }
2990
                    $all_button .= '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>&nbsp;';
2991
2992
                    $html .= $all_button;
2993
                } else {
2994
                    if ($this->review_answers) {
2995
                        $all_label = get_lang('ReviewQuestions');
2996
                        $class = 'btn btn-success';
2997
                    } else {
2998
                        $all_label = get_lang('EndTest');
2999
                        $class = 'btn btn-warning';
3000
                    }
3001
					$class .= ' question-validate-btn'; // used to select it with jquery
3002
                    $all_button = '&nbsp;<a href="javascript://" class="'.$class.'" onclick="validate_all(); ">'.$all_label.'</a>';
3003
                    $all_button .= '&nbsp;' . Display::span(null, ['id' => 'save_all_reponse']);
3004
                    $html .= $all_button;
3005
                }
3006
            }
3007
        }
3008
        return $html;
3009
    }
3010
3011
    /**
3012
     * So the time control will work
3013
     *
3014
     * @param string $time_left
3015
     * @return string
3016
     */
3017
    public function show_time_control_js($time_left)
3018
    {
3019
        $time_left = intval($time_left);
3020
        return "<script>
3021
3022
            function get_expired_date_string(expired_time) {
3023
                var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
3024
                var day, month, year, hours, minutes, seconds, date_string;
3025
                var obj_date = new Date(expired_time);
3026
                day     = obj_date.getDate();
3027
                if (day < 10) day = '0' + day;
3028
                    month   = obj_date.getMonth();
3029
                    year    = obj_date.getFullYear();
3030
                    hours   = obj_date.getHours();
3031
                if (hours < 10) hours = '0' + hours;
3032
                minutes = obj_date.getMinutes();
3033
                if (minutes < 10) minutes = '0' + minutes;
3034
                seconds = obj_date.getSeconds();
3035
                if (seconds < 10) seconds = '0' + seconds;
3036
                date_string = months[month] +' ' + day + ', ' + year + ' ' + hours + ':' + minutes + ':' + seconds;
3037
                return date_string;
3038
            }
3039
3040
            function open_clock_warning() {
3041
                $('#clock_warning').dialog({
3042
                    modal:true,
3043
                    height:250,
3044
                    closeOnEscape: false,
3045
                    resizable: false,
3046
                    buttons: {
3047
                        '".addslashes(get_lang("EndTest"))."': function() {
3048
                            $('#clock_warning').dialog('close');
3049
                        }
3050
                    },
3051
                    close: function() {
3052
                        send_form();
3053
                    }
3054
                });
3055
                $('#clock_warning').dialog('open');
3056
3057
                $('#counter_to_redirect').epiclock({
3058
                    mode: $.epiclock.modes.countdown,
3059
                    offset: {seconds: 5},
3060
                    format: 's'
3061
                }).bind('timer', function () {
3062
                    send_form();
3063
                });
3064
3065
            }
3066
3067
            function send_form() {
3068
                if ($('#exercise_form').length) {
3069
                    $('#exercise_form').submit();
3070
                } else {
3071
                    //In reminder
3072
                    final_submit();
3073
                }
3074
            }
3075
3076
            function onExpiredTimeExercise() {
3077
                $('#wrapper-clock').hide();
3078
                $('#exercise_form').hide();
3079
                $('#expired-message-id').show();
3080
3081
                //Fixes bug #5263
3082
                $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."');
3083
                open_clock_warning();
3084
            }
3085
3086
			$(document).ready(function() {
3087
3088
				var current_time = new Date().getTime();
3089
                var time_left    = parseInt(".$time_left."); // time in seconds when using minutes there are some seconds lost
3090
				var expired_time = current_time + (time_left*1000);
3091
				var expired_date = get_expired_date_string(expired_time);
3092
3093
                $('#exercise_clock_warning').epiclock({
3094
                    mode: $.epiclock.modes.countdown,
3095
                    offset: {seconds: time_left},
3096
                    format: 'x:i:s',
3097
                    renderer: 'minute'
3098
                }).bind('timer', function () {
3099
                    onExpiredTimeExercise();
3100
                });
3101
	       		$('#submit_save').click(function () {});
3102
	    });
3103
	    </script>";
3104
    }
3105
3106
    /**
3107
     * Lp javascript for hotspots
3108
     */
3109
    public function show_lp_javascript()
3110
    {
3111
        return "";
3112
    }
3113
3114
    /**
3115
     * This function was originally found in the exercise_show.php
3116
     * @param int       $exeId
3117
     * @param int       $questionId
3118
     * @param int       $choice the user selected
3119
     * @param string    $from  function is called from 'exercise_show' or 'exercise_result'
3120
     * @param array     $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates
3121
     * @param bool      $saved_results save results in the DB or just show the reponse
3122
     * @param bool      $from_database gets information from DB or from the current selection
3123
     * @param bool      $show_result show results or not
3124
     * @param int       $propagate_neg
3125
     * @param array     $hotspot_delineation_result
3126
     *
3127
     * @todo    reduce parameters of this function
3128
     * @return  string  html code
3129
     */
3130
    public function manage_answer(
3131
        $exeId,
3132
        $questionId,
3133
        $choice,
3134
        $from = 'exercise_show',
3135
        $exerciseResultCoordinates = array(),
3136
        $saved_results = true,
3137
        $from_database = false,
3138
        $show_result = true,
3139
        $propagate_neg = 0,
3140
        $hotspot_delineation_result = array(),
3141
        $showTotalScoreAndUserChoices = false
3142
    ) {
3143
        global $debug;
3144
        //needed in order to use in the exercise_attempt() for the time
3145
        global $learnpath_id, $learnpath_item_id;
3146
        require_once api_get_path(LIBRARY_PATH).'geometry.lib.php';
3147
3148
        $em = Database::getManager();
3149
3150
        $feedback_type = $this->selectFeedbackType();
3151
        $results_disabled = $this->selectResultsDisabled();
3152
3153
        if ($debug) {
3154
            error_log("<------ manage_answer ------> ");
3155
            error_log('exe_id: '.$exeId);
3156
            error_log('$from:  '.$from);
3157
            error_log('$saved_results: '.intval($saved_results));
3158
            error_log('$from_database: '.intval($from_database));
3159
            error_log('$show_result: '.$show_result);
3160
            error_log('$propagate_neg: '.$propagate_neg);
3161
            error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1));
3162
            error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1));
3163
            error_log('$learnpath_id: '.$learnpath_id);
3164
            error_log('$learnpath_item_id: '.$learnpath_item_id);
3165
            error_log('$choice: '.print_r($choice, 1));
3166
        }
3167
3168
        $extra_data = array();
3169
        $final_overlap = 0;
3170
        $final_missing = 0;
3171
        $final_excess = 0;
3172
        $overlap_color = 0;
3173
        $missing_color = 0;
3174
        $excess_color = 0;
3175
        $threadhold1 = 0;
3176
        $threadhold2 = 0;
3177
        $threadhold3 = 0;
3178
3179
        $arrques = null;
3180
        $arrans  = null;
3181
3182
        $questionId = intval($questionId);
3183
        $exeId = intval($exeId);
3184
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
3185
        $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER);
3186
3187
        // Creates a temporary Question object
3188
        $course_id = $this->course_id;
3189
        $objQuestionTmp = Question::read($questionId, $course_id);
3190
3191
        if ($objQuestionTmp === false) {
3192
            return false;
3193
        }
3194
3195
        $questionName = $objQuestionTmp->selectTitle();
3196
        $questionWeighting = $objQuestionTmp->selectWeighting();
3197
        $answerType = $objQuestionTmp->selectType();
3198
        $quesId = $objQuestionTmp->selectId();
3199
        $extra = $objQuestionTmp->extra;
3200
3201
        $next = 1; //not for now
3202
3203
        // Extra information of the question
3204
        if (!empty($extra)) {
3205
            $extra = explode(':', $extra);
3206
            if ($debug) {
3207
                error_log(print_r($extra, 1));
3208
            }
3209
            // Fixes problems with negatives values using intval
3210
            $true_score = floatval(trim($extra[0]));
3211
            $false_score = floatval(trim($extra[1]));
3212
            $doubt_score = floatval(trim($extra[2]));
3213
        }
3214
3215
        $totalWeighting = 0;
3216
        $totalScore = 0;
3217
3218
        // Destruction of the Question object
3219
        unset($objQuestionTmp);
3220
3221
        // Construction of the Answer object
3222
        $objAnswerTmp = new Answer($questionId);
3223
        $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
3224
3225
        if ($debug) {
3226
            error_log('Count of answers: '.$nbrAnswers);
3227
            error_log('$answerType: '.$answerType);
3228
        }
3229
3230
        if ($answerType == FREE_ANSWER ||
3231
            $answerType == ORAL_EXPRESSION ||
3232
            $answerType == CALCULATED_ANSWER
3233
        ) {
3234
            $nbrAnswers = 1;
3235
        }
3236
3237
        $nano = null;
3238
3239
        if ($answerType == ORAL_EXPRESSION) {
3240
            $exe_info = Event::get_exercise_results_by_attempt($exeId);
3241
            $exe_info = isset($exe_info[$exeId]) ? $exe_info[$exeId] : null;
3242
3243
            $params = array();
3244
            $params['course_id'] = $course_id;
3245
            $params['session_id'] = api_get_session_id();
3246
            $params['user_id'] = isset($exe_info['exe_user_id'])? $exe_info['exe_user_id'] : api_get_user_id();
3247
            $params['exercise_id'] = isset($exe_info['exe_exo_id'])? $exe_info['exe_exo_id'] : $this->id;
3248
            $params['question_id'] = $questionId;
3249
            $params['exe_id'] = isset($exe_info['exe_id']) ? $exe_info['exe_id'] : $exeId;
3250
3251
            $nano = new Nanogong($params);
3252
3253
            //probably this attempt came in an exercise all question by page
3254
            if ($feedback_type == 0) {
3255
                $nano->replace_with_real_exe($exeId);
3256
            }
3257
        }
3258
3259
        $user_answer = '';
3260
3261
        // Get answer list for matching
3262
        $sql = "SELECT id_auto, id, answer
3263
                FROM $table_ans
3264
                WHERE c_id = $course_id AND question_id = $questionId";
3265
        $res_answer = Database::query($sql);
3266
3267
        $answerMatching = array();
3268
        while ($real_answer = Database::fetch_array($res_answer)) {
3269
            $answerMatching[$real_answer['id_auto']] = $real_answer['answer'];
3270
        }
3271
3272
        $real_answers = array();
3273
        $quiz_question_options = Question::readQuestionOption(
3274
            $questionId,
3275
            $course_id
3276
        );
3277
3278
        $organs_at_risk_hit = 0;
3279
        $questionScore = 0;
3280
3281
        if ($debug) error_log('Start answer loop ');
3282
3283
        $orderedHotspots = [];
3284
3285
        if ($answerType == HOT_SPOT) {
3286
            $orderedHotspots = $em
3287
                ->getRepository('ChamiloCoreBundle:TrackEHotspot')
3288
                ->findBy([
3289
                        'hotspotQuestionId' => $questionId,
3290
                        'cId' => $course_id,
3291
                        'hotspotExeId' => $exeId
3292
                    ],
3293
                    ['hotspotId' => 'ASC']
3294
                );
3295
        }
3296
        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
3297
            $answer = $objAnswerTmp->selectAnswer($answerId);
3298
            $answerComment = $objAnswerTmp->selectComment($answerId);
3299
            $answerCorrect = $objAnswerTmp->isCorrect($answerId);
3300
            $answerWeighting = (float)$objAnswerTmp->selectWeighting($answerId);
3301
            $answerAutoId = $objAnswerTmp->selectAutoId($answerId);
3302
3303
            if ($debug) {
3304
                error_log("answer auto id: $answerAutoId ");
3305
                error_log("answer correct: $answerCorrect ");
3306
            }
3307
3308
            // Delineation
3309
            $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1);
3310
            $answer_delineation_destination=$objAnswerTmp->selectDestination(1);
3311
3312
            switch ($answerType) {
3313
                // for unique answer
3314
                case UNIQUE_ANSWER:
3315
                case UNIQUE_ANSWER_IMAGE:
3316
                case UNIQUE_ANSWER_NO_OPTION:
3317
                    if ($from_database) {
3318
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3319
                                WHERE
3320
                                    exe_id = '".$exeId."' AND
3321
                                    question_id= '".$questionId."'";
3322
                        $result = Database::query($sql);
3323
                        $choice = Database::result($result,0,"answer");
3324
3325
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3326
                        if ($studentChoice) {
3327
                            $questionScore += $answerWeighting;
3328
                            $totalScore += $answerWeighting;
3329
                        }
3330
                    } else {
3331
                        $studentChoice = $choice == $answerAutoId ? 1 : 0;
3332
                        if ($studentChoice) {
3333
                            $questionScore += $answerWeighting;
3334
                            $totalScore += $answerWeighting;
3335
                        }
3336
                    }
3337
                    break;
3338
                // for multiple answers
3339
                case MULTIPLE_ANSWER_TRUE_FALSE:
3340
                    if ($from_database) {
3341
                        $choice = array();
3342
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3343
                                WHERE
3344
                                    exe_id = $exeId AND
3345
                                    question_id = ".$questionId;
3346
3347
                        $result = Database::query($sql);
3348
                        while ($row = Database::fetch_array($result)) {
3349
                            $ind = $row['answer'];
3350
                            $values = explode(':', $ind);
3351
                            $my_answer_id = isset($values[0]) ? $values[0] : '';
3352
                            $option = isset($values[1]) ? $values[1] : '';
3353
                            $choice[$my_answer_id] = $option;
3354
                        }
3355
                    }
3356
3357
                    $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3358
3359
                    if (!empty($studentChoice)) {
3360
                        if ($studentChoice == $answerCorrect) {
3361
                            $questionScore += $true_score;
3362
                        } else {
3363
                            if ($quiz_question_options[$studentChoice]['name'] == "Don't know" ||
3364
                                $quiz_question_options[$studentChoice]['name'] == "DoubtScore"
3365
                            ) {
3366
                                $questionScore += $doubt_score;
3367
                            } else {
3368
                                $questionScore += $false_score;
3369
                            }
3370
                        }
3371
                    } else {
3372
                        // If no result then the user just hit don't know
3373
                        $studentChoice = 3;
3374
                        $questionScore  +=  $doubt_score;
3375
                    }
3376
                    $totalScore = $questionScore;
3377
                    break;
3378
                case MULTIPLE_ANSWER: //2
3379
                    if ($from_database) {
3380
                        $choice = array();
3381
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3382
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3383
                        $resultans = Database::query($sql);
3384
                        while ($row = Database::fetch_array($resultans)) {
3385
                            $ind = $row['answer'];
3386
                            $choice[$ind] = 1;
3387
                        }
3388
3389
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3390
                        $real_answers[$answerId] = (bool)$studentChoice;
3391
3392
                        if ($studentChoice) {
3393
                            $questionScore  +=$answerWeighting;
3394
                        }
3395
                    } else {
3396
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3397
                        $real_answers[$answerId] = (bool)$studentChoice;
3398
3399
                        if (isset($studentChoice)) {
3400
                            $questionScore  += $answerWeighting;
3401
                        }
3402
                    }
3403
                    $totalScore += $answerWeighting;
3404
3405
                    if ($debug) error_log("studentChoice: $studentChoice");
3406
                    break;
3407
                case GLOBAL_MULTIPLE_ANSWER:
3408
                    if ($from_database) {
3409
                        $choice = array();
3410
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3411
                                WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3412
                        $resultans = Database::query($sql);
3413
                        while ($row = Database::fetch_array($resultans)) {
3414
                            $ind = $row['answer'];
3415
                            $choice[$ind] = 1;
3416
                        }
3417
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3418
                        $real_answers[$answerId] = (bool)$studentChoice;
3419
                        if ($studentChoice) {
3420
                            $questionScore +=$answerWeighting;
3421
                        }
3422
                    } else {
3423
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3424
                        if (isset($studentChoice)) {
3425
                            $questionScore += $answerWeighting;
3426
                        }
3427
                        $real_answers[$answerId] = (bool)$studentChoice;
3428
                    }
3429
                    $totalScore += $answerWeighting;
3430
                    if ($debug) error_log("studentChoice: $studentChoice");
3431
                    break;
3432
                case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
3433
                    if ($from_database) {
3434
                        $sql = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3435
                                WHERE exe_id = $exeId AND question_id= ".$questionId;
3436
                        $resultans = Database::query($sql);
3437
                        while ($row = Database::fetch_array($resultans)) {
3438
                            $ind = $row['answer'];
3439
                            $result = explode(':',$ind);
3440
                            if (isset($result[0])) {
3441
                                $my_answer_id = isset($result[0]) ? $result[0] : '';
3442
                                $option = isset($result[1]) ? $result[1] : '';
3443
                                $choice[$my_answer_id] = $option;
3444
                            }
3445
                        }
3446
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3447
3448
                        if ($answerCorrect == $studentChoice) {
3449
                            //$answerCorrect = 1;
3450
                            $real_answers[$answerId] = true;
3451
                        } else {
3452
                            //$answerCorrect = 0;
3453
                            $real_answers[$answerId] = false;
3454
                        }
3455
                    } else {
3456
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : '';
3457
                        if ($answerCorrect == $studentChoice) {
3458
                            //$answerCorrect = 1;
3459
                            $real_answers[$answerId] = true;
3460
                        } else {
3461
                            //$answerCorrect = 0;
3462
                            $real_answers[$answerId] = false;
3463
                        }
3464
                    }
3465
                    break;
3466
                case MULTIPLE_ANSWER_COMBINATION:
3467
                    if ($from_database) {
3468
                        $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3469
                                WHERE exe_id = $exeId AND question_id= $questionId";
3470
                        $resultans = Database::query($sql);
3471
                        while ($row = Database::fetch_array($resultans)) {
3472
                            $ind = $row['answer'];
3473
                            $choice[$ind] = 1;
3474
                        }
3475
3476
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3477
3478
                        if ($answerCorrect == 1) {
3479
                            if ($studentChoice) {
3480
                                $real_answers[$answerId] = true;
3481
                            } else {
3482
                                $real_answers[$answerId] = false;
3483
                            }
3484
                        } else {
3485
                            if ($studentChoice) {
3486
                                $real_answers[$answerId] = false;
3487
                            } else {
3488
                                $real_answers[$answerId] = true;
3489
                            }
3490
                        }
3491
                    } else {
3492
                        $studentChoice = isset($choice[$answerAutoId]) ? $choice[$answerAutoId] : null;
3493
                        if ($answerCorrect == 1) {
3494
                            if ($studentChoice) {
3495
                                $real_answers[$answerId] = true;
3496
                            } else {
3497
                                $real_answers[$answerId] = false;
3498
                            }
3499
                        } else {
3500
                            if ($studentChoice) {
3501
                                $real_answers[$answerId] = false;
3502
                            } else {
3503
                                $real_answers[$answerId] = true;
3504
                            }
3505
                        }
3506
                    }
3507
                    break;
3508
                case FILL_IN_BLANKS:
3509
                    $str = '';
3510
                    if ($from_database) {
3511
                        $sql = "SELECT answer
3512
                                FROM $TBL_TRACK_ATTEMPT
3513
                                WHERE
3514
                                    exe_id = $exeId AND
3515
                                    question_id= ".intval($questionId);
3516
                        $result = Database::query($sql);
3517
                        $str = Database::result($result, 0, 'answer');
3518
                    }
3519
3520
                    if ($saved_results == false && strpos($str, 'font color') !== false) {
3521
                        // the question is encoded like this
3522
                        // [A] B [C] D [E] F::10,10,10@1
3523
                        // number 1 before the "@" means that is a switchable fill in blank question
3524
                        // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
3525
                        // means that is a normal fill blank question
3526
                        // first we explode the "::"
3527
                        $pre_array = explode('::', $answer);
3528
3529
                        // is switchable fill blank or not
3530
                        $last = count($pre_array) - 1;
3531
                        $is_set_switchable = explode('@', $pre_array[$last]);
3532
                        $switchable_answer_set = false;
3533
                        if (isset ($is_set_switchable[1]) && $is_set_switchable[1] == 1) {
3534
                            $switchable_answer_set = true;
3535
                        }
3536
                        $answer = '';
3537
                        for ($k = 0; $k < $last; $k++) {
3538
                            $answer .= $pre_array[$k];
3539
                        }
3540
                        // splits weightings that are joined with a comma
3541
                        $answerWeighting = explode(',', $is_set_switchable[0]);
3542
                        // we save the answer because it will be modified
3543
                        $temp = $answer;
3544
                        $answer = '';
3545
                        $j = 0;
3546
                        //initialise answer tags
3547
                        $user_tags = $correct_tags = $real_text = array();
3548
                        // the loop will stop at the end of the text
3549
                        while (1) {
3550
                            // quits the loop if there are no more blanks (detect '[')
3551
                            if (($pos = api_strpos($temp, '[')) === false) {
3552
                                // adds the end of the text
3553
                                $answer = $temp;
3554
                                $real_text[] = $answer;
3555
                                break; //no more "blanks", quit the loop
3556
                            }
3557
                            // adds the piece of text that is before the blank
3558
                            //and ends with '[' into a general storage array
3559
                            $real_text[] = api_substr($temp, 0, $pos +1);
3560
                            $answer .= api_substr($temp, 0, $pos +1);
3561
                            //take the string remaining (after the last "[" we found)
3562
                            $temp = api_substr($temp, $pos +1);
3563
                            // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3564
                            if (($pos = api_strpos($temp, ']')) === false) {
3565
                                // adds the end of the text
3566
                                $answer .= $temp;
3567
                                break;
3568
                            }
3569
                            if ($from_database) {
3570
                                $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3571
                                              WHERE
3572
                                                exe_id = '".$exeId."' AND
3573
                                                question_id= ".intval($questionId)."";
3574
                                $resfill = Database::query($queryfill);
3575
                                $str = Database::result($resfill, 0, 'answer');
3576
                                api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3577
                                $str = str_replace('\r\n', '', $str);
3578
3579
                                $choice = $arr[1];
3580
                                if (isset($choice[$j])) {
3581
                                    $tmp = api_strrpos($choice[$j], ' / ');
3582
                                    $choice[$j] = api_substr($choice[$j], 0, $tmp);
3583
                                    $choice[$j] = trim($choice[$j]);
3584
                                    // Needed to let characters ' and " to work as part of an answer
3585
                                    $choice[$j] = stripslashes($choice[$j]);
3586
                                } else {
3587
                                    $choice[$j] = null;
3588
                                }
3589
                            } else {
3590
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3591
                                $choice[$j] = api_htmlentities(trim($choice[$j]));
3592
                            }
3593
3594
                            $user_tags[] = $choice[$j];
3595
                            //put the contents of the [] answer tag into correct_tags[]
3596
                            $correct_tags[] = api_substr($temp, 0, $pos);
3597
                            $j++;
3598
                            $temp = api_substr($temp, $pos +1);
3599
                        }
3600
                        $answer = '';
3601
                        $real_correct_tags = $correct_tags;
3602
                        $chosen_list = array();
3603
3604
                        for ($i = 0; $i < count($real_correct_tags); $i++) {
3605
                            if ($i == 0) {
3606
                                $answer .= $real_text[0];
3607
                            }
3608
                            if (!$switchable_answer_set) {
3609
                                // Needed to parse ' and " characters
3610
                                $user_tags[$i] = stripslashes($user_tags[$i]);
3611
                                if ($correct_tags[$i] == $user_tags[$i]) {
3612
                                    // gives the related weighting to the student
3613
                                    $questionScore += $answerWeighting[$i];
3614
                                    // increments total score
3615
                                    $totalScore += $answerWeighting[$i];
3616
                                    // adds the word in green at the end of the string
3617
                                    $answer .= $correct_tags[$i];
3618
                                } elseif (!empty($user_tags[$i])) {
3619
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3620
                                    // adds the word in red at the end of the string, and strikes it
3621
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3622
                                } else {
3623
                                    // adds a tabulation if no word has been typed by the student
3624
                                    $answer .= ''; // remove &nbsp; that causes issue
3625
                                }
3626
                            } else {
3627
                                // switchable fill in the blanks
3628
                                if (in_array($user_tags[$i], $correct_tags)) {
3629
                                    $chosen_list[] = $user_tags[$i];
3630
                                    $correct_tags = array_diff($correct_tags, $chosen_list);
3631
                                    // gives the related weighting to the student
3632
                                    $questionScore += $answerWeighting[$i];
3633
                                    // increments total score
3634
                                    $totalScore += $answerWeighting[$i];
3635
                                    // adds the word in green at the end of the string
3636
                                    $answer .= $user_tags[$i];
3637
                                } elseif (!empty ($user_tags[$i])) {
3638
                                    // else if the word entered by the student IS NOT the same as the one defined by the professor
3639
                                    // adds the word in red at the end of the string, and strikes it
3640
                                    $answer .= '<font color="red"><s>' . $user_tags[$i] . '</s></font>';
3641
                                } else {
3642
                                    // adds a tabulation if no word has been typed by the student
3643
                                    $answer .= '';  // remove &nbsp; that causes issue
3644
                                }
3645
                            }
3646
3647
                            // adds the correct word, followed by ] to close the blank
3648
                            $answer .= ' / <font color="green"><b>' . $real_correct_tags[$i] . '</b></font>]';
3649
                            if (isset($real_text[$i +1])) {
3650
                                $answer .= $real_text[$i +1];
3651
                            }
3652
                        }
3653
                    } else {
3654
                        // insert the student result in the track_e_attempt table, field answer
3655
                        // $answer is the answer like in the c_quiz_answer table for the question
3656
                        // student data are choice[]
3657
                        $listCorrectAnswers = FillBlanks::getAnswerInfo(
3658
                            $answer
3659
                        );
3660
                        $switchableAnswerSet = $listCorrectAnswers["switchable"];
3661
                        $answerWeighting = $listCorrectAnswers["tabweighting"];
3662
                        // user choices is an array $choice
3663
3664
                        // get existing user data in n the BDD
3665
                        if ($from_database) {
3666
                            $sql = "SELECT answer
3667
                                    FROM $TBL_TRACK_ATTEMPT
3668
                                    WHERE
3669
                                        exe_id = $exeId AND
3670
                                        question_id= ".intval($questionId);
3671
                            $result = Database::query($sql);
3672
                            $str = Database::result($result, 0, 'answer');
3673
                            $listStudentResults = FillBlanks::getAnswerInfo(
3674
                                $str,
3675
                                true
3676
                            );
3677
                            $choice = $listStudentResults['studentanswer'];
3678
                        }
3679
3680
                        // loop other all blanks words
3681
                        if (!$switchableAnswerSet) {
3682
                            // not switchable answer, must be in the same place than teacher order
3683
                            for ($i = 0; $i < count($listCorrectAnswers['tabwords']); $i++) {
3684
                                $studentAnswer = isset($choice[$i]) ? trim($choice[$i]) : '';
3685
3686
                                // This value is the user input, not escaped while correct answer is escaped by fckeditor
3687
                                // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618
3688
                                if (!$from_database) {
3689
                                    $studentAnswer = htmlentities(
3690
                                        api_utf8_encode($studentAnswer)
3691
                                    );
3692
                                }
3693
3694
                                $correctAnswer = $listCorrectAnswers['tabwords'][$i];
3695
                                $isAnswerCorrect = 0;
3696
                                if (FillBlanks::isGoodStudentAnswer(
3697
                                    $studentAnswer,
3698
                                    $correctAnswer
3699
                                )
3700
                                ) {
3701
                                    // gives the related weighting to the student
3702
                                    $questionScore += $answerWeighting[$i];
3703
                                    // increments total score
3704
                                    $totalScore += $answerWeighting[$i];
3705
                                    $isAnswerCorrect = 1;
3706
                                }
3707
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3708
                                $listCorrectAnswers['studentscore'][$i] = $isAnswerCorrect;
3709
                            }
3710
                        } else {
3711
                            // switchable answer
3712
                            $listStudentAnswerTemp = $choice;
3713
                            $listTeacherAnswerTemp = $listCorrectAnswers['tabwords'];
3714
                            // for every teacher answer, check if there is a student answer
3715
                            for ($i = 0; $i < count($listStudentAnswerTemp); $i++) {
3716
                                $studentAnswer = trim(
3717
                                    $listStudentAnswerTemp[$i]
3718
                                );
3719
                                $found = false;
3720
                                for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) {
3721
                                    $correctAnswer = $listTeacherAnswerTemp[$j];
3722
                                    if (!$found) {
3723
                                        if (FillBlanks::isGoodStudentAnswer(
3724
                                            $studentAnswer,
3725
                                            $correctAnswer
3726
                                        )
3727
                                        ) {
3728
                                            $questionScore += $answerWeighting[$i];
3729
                                            $totalScore += $answerWeighting[$i];
3730
                                            $listTeacherAnswerTemp[$j] = "";
3731
                                            $found = true;
3732
                                        }
3733
                                    }
3734
                                }
3735
                                $listCorrectAnswers['studentanswer'][$i] = $studentAnswer;
3736
                                if (!$found) {
3737
                                    $listCorrectAnswers['studentscore'][$i] = 0;
3738
                                } else {
3739
                                    $listCorrectAnswers['studentscore'][$i] = 1;
3740
                                }
3741
                            }
3742
                        }
3743
                        $answer = FillBlanks::getAnswerInStudentAttempt(
3744
                            $listCorrectAnswers
3745
                        );
3746
                    }
3747
                    break;
3748
                case CALCULATED_ANSWER:
3749
                    $calculatedAnswerId = Session::read('calculatedAnswerId');
3750
                    $answer = '';
3751
                    if ($calculatedAnswerId) {
3752
                        $calculatedAnswerInfo = Session::read('calculatedAnswerInfo');
3753
                        if (isset($calculatedAnswerInfo[$questionId])) {
3754
                            $answer = $calculatedAnswerInfo[$questionId];
3755
                        } else {
3756
                            $answer = $objAnswerTmp->selectAnswer($calculatedAnswerId[$questionId]);
3757
                        }
3758
                    }
3759
                    $preArray = explode('@@', $answer);
3760
                    $last = count($preArray) - 1;
3761
                    $answer = '';
3762
                    for ($k = 0; $k < $last; $k++) {
3763
                        $answer .= $preArray[$k];
3764
                    }
3765
                    $answerWeighting = array($answerWeighting);
3766
                    // we save the answer because it will be modified
3767
                    $temp = $answer;
3768
                    $answer = '';
3769
                    $j = 0;
3770
                    //initialise answer tags
3771
                    $userTags = $correctTags = $realText = array();
3772
                    // the loop will stop at the end of the text
3773
                    while (1) {
3774
                        // quits the loop if there are no more blanks (detect '[')
3775
                        if (($pos = api_strpos($temp, '[')) === false) {
3776
                            // adds the end of the text
3777
                            $answer = $temp;
3778
                            $realText[] = $answer;
3779
                            break; //no more "blanks", quit the loop
3780
                        }
3781
                        // adds the piece of text that is before the blank
3782
                        //and ends with '[' into a general storage array
3783
                        $realText[] = api_substr($temp, 0, $pos +1);
3784
                        $answer .= api_substr($temp, 0, $pos +1);
3785
                        //take the string remaining (after the last "[" we found)
3786
                        $temp = api_substr($temp, $pos +1);
3787
                        // quit the loop if there are no more blanks, and update $pos to the position of next ']'
3788
                        if (($pos = api_strpos($temp, ']')) === false) {
3789
                            // adds the end of the text
3790
                            $answer .= $temp;
3791
                            break;
3792
                        }
3793
                        if ($from_database) {
3794
                            $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3795
                                          WHERE
3796
                                            exe_id = '".$exeId."' AND
3797
                                            question_id= ".intval($questionId)."";
3798
                            $resfill = Database::query($queryfill);
3799
                            $str = Database::result($resfill, 0, 'answer');
3800
                            api_preg_match_all('#\[([^[]*)\]#', $str, $arr);
3801
                            $str = str_replace('\r\n', '', $str);
3802
                            $choice = $arr[1];
3803
                            if (isset($choice[$j])) {
3804
                                $tmp = api_strrpos($choice[$j], ' / ');
3805
                                $choice[$j] = api_substr($choice[$j], 0, $tmp);
3806
                                $choice[$j] = trim($choice[$j]);
3807
                                // Needed to let characters ' and " to work as part of an answer
3808
                                $choice[$j] = stripslashes($choice[$j]);
3809
                            } else {
3810
                                $choice[$j] = null;
3811
                            }
3812
                        } else {
3813
                            // This value is the user input, not escaped while correct answer is escaped by fckeditor
3814
                            $choice[$j] = api_htmlentities(trim($choice[$j]));
3815
                        }
3816
                        $userTags[] = $choice[$j];
3817
                        //put the contents of the [] answer tag into correct_tags[]
3818
                        $correctTags[] = api_substr($temp, 0, $pos);
3819
                        $j++;
3820
                        $temp = api_substr($temp, $pos +1);
3821
                    }
3822
                    $answer = '';
3823
                    $realCorrectTags = $correctTags;
3824
3825
                    if ($from_database && empty($calculatedAnswerId)) {
3826
                        $queryfill = "SELECT answer FROM ".$TBL_TRACK_ATTEMPT."
3827
                                      WHERE
3828
                                        exe_id = '".$exeId."' AND
3829
                                        question_id= ".intval($questionId);
3830
                        $resfill = Database::query($queryfill);
3831
                        $rowFill = Database::fetch_assoc($resfill);
3832
                        $answer = $rowFill['answer'];
3833
                        $questionScore = $rowFill['marks'];
3834
                    }
3835
3836
                    for ($i = 0; $i < count($realCorrectTags); $i++) {
3837
                        if ($i == 0) {
3838
                            $answer .= $realText[0];
3839
                        }
3840
                        // Needed to parse ' and " characters
3841
                        $userTags[$i] = stripslashes($userTags[$i]);
3842
                        if ($correctTags[$i] == $userTags[$i]) {
3843
                            // gives the related weighting to the student
3844
                            $questionScore += $answerWeighting[$i];
3845
                            // increments total score
3846
                            $totalScore += $answerWeighting[$i];
3847
                            // adds the word in green at the end of the string
3848
                            $answer .= $correctTags[$i];
3849
                        } elseif (!empty($userTags[$i])) {
3850
                            // else if the word entered by the student IS NOT the same as the one defined by the professor
3851
                            // adds the word in red at the end of the string, and strikes it
3852
                            $answer .= '<font color="red"><s>' . $userTags[$i] . '</s></font>';
3853
                        } else {
3854
                            // adds a tabulation if no word has been typed by the student
3855
                            $answer .= ''; // remove &nbsp; that causes issue
3856
                        }
3857
                        // adds the correct word, followed by ] to close the blank
3858
                        $answer .= ' / <font color="green"><b>' . $realCorrectTags[$i] . '</b></font>]';
3859
                        if (isset($realText[$i +1])) {
3860
                            $answer .= $realText[$i +1];
3861
                        }
3862
                    }
3863
                    break;
3864
                case FREE_ANSWER:
3865
                    if ($from_database) {
3866
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3867
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3868
                        $resq = Database::query($query);
3869
                        $data = Database::fetch_array($resq);
3870
3871
                        $choice = $data['answer'];
3872
                        $choice = str_replace('\r\n', '', $choice);
3873
                        $choice = stripslashes($choice);
3874
                        $questionScore = $data['marks'];
3875
3876
                        if ($questionScore == -1) {
3877
                            $totalScore+= 0;
3878
                        } else {
3879
                            $totalScore+= $questionScore;
3880
                        }
3881
                        if ($questionScore == '') {
3882
                            $questionScore = 0;
3883
                        }
3884
                        $arrques = $questionName;
3885
                        $arrans = $choice;
3886
                    } else {
3887
                        $studentChoice = $choice;
3888
                        if ($studentChoice) {
3889
                            //Fixing negative puntation see #2193
3890
                            $questionScore = 0;
3891
                            $totalScore += 0;
3892
                        }
3893
                    }
3894
                    break;
3895
                case ORAL_EXPRESSION:
3896
                    if ($from_database) {
3897
                        $query  = "SELECT answer, marks FROM ".$TBL_TRACK_ATTEMPT."
3898
                                   WHERE exe_id = '".$exeId."' AND question_id= '".$questionId."'";
3899
                        $resq   = Database::query($query);
3900
                        $choice = Database::result($resq,0,'answer');
3901
                        $choice = str_replace('\r\n', '', $choice);
3902
                        $choice = stripslashes($choice);
3903
                        $questionScore = Database::result($resq,0,"marks");
3904
                        if ($questionScore == -1) {
3905
                            $totalScore+=0;
3906
                        } else {
3907
                            $totalScore+=$questionScore;
3908
                        }
3909
                        $arrques = $questionName;
3910
                        $arrans  = $choice;
3911
                    } else {
3912
                        $studentChoice = $choice;
3913
                        if ($studentChoice) {
3914
                            //Fixing negative puntation see #2193
3915
                            $questionScore = 0;
3916
                            $totalScore += 0;
3917
                        }
3918
                    }
3919
                    break;
3920
                case DRAGGABLE:
3921
                    //no break
3922
                case MATCHING_DRAGGABLE:
3923
                    //no break
3924
                case MATCHING:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
3925
                    if ($from_database) {
3926
                        $sql = 'SELECT id, answer, id_auto
3927
                                FROM '.$table_ans.'
3928
                                WHERE
3929
                                    c_id = '.$course_id.' AND
3930
                                    question_id = "'.$questionId.'" AND
3931
                                    correct = 0';
3932
                        $res_answer = Database::query($sql);
3933
                        // Getting the real answer
3934
                        $real_list = array();
3935
                        while ($real_answer = Database::fetch_array($res_answer)) {
3936
                            $real_list[$real_answer['id_auto']] = $real_answer['answer'];
3937
                        }
3938
3939
                        $sql = 'SELECT id, answer, correct, id_auto, ponderation
3940
                                FROM '.$table_ans.'
3941
                                WHERE
3942
                                    c_id = '.$course_id.' AND
3943
                                    question_id="'.$questionId.'" AND
3944
                                    correct <> 0
3945
                                ORDER BY id_auto';
3946
                        $res_answers = Database::query($sql);
3947
3948
                        $questionScore = 0;
3949
3950
                        while ($a_answers = Database::fetch_array($res_answers)) {
3951
                            $i_answer_id = $a_answers['id']; //3
3952
                            $s_answer_label = $a_answers['answer'];  // your daddy - your mother
3953
                            $i_answer_correct_answer = $a_answers['correct']; //1 - 2
3954
                            $i_answer_id_auto = $a_answers['id_auto']; // 3 - 4
3955
3956
                            $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT
3957
                                    WHERE
3958
                                        exe_id = '$exeId' AND
3959
                                        question_id = '$questionId' AND
3960
                                        position = '$i_answer_id_auto'";
3961
3962
                            $res_user_answer = Database::query($sql);
3963
3964
                            if (Database::num_rows($res_user_answer) > 0) {
3965
                                //  rich - good looking
3966
                                $s_user_answer = Database::result($res_user_answer, 0, 0);
3967
                            } else {
3968
                                $s_user_answer = 0;
3969
                            }
3970
3971
                            $i_answerWeighting = $a_answers['ponderation'];
3972
3973
                            $user_answer = '';
3974
                            if (!empty($s_user_answer)) {
3975
                                if ($answerType == DRAGGABLE) {
3976
                                    if ($s_user_answer == $i_answer_correct_answer) {
3977
                                        $questionScore += $i_answerWeighting;
3978
                                        $totalScore += $i_answerWeighting;
3979
                                        $user_answer = Display::label(get_lang('Correct'), 'success');
3980
                                    } else {
3981
                                        $user_answer = Display::label(get_lang('Incorrect'), 'danger');
3982
                                    }
3983
                                } else {
3984
                                    if ($s_user_answer == $i_answer_correct_answer) {
3985
                                        $questionScore += $i_answerWeighting;
3986
                                        $totalScore += $i_answerWeighting;
3987
3988
                                        if (isset($real_list[$i_answer_id])) {
3989
                                            $user_answer = Display::span($real_list[$i_answer_id]);
3990
                                        }
3991
                                    } else {
3992
                                        $user_answer = Display::span(
3993
                                            $real_list[$s_user_answer],
3994
                                            ['style' => 'color: #FF0000; text-decoration: line-through;']
3995
                                        );
3996
                                    }
3997
                                }
3998
                            } elseif ($answerType == DRAGGABLE) {
3999
                                $user_answer = Display::label(get_lang('Incorrect'), 'danger');
4000
                            }
4001
4002
                            if ($show_result) {
4003
                                if ($showTotalScoreAndUserChoices == true) {
4004
                                    $user_answer = '';
4005
                                }
4006
                                echo '<tr>';
4007
                                echo '<td>' . $s_answer_label . '</td>';
4008
                                echo '<td>' . $user_answer;
4009
4010
                                if (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4011
                                    if (isset($real_list[$i_answer_correct_answer]) && $showTotalScoreAndUserChoices == false) {
4012
                                        echo Display::span(
4013
                                            $real_list[$i_answer_correct_answer],
4014
                                            ['style' => 'color: #008000; font-weight: bold;']
4015
                                        );
4016
                                    }
4017
                                }
4018
                                echo '</td>';
4019
                                echo '</tr>';
4020
                            }
4021
                        }
4022
                        break(2); // break the switch and the "for" condition
4023
                    } else {
4024
                        if ($answerCorrect) {
4025
                            if (isset($choice[$answerAutoId]) &&
4026
                                $answerCorrect == $choice[$answerAutoId]
4027
                            ) {
4028
                                $questionScore += $answerWeighting;
4029
                                $totalScore += $answerWeighting;
4030
                                $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]);
4031
                            } else {
4032
                                if (isset($answerMatching[$choice[$answerAutoId]])) {
4033
                                    $user_answer = Display::span(
4034
                                        $answerMatching[$choice[$answerAutoId]],
4035
                                        ['style' => 'color: #FF0000; text-decoration: line-through;']
4036
                                    );
4037
                                }
4038
                            }
4039
                            $matching[$answerAutoId] = $choice[$answerAutoId];
4040
                        }
4041
                        break;
4042
                    }
4043
                case HOT_SPOT:
4044
                    if ($from_database) {
4045
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4046
                        $sql = "SELECT hotspot_correct
4047
                                FROM $TBL_TRACK_HOTSPOT
4048
                                WHERE
4049
                                    hotspot_exe_id = '".$exeId."' AND
4050
                                    hotspot_question_id= '".$questionId."' AND
4051
                                    hotspot_answer_id = ".intval($answerAutoId)."";
4052
                        $result = Database::query($sql);
4053
                        $studentChoice = Database::result($result, 0, "hotspot_correct");
4054
4055
                        if ($studentChoice) {
4056
                            $questionScore += $answerWeighting;
4057
                            $totalScore += $answerWeighting;
4058
                        }
4059
                    } else {
4060
                        if (!isset($choice[$answerAutoId])) {
4061
                            $choice[$answerAutoId] = 0;
4062
                        } else {
4063
                            $studentChoice = $choice[$answerAutoId];
4064
4065
                            $choiceIsValid = false;
4066
4067
                            if (!empty($studentChoice)) {
4068
                                $hotspotType = $objAnswerTmp->selectHotspotType($answerId);
4069
                                $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId);
4070
                                $choicePoint = Geometry::decodePoint($studentChoice);
4071
4072
                                switch ($hotspotType) {
4073
                                    case 'square':
4074
                                        $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates);
4075
                                        $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint);
4076
                                        break;
4077
4078
                                    case 'circle':
4079
                                        $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates);
4080
                                        $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint);
4081
                                        break;
4082
4083
                                    case 'poly':
4084
                                        $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates);
4085
                                        $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint);
4086
                                        break;
4087
                                }
4088
                            }
4089
4090
                            $choice[$answerAutoId] = 0;
4091
                            if ($choiceIsValid) {
4092
                                $questionScore += $answerWeighting;
4093
                                $totalScore += $answerWeighting;
4094
                                $choice[$answerAutoId] = 1;
4095
                            }
4096
                        }
4097
                    }
4098
                    break;
4099
                // @todo never added to chamilo
4100
                //for hotspot with fixed order
4101
                case HOT_SPOT_ORDER :
4102
                    $studentChoice = $choice['order'][$answerId];
4103
                    if ($studentChoice == $answerId) {
4104
                        $questionScore  += $answerWeighting;
4105
                        $totalScore     += $answerWeighting;
4106
                        $studentChoice = true;
4107
                    } else {
4108
                        $studentChoice = false;
4109
                    }
4110
                    break;
4111
                // for hotspot with delineation
4112
                case HOT_SPOT_DELINEATION :
4113
                    if ($from_database) {
4114
                        // getting the user answer
4115
                        $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4116
                        $query   = "SELECT hotspot_correct, hotspot_coordinate
4117
                                    FROM $TBL_TRACK_HOTSPOT
4118
                                    WHERE
4119
                                        hotspot_exe_id = '".$exeId."' AND
4120
                                        hotspot_question_id= '".$questionId."' AND
4121
                                        hotspot_answer_id='1'";
4122
                        //by default we take 1 because it's a delineation
4123
                        $resq = Database::query($query);
4124
                        $row = Database::fetch_array($resq,'ASSOC');
4125
4126
                        $choice = $row['hotspot_correct'];
4127
                        $user_answer = $row['hotspot_coordinate'];
4128
4129
                        // THIS is very important otherwise the poly_compile will throw an error!!
4130
                        // round-up the coordinates
4131
                        $coords = explode('/',$user_answer);
4132
                        $user_array = '';
4133
                        foreach ($coords as $coord) {
4134
                            list($x,$y) = explode(';',$coord);
4135
                            $user_array .= round($x).';'.round($y).'/';
4136
                        }
4137
                        $user_array = substr($user_array,0,-1);
4138
                    } else {
4139
                        if (!empty($studentChoice)) {
4140
                            $newquestionList[] = $questionId;
4141
                        }
4142
4143
                        if ($answerId === 1) {
4144
                            $studentChoice = $choice[$answerId];
4145
                            $questionScore += $answerWeighting;
4146
4147
                            if ($hotspot_delineation_result[1]==1) {
4148
                                $totalScore += $answerWeighting; //adding the total
4149
                            }
4150
                        }
4151
                    }
4152
                    $_SESSION['hotspot_coord'][1] = $delineation_cord;
4153
                    $_SESSION['hotspot_dest'][1] = $answer_delineation_destination;
4154
                    break;
4155
            } // end switch Answertype
4156
4157
            if ($show_result) {
4158
                if ($debug) error_log('Showing questions $from '.$from);
4159
                if ($from == 'exercise_result') {
4160
                    // display answers (if not matching type, or if the answer is correct)
4161
                    if (
4162
                        !in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE]) ||
4163
                        $answerCorrect
4164
                    ) {
4165
                        if (
4166
                            in_array(
4167
                                $answerType,
4168
                                array(
4169
                                    UNIQUE_ANSWER,
4170
                                    UNIQUE_ANSWER_IMAGE,
4171
                                    UNIQUE_ANSWER_NO_OPTION,
4172
                                    MULTIPLE_ANSWER,
4173
                                    MULTIPLE_ANSWER_COMBINATION,
4174
                                    GLOBAL_MULTIPLE_ANSWER
4175
                                )
4176
                            )
4177
                        ) {
4178
                            ExerciseShowFunctions::display_unique_or_multiple_answer(
4179
                                $feedback_type,
4180
                                $answerType,
4181
                                $studentChoice,
4182
                                $answer,
4183
                                $answerComment,
4184
                                $answerCorrect,
4185
                                0,
4186
                                0,
4187
                                0,
4188
                                $results_disabled,
4189
                                $showTotalScoreAndUserChoices
4190
                            );
4191
                        } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
4192
                            ExerciseShowFunctions::display_multiple_answer_true_false(
4193
                                $feedback_type,
4194
                                $answerType,
4195
                                $studentChoice,
4196
                                $answer,
4197
                                $answerComment,
4198
                                $answerCorrect,
4199
                                0,
4200
                                $questionId,
4201
                                0,
4202
                                $results_disabled,
4203
                                $showTotalScoreAndUserChoices
4204
                            );
4205
                        } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
4206
                            ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4207
                                $feedback_type,
4208
                                $answerType,
4209
                                $studentChoice,
4210
                                $answer,
4211
                                $answerComment,
4212
                                $answerCorrect,
4213
                                0,
4214
                                0,
4215
                                0,
4216
                                $results_disabled,
4217
                                $showTotalScoreAndUserChoices
4218
                            );
4219
                        } elseif ($answerType == FILL_IN_BLANKS) {
4220
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4221
                                $feedback_type,
4222
                                $answer,
4223
                                0,
4224
                                0,
4225
                                $results_disabled,
4226
                                '',
4227
                                $showTotalScoreAndUserChoices
4228
                            );
4229
                        } elseif ($answerType == CALCULATED_ANSWER) {
4230
                            ExerciseShowFunctions::display_calculated_answer(
4231
                                $feedback_type,
4232
                                $answer,
4233
                                0,
4234
                                0,
4235
                                $results_disabled,
4236
                                $showTotalScoreAndUserChoices
4237
                            );
4238
                        } elseif ($answerType == FREE_ANSWER) {
4239
                            ExerciseShowFunctions::display_free_answer(
4240
                                $feedback_type,
4241
                                $choice,
4242
                                $exeId,
4243
                                $questionId,
4244
                                $questionScore,
4245
                                $results_disabled
4246
                            );
4247
                        } elseif ($answerType == ORAL_EXPRESSION) {
4248
                            // to store the details of open questions in an array to be used in mail
4249
                            ExerciseShowFunctions::display_oral_expression_answer(
4250
                                $feedback_type,
4251
                                $choice,
4252
                                0,
4253
                                0,
4254
                                $nano,
4255
                                $results_disabled
4256
                            );
4257
                        } elseif ($answerType == HOT_SPOT) {
4258
                            foreach ($orderedHotspots as $correctAnswerId => $hotspot) {
4259
                                if ($hotspot->getHotspotAnswerId() == $answerAutoId) {
4260
                                    break;
4261
                                }
4262
                            }
4263
4264
                            ExerciseShowFunctions::display_hotspot_answer(
4265
                                $feedback_type,
4266
                                ++$correctAnswerId,
4267
                                $answer,
4268
                                $studentChoice,
4269
                                $answerComment,
4270
                                $results_disabled,
4271
                                $answerId,
4272
                                $showTotalScoreAndUserChoices
4273
                            );
4274
                        } elseif ($answerType == HOT_SPOT_ORDER) {
4275
                            ExerciseShowFunctions::display_hotspot_order_answer(
4276
                                $feedback_type,
4277
                                $answerId,
4278
                                $answer,
4279
                                $studentChoice,
4280
                                $answerComment
4281
                            );
4282
                        } elseif ($answerType == HOT_SPOT_DELINEATION) {
4283
                            $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId];
4284
4285
                            //round-up the coordinates
4286
                            $coords = explode('/',$user_answer);
4287
                            $user_array = '';
4288
                            foreach ($coords as $coord) {
4289
                                list($x,$y) = explode(';',$coord);
4290
                                $user_array .= round($x).';'.round($y).'/';
4291
                            }
4292
                            $user_array = substr($user_array,0,-1);
4293
4294
                            if ($next) {
4295
4296
                                $user_answer = $user_array;
4297
4298
                                // we compare only the delineation not the other points
4299
                                $answer_question = $_SESSION['hotspot_coord'][1];
4300
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4301
4302
                                //calculating the area
4303
                                $poly_user = convert_coordinates($user_answer, '/');
4304
                                $poly_answer = convert_coordinates($answer_question, '|');
4305
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4306
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4307
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4308
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4309
4310
                                $overlap = $poly_results['both'];
4311
                                $poly_answer_area = $poly_results['s1'];
4312
                                $poly_user_area = $poly_results['s2'];
4313
                                $missing = $poly_results['s1Only'];
4314
                                $excess = $poly_results['s2Only'];
4315
4316
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user));
4317
                                // //this is an area in pixels
4318
                                if ($debug > 0) {
4319
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4320
                                }
4321
4322
                                if ($overlap < 1) {
4323
                                    //shortcut to avoid complicated calculations
4324
                                    $final_overlap = 0;
4325
                                    $final_missing = 100;
4326
                                    $final_excess = 100;
4327
                                } else {
4328
                                    // the final overlap is the percentage of the initial polygon
4329
                                    // that is overlapped by the user's polygon
4330
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4331
                                    if ($debug > 1) {
4332
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4333
                                    }
4334
                                    // the final missing area is the percentage of the initial polygon
4335
                                    // that is not overlapped by the user's polygon
4336
                                    $final_missing = 100 - $final_overlap;
4337
                                    if ($debug > 1) {
4338
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4339
                                    }
4340
                                    // the final excess area is the percentage of the initial polygon's size
4341
                                    // that is covered by the user's polygon outside of the initial polygon
4342
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4343
                                    if ($debug > 1) {
4344
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4345
                                    }
4346
                                }
4347
4348
                                //checking the destination parameters parsing the "@@"
4349
                                $destination_items= explode('@@', $answerDestination);
4350
                                $threadhold_total = $destination_items[0];
4351
                                $threadhold_items=explode(';',$threadhold_total);
4352
                                $threadhold1 = $threadhold_items[0]; // overlap
4353
                                $threadhold2 = $threadhold_items[1]; // excess
4354
                                $threadhold3 = $threadhold_items[2];	 //missing
4355
4356
                                // if is delineation
4357
                                if ($answerId===1) {
4358
                                    //setting colors
4359
                                    if ($final_overlap>=$threadhold1) {
4360
                                        $overlap_color=true; //echo 'a';
4361
                                    }
4362
                                    //echo $excess.'-'.$threadhold2;
4363
                                    if ($final_excess<=$threadhold2) {
4364
                                        $excess_color=true; //echo 'b';
4365
                                    }
4366
                                    //echo '--------'.$missing.'-'.$threadhold3;
4367
                                    if ($final_missing<=$threadhold3) {
4368
                                        $missing_color=true; //echo 'c';
4369
                                    }
4370
4371
                                    // if pass
4372
                                    if (
4373
                                        $final_overlap >= $threadhold1 &&
4374
                                        $final_missing <= $threadhold3 &&
4375
                                        $final_excess <= $threadhold2
4376
                                    ) {
4377
                                        $next=1; //go to the oars
4378
                                        $result_comment=get_lang('Acceptable');
4379
                                        $final_answer = 1;	// do not update with  update_exercise_attempt
4380
                                    } else {
4381
                                        $next=0;
4382
                                        $result_comment=get_lang('Unacceptable');
4383
                                        $comment=$answerDestination=$objAnswerTmp->selectComment(1);
4384
                                        $answerDestination=$objAnswerTmp->selectDestination(1);
4385
                                        //checking the destination parameters parsing the "@@"
4386
                                        $destination_items= explode('@@', $answerDestination);
4387
                                    }
4388
                                } elseif($answerId>1) {
4389
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4390
                                        if ($debug>0) {
4391
                                            error_log(__LINE__.' - answerId is of type noerror',0);
4392
                                        }
4393
                                        //type no error shouldn't be treated
4394
                                        $next = 1;
4395
                                        continue;
4396
                                    }
4397
                                    if ($debug>0) {
4398
                                        error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR',0);
4399
                                    }
4400
                                    //check the intersection between the oar and the user
4401
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4402
                                    //echo 'official';print_r($x_list);print_r($y_list);
4403
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4404
                                    $inter= $result['success'];
4405
4406
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4407
                                    $delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4408
4409
                                    $poly_answer = convert_coordinates($delineation_cord,'|');
4410
                                    $max_coord = poly_get_max($poly_user,$poly_answer);
4411
                                    $poly_answer_compiled = poly_compile($poly_answer,$max_coord);
4412
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4413
4414
                                    if ($overlap == false) {
4415
                                        //all good, no overlap
4416
                                        $next = 1;
4417
                                        continue;
4418
                                    } else {
4419
                                        if ($debug>0) {
4420
                                            error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit',0);
4421
                                        }
4422
                                        $organs_at_risk_hit++;
4423
                                        //show the feedback
4424
                                        $next=0;
4425
                                        $comment=$answerDestination=$objAnswerTmp->selectComment($answerId);
4426
                                        $answerDestination=$objAnswerTmp->selectDestination($answerId);
4427
4428
                                        $destination_items= explode('@@', $answerDestination);
4429
                                        $try_hotspot=$destination_items[1];
4430
                                        $lp_hotspot=$destination_items[2];
4431
                                        $select_question_hotspot=$destination_items[3];
4432
                                        $url_hotspot=$destination_items[4];
4433
                                    }
4434
                                }
4435
                            } else {	// the first delineation feedback
4436
                                if ($debug>0) {
4437
                                    error_log(__LINE__.' first',0);
4438
                                }
4439
                            }
4440
                        } elseif (in_array($answerType, [MATCHING, MATCHING_DRAGGABLE])) {
4441
                            echo '<tr>';
4442
                            echo Display::tag('td', $answerMatching[$answerId]);
4443
                            echo Display::tag(
4444
                                'td',
4445
                                "$user_answer / " . Display::tag(
4446
                                    'strong',
4447
                                    $answerMatching[$answerCorrect],
4448
                                    ['style' => 'color: #008000; font-weight: bold;']
4449
                                )
4450
                            );
4451
                            echo '</tr>';
4452
                        }
4453
                    }
4454
                } else {
4455
                    if ($debug) error_log('Showing questions $from '.$from);
4456
4457
                    switch ($answerType) {
4458
                        case UNIQUE_ANSWER:
4459
                        case UNIQUE_ANSWER_IMAGE:
4460
                        case UNIQUE_ANSWER_NO_OPTION:
4461
                        case MULTIPLE_ANSWER:
4462
                        case GLOBAL_MULTIPLE_ANSWER :
4463
                        case MULTIPLE_ANSWER_COMBINATION:
4464
                            if ($answerId == 1) {
4465
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4466
                                    $feedback_type,
4467
                                    $answerType,
4468
                                    $studentChoice,
4469
                                    $answer,
4470
                                    $answerComment,
4471
                                    $answerCorrect,
4472
                                    $exeId,
4473
                                    $questionId,
4474
                                    $answerId,
4475
                                    $results_disabled,
4476
                                    $showTotalScoreAndUserChoices
4477
                                );
4478
                            } else {
4479
                                ExerciseShowFunctions::display_unique_or_multiple_answer(
4480
                                    $feedback_type,
4481
                                    $answerType,
4482
                                    $studentChoice,
4483
                                    $answer,
4484
                                    $answerComment,
4485
                                    $answerCorrect,
4486
                                    $exeId,
4487
                                    $questionId,
4488
                                    '',
4489
                                    $results_disabled,
4490
                                    $showTotalScoreAndUserChoices
4491
                                );
4492
                            }
4493
                            break;
4494
                        case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
4495
                            if ($answerId == 1) {
4496
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4497
                                    $feedback_type,
4498
                                    $answerType,
4499
                                    $studentChoice,
4500
                                    $answer,
4501
                                    $answerComment,
4502
                                    $answerCorrect,
4503
                                    $exeId,
4504
                                    $questionId,
4505
                                    $answerId,
4506
                                    $results_disabled,
4507
                                    $showTotalScoreAndUserChoices
4508
                                );
4509
                            } else {
4510
                                ExerciseShowFunctions::display_multiple_answer_combination_true_false(
4511
                                    $feedback_type,
4512
                                    $answerType,
4513
                                    $studentChoice,
4514
                                    $answer,
4515
                                    $answerComment,
4516
                                    $answerCorrect,
4517
                                    $exeId,
4518
                                    $questionId,
4519
                                    '',
4520
                                    $results_disabled,
4521
                                    $showTotalScoreAndUserChoices
4522
                                );
4523
                            }
4524
                            break;
4525
                        case MULTIPLE_ANSWER_TRUE_FALSE:
4526
                            if ($answerId == 1) {
4527
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4528
                                    $feedback_type,
4529
                                    $answerType,
4530
                                    $studentChoice,
4531
                                    $answer,
4532
                                    $answerComment,
4533
                                    $answerCorrect,
4534
                                    $exeId,
4535
                                    $questionId,
4536
                                    $answerId,
4537
                                    $results_disabled,
4538
                                    $showTotalScoreAndUserChoices
4539
                                );
4540
                            } else {
4541
                                ExerciseShowFunctions::display_multiple_answer_true_false(
4542
                                    $feedback_type,
4543
                                    $answerType,
4544
                                    $studentChoice,
4545
                                    $answer,
4546
                                    $answerComment,
4547
                                    $answerCorrect,
4548
                                    $exeId,
4549
                                    $questionId,
4550
                                    '',
4551
                                    $results_disabled,
4552
                                    $showTotalScoreAndUserChoices
4553
                                );
4554
                            }
4555
                            break;
4556
                        case FILL_IN_BLANKS:
4557
                            ExerciseShowFunctions::display_fill_in_blanks_answer(
4558
                                $feedback_type,
4559
                                $answer,
4560
                                $exeId,
4561
                                $questionId,
4562
                                $results_disabled,
4563
                                $str,
4564
                                $showTotalScoreAndUserChoices
4565
                            );
4566
                            break;
4567
                        case CALCULATED_ANSWER:
4568
                            ExerciseShowFunctions::display_calculated_answer(
4569
                                $feedback_type,
4570
                                $answer,
4571
                                $exeId,
4572
                                $questionId,
4573
                                $results_disabled,
4574
                                '',
4575
                                $showTotalScoreAndUserChoices
4576
                            );
4577
                            break;
4578
                        case FREE_ANSWER:
4579
                            echo ExerciseShowFunctions::display_free_answer(
4580
                                $feedback_type,
4581
                                $choice,
4582
                                $exeId,
4583
                                $questionId,
4584
                                $questionScore,
4585
                                $results_disabled
4586
                            );
4587
                            break;
4588
                        case ORAL_EXPRESSION:
4589
                            echo '<tr>
4590
                                <td valign="top">' . ExerciseShowFunctions::display_oral_expression_answer(
4591
                                    $feedback_type,
4592
                                    $choice,
4593
                                    $exeId,
4594
                                    $questionId,
4595
                                    $nano,
4596
                                    $results_disabled
4597
                                ) . '</td>
4598
                                </tr>
4599
                                </table>';
4600
                            break;
4601
                        case HOT_SPOT:
4602
                            ExerciseShowFunctions::display_hotspot_answer(
4603
                                $feedback_type,
4604
                                $answerId,
4605
                                $answer,
4606
                                $studentChoice,
4607
                                $answerComment,
4608
                                $results_disabled
4609
                                $results_disabled,
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_VARIABLE, expecting ',' or ')'
Loading history...
4610
                                $answerId
4611
                            );
4612
                            break;
4613
                        case HOT_SPOT_DELINEATION:
4614
                            $user_answer = $user_array;
4615
                            if ($next) {
4616
                                //$tbl_track_e_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
4617
                                // Save into db
4618
                                /*	$sql = "INSERT INTO $tbl_track_e_hotspot (
4619
                                 * hotspot_user_id,
4620
                                 *  hotspot_course_code,
4621
                                 *  hotspot_exe_id,
4622
                                 *  hotspot_question_id,
4623
                                 *  hotspot_answer_id,
4624
                                 *  hotspot_correct,
4625
                                 *  hotspot_coordinate
4626
                                 *  )
4627
                                VALUES (
4628
                                 * '".Database::escape_string($_user['user_id'])."',
4629
                                 *  '".Database::escape_string($_course['id'])."',
4630
                                 *  '".Database::escape_string($exeId)."', '".Database::escape_string($questionId)."',
4631
                                 *  '".Database::escape_string($answerId)."',
4632
                                 *  '".Database::escape_string($studentChoice)."',
4633
                                 *  '".Database::escape_string($user_array)."')";
4634
                                $result = Database::query($sql,__FILE__,__LINE__);
4635
                                 */
4636
                                $user_answer = $user_array;
4637
4638
                                // we compare only the delineation not the other points
4639
                                $answer_question = $_SESSION['hotspot_coord'][1];
4640
                                $answerDestination = $_SESSION['hotspot_dest'][1];
4641
4642
                                //calculating the area
4643
                                $poly_user = convert_coordinates($user_answer, '/');
4644
                                $poly_answer = convert_coordinates($answer_question, '|');
4645
4646
                                $max_coord = poly_get_max($poly_user, $poly_answer);
4647
                                $poly_user_compiled = poly_compile($poly_user, $max_coord);
4648
                                $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4649
                                $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord);
4650
4651
                                $overlap = $poly_results['both'];
4652
                                $poly_answer_area = $poly_results['s1'];
4653
                                $poly_user_area = $poly_results['s2'];
4654
                                $missing = $poly_results['s1Only'];
4655
                                $excess = $poly_results['s2Only'];
4656
4657
                                //$overlap = round(polygons_overlap($poly_answer,$poly_user)); //this is an area in pixels
4658
                                if ($debug > 0) {
4659
                                    error_log(__LINE__ . ' - Polygons results are ' . print_r($poly_results, 1), 0);
4660
                                }
4661
                                if ($overlap < 1) {
4662
                                    //shortcut to avoid complicated calculations
4663
                                    $final_overlap = 0;
4664
                                    $final_missing = 100;
4665
                                    $final_excess = 100;
4666
                                } else {
4667
                                    // the final overlap is the percentage of the initial polygon that is overlapped by the user's polygon
4668
                                    $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100);
4669
                                    if ($debug > 1) {
4670
                                        error_log(__LINE__ . ' - Final overlap is ' . $final_overlap, 0);
4671
                                    }
4672
                                    // the final missing area is the percentage of the initial polygon that is not overlapped by the user's polygon
4673
                                    $final_missing = 100 - $final_overlap;
4674
                                    if ($debug > 1) {
4675
                                        error_log(__LINE__ . ' - Final missing is ' . $final_missing, 0);
4676
                                    }
4677
                                    // the final excess area is the percentage of the initial polygon's size that is covered by the user's polygon outside of the initial polygon
4678
                                    $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100);
4679
                                    if ($debug > 1) {
4680
                                        error_log(__LINE__ . ' - Final excess is ' . $final_excess, 0);
4681
                                    }
4682
                                }
4683
4684
                                //checking the destination parameters parsing the "@@"
4685
                                $destination_items = explode('@@', $answerDestination);
4686
                                $threadhold_total = $destination_items[0];
4687
                                $threadhold_items = explode(';', $threadhold_total);
4688
                                $threadhold1 = $threadhold_items[0]; // overlap
4689
                                $threadhold2 = $threadhold_items[1]; // excess
4690
                                $threadhold3 = $threadhold_items[2];  //missing
4691
                                // if is delineation
4692
                                if ($answerId === 1) {
4693
                                    //setting colors
4694
                                    if ($final_overlap >= $threadhold1) {
4695
                                        $overlap_color = true; //echo 'a';
4696
                                    }
4697
                                    //echo $excess.'-'.$threadhold2;
4698
                                    if ($final_excess <= $threadhold2) {
4699
                                        $excess_color = true; //echo 'b';
4700
                                    }
4701
                                    //echo '--------'.$missing.'-'.$threadhold3;
4702
                                    if ($final_missing <= $threadhold3) {
4703
                                        $missing_color = true; //echo 'c';
4704
                                    }
4705
4706
                                    // if pass
4707
                                    if ($final_overlap >= $threadhold1 && $final_missing <= $threadhold3 && $final_excess <= $threadhold2) {
4708
                                        $next = 1; //go to the oars
4709
                                        $result_comment = get_lang('Acceptable');
4710
                                        $final_answer = 1; // do not update with  update_exercise_attempt
4711
                                    } else {
4712
                                        $next = 0;
4713
                                        $result_comment = get_lang('Unacceptable');
4714
                                        $comment = $answerDestination = $objAnswerTmp->selectComment(1);
4715
                                        $answerDestination = $objAnswerTmp->selectDestination(1);
4716
                                        //checking the destination parameters parsing the "@@"
4717
                                        $destination_items = explode('@@', $answerDestination);
4718
                                    }
4719
                                } elseif ($answerId > 1) {
4720
                                    if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') {
4721
                                        if ($debug > 0) {
4722
                                            error_log(__LINE__ . ' - answerId is of type noerror', 0);
4723
                                        }
4724
                                        //type no error shouldn't be treated
4725
                                        $next = 1;
4726
                                        continue;
4727
                                    }
4728
                                    if ($debug > 0) {
4729
                                        error_log(__LINE__ . ' - answerId is >1 so we\'re probably in OAR', 0);
4730
                                    }
4731
                                    //check the intersection between the oar and the user
4732
                                    //echo 'user';	print_r($x_user_list);		print_r($y_user_list);
4733
                                    //echo 'official';print_r($x_list);print_r($y_list);
4734
                                    //$result = get_intersection_data($x_list,$y_list,$x_user_list,$y_user_list);
4735
                                    $inter = $result['success'];
4736
4737
                                    //$delineation_cord=$objAnswerTmp->selectHotspotCoordinates($answerId);
4738
                                    $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId);
4739
4740
                                    $poly_answer = convert_coordinates($delineation_cord, '|');
4741
                                    $max_coord = poly_get_max($poly_user, $poly_answer);
4742
                                    $poly_answer_compiled = poly_compile($poly_answer, $max_coord);
4743
                                    $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled,$max_coord);
4744
4745
                                    if ($overlap == false) {
4746
                                        //all good, no overlap
4747
                                        $next = 1;
4748
                                        continue;
4749
                                    } else {
4750
                                        if ($debug > 0) {
4751
                                            error_log(__LINE__ . ' - Overlap is ' . $overlap . ': OAR hit', 0);
4752
                                        }
4753
                                        $organs_at_risk_hit++;
4754
                                        //show the feedback
4755
                                        $next = 0;
4756
                                        $comment = $answerDestination = $objAnswerTmp->selectComment($answerId);
4757
                                        $answerDestination = $objAnswerTmp->selectDestination($answerId);
4758
4759
                                        $destination_items = explode('@@', $answerDestination);
4760
                                        $try_hotspot = $destination_items[1];
4761
                                        $lp_hotspot = $destination_items[2];
4762
                                        $select_question_hotspot = $destination_items[3];
4763
                                        $url_hotspot=$destination_items[4];
4764
                                    }
4765
                                }
4766
                            } else {	// the first delineation feedback
4767
                                if ($debug > 0) {
4768
                                    error_log(__LINE__ . ' first', 0);
4769
                                }
4770
                            }
4771
                            break;
4772
                        case HOT_SPOT_ORDER:
4773
                            ExerciseShowFunctions::display_hotspot_order_answer(
4774
                                $feedback_type,
4775
                                $answerId,
4776
                                $answer,
4777
                                $studentChoice,
4778
                                $answerComment
4779
                            );
4780
                            break;
4781
                        case DRAGGABLE:
4782
                            //no break
4783
                        case MATCHING_DRAGGABLE:
4784
                            //no break
4785
                        case MATCHING:
4786
                            echo '<tr>';
4787
                            echo Display::tag('td', $answerMatching[$answerId]);
4788
                            echo Display::tag(
4789
                                'td',
4790
                                "$user_answer / " . Display::tag(
4791
                                    'strong',
4792
                                    $answerMatching[$answerCorrect],
4793
                                    ['style' => 'color: #008000; font-weight: bold;']
4794
                                )
4795
                            );
4796
                            echo '</tr>';
4797
4798
                            break;
4799
                    }
4800
                }
4801
            }
4802
            if ($debug) error_log(' ------ ');
4803
        } // end for that loops over all answers of the current question
4804
4805
        if ($debug) error_log('-- end answer loop --');
4806
4807
        $final_answer = true;
4808
4809
        foreach ($real_answers as $my_answer) {
4810
            if (!$my_answer) {
4811
                $final_answer = false;
4812
            }
4813
        }
4814
4815
        //we add the total score after dealing with the answers
4816
        if ($answerType == MULTIPLE_ANSWER_COMBINATION ||
4817
            $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE
4818
        ) {
4819
            if ($final_answer) {
4820
                //getting only the first score where we save the weight of all the question
4821
                $answerWeighting = $objAnswerTmp->selectWeighting(1);
4822
                $questionScore += $answerWeighting;
4823
                $totalScore += $answerWeighting;
4824
            }
4825
        }
4826
4827
        //Fixes multiple answer question in order to be exact
4828
        //if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
4829
       /* if ($answerType == GLOBAL_MULTIPLE_ANSWER) {
4830
            $diff = @array_diff($answer_correct_array, $real_answers);
4831
4832
            // All good answers or nothing works like exact
4833
4834
            $counter = 1;
4835
            $correct_answer = true;
4836
            foreach ($real_answers as $my_answer) {
4837
                if ($debug)
4838
                    error_log(" my_answer: $my_answer answer_correct_array[counter]: ".$answer_correct_array[$counter]);
4839
                if ($my_answer != $answer_correct_array[$counter]) {
4840
                    $correct_answer = false;
4841
                    break;
4842
                }
4843
                $counter++;
4844
            }
4845
4846
            if ($debug) error_log(" answer_correct_array: ".print_r($answer_correct_array, 1)."");
4847
            if ($debug) error_log(" real_answers: ".print_r($real_answers, 1)."");
4848
            if ($debug) error_log(" correct_answer: ".$correct_answer);
4849
4850
            if ($correct_answer == false) {
4851
                $questionScore = 0;
4852
            }
4853
4854
            // This makes the result non exact
4855
            if (!empty($diff)) {
4856
                $questionScore = 0;
4857
            }
4858
        }*/
4859
4860
        $extra_data = array(
4861
            'final_overlap' => $final_overlap,
4862
            'final_missing'=>$final_missing,
4863
            'final_excess'=> $final_excess,
4864
            'overlap_color' => $overlap_color,
4865
            'missing_color'=>$missing_color,
4866
            'excess_color'=> $excess_color,
4867
            'threadhold1'   => $threadhold1,
4868
            'threadhold2'=>$threadhold2,
4869
            'threadhold3'=> $threadhold3,
4870
        );
4871
        if ($from == 'exercise_result') {
4872
            // if answer is hotspot. To the difference of exercise_show.php,
4873
            //  we use the results from the session (from_db=0)
4874
            // TODO Change this, because it is wrong to show the user
4875
            //  some results that haven't been stored in the database yet
4876
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER || $answerType == HOT_SPOT_DELINEATION ) {
4877
4878
                if ($debug) error_log('$from AND this is a hotspot kind of question ');
4879
4880
                $my_exe_id = 0;
4881
                $from_database = 0;
4882
                if ($answerType == HOT_SPOT_DELINEATION) {
4883
                    if (0) {
4884
                        if ($overlap_color) {
4885
                            $overlap_color='green';
4886
                        } else {
4887
                            $overlap_color='red';
4888
                        }
4889
                        if ($missing_color) {
4890
                            $missing_color='green';
4891
                        } else {
4892
                            $missing_color='red';
4893
                        }
4894
                        if ($excess_color) {
4895
                            $excess_color='green';
4896
                        } else {
4897
                            $excess_color='red';
4898
                        }
4899
                        if (!is_numeric($final_overlap)) {
4900
                            $final_overlap = 0;
4901
                        }
4902
                        if (!is_numeric($final_missing)) {
4903
                            $final_missing = 0;
4904
                        }
4905
                        if (!is_numeric($final_excess)) {
4906
                            $final_excess = 0;
4907
                        }
4908
4909
                        if ($final_overlap>100) {
4910
                            $final_overlap = 100;
4911
                        }
4912
4913
                        $table_resume='<table class="data_table">
4914
                                <tr class="row_odd" >
4915
                                    <td></td>
4916
                                    <td ><b>' . get_lang('Requirements') . '</b></td>
4917
                                    <td><b>' . get_lang('YourAnswer') . '</b></td>
4918
                                </tr>
4919
                                <tr class="row_even">
4920
                                    <td><b>' . get_lang('Overlap') . '</b></td>
4921
                                    <td>' . get_lang('Min') . ' ' . $threadhold1 . '</td>
4922
                                    <td><div style="color:' . $overlap_color . '">'
4923
                                        . (($final_overlap < 0) ? 0 : intval($final_overlap)) . '</div></td>
4924
                                </tr>
4925
                                <tr>
4926
                                    <td><b>' . get_lang('Excess') . '</b></td>
4927
                                    <td>' . get_lang('Max') . ' ' . $threadhold2 . '</td>
4928
                                    <td><div style="color:' . $excess_color . '">'
4929
                                        . (($final_excess < 0) ? 0 : intval($final_excess)) . '</div></td>
4930
                                </tr>
4931
                                <tr class="row_even">
4932
                                    <td><b>' . get_lang('Missing') . '</b></td>
4933
                                    <td>' . get_lang('Max') . ' ' . $threadhold3 . '</td>
4934
                                    <td><div style="color:' . $missing_color . '">'
4935
                                        . (($final_missing < 0) ? 0 : intval($final_missing)) . '</div></td>
4936
                                </tr>
4937
                            </table>';
4938
                        if ($next == 0) {
4939
                            $try = $try_hotspot;
4940
                            $lp = $lp_hotspot;
4941
                            $destinationid = $select_question_hotspot;
4942
                            $url = $url_hotspot;
4943
                        } else {
4944
                            //show if no error
4945
                            //echo 'no error';
4946
                            $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers);
4947
                            $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers);
4948
                        }
4949
4950
                        echo '<h1><div style="color:#333;">' . get_lang('Feedback') . '</div></h1>
4951
                            <p style="text-align:center">';
4952
4953
                        $message = '<p>' . get_lang('YourDelineation') . '</p>';
4954
                        $message .= $table_resume;
4955
                        $message .= '<br />' . get_lang('ResultIs') . ' ' . $result_comment . '<br />';
4956
                        if ($organs_at_risk_hit > 0) {
4957
                            $message .= '<p><b>' . get_lang('OARHit') . '</b></p>';
4958
                        }
4959
                        $message .='<p>' . $comment . '</p>';
4960
                        echo $message;
4961
                    } else {
4962
                        echo $hotspot_delineation_result[0]; //prints message
4963
                        $from_database = 1;  // the hotspot_solution.swf needs this variable
4964
                    }
4965
4966
                    //save the score attempts
4967
4968
                    if (1) {
4969
                        //getting the answer 1 or 0 comes from exercise_submit_modal.php
4970
                        $final_answer = $hotspot_delineation_result[1];
4971
                        if ($final_answer == 0) {
4972
                            $questionScore = 0;
4973
                        }
4974
                        // we always insert the answer_id 1 = delineation
4975
                        Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0);
4976
                        //in delineation mode, get the answer from $hotspot_delineation_result[1]
4977
                        Event::saveExerciseAttemptHotspot(
4978
                            $exeId,
4979
                            $quesId,
4980
                            1,
4981
                            $hotspot_delineation_result[1],
4982
                            $exerciseResultCoordinates[$quesId]
4983
                        );
4984
                    } else {
4985
                        if ($final_answer==0) {
4986
                            $questionScore = 0;
4987
                            $answer=0;
4988
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
4989
                            if (is_array($exerciseResultCoordinates[$quesId])) {
4990
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
4991
                                    Event::saveExerciseAttemptHotspot($exeId,$quesId,$idx,0,$val);
4992
                                }
4993
                            }
4994
                        } else {
4995
                            Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0);
4996
                            if (is_array($exerciseResultCoordinates[$quesId])) {
4997
                                foreach($exerciseResultCoordinates[$quesId] as $idx => $val) {
4998
                                    Event::saveExerciseAttemptHotspot($exeId,$quesId,$idx,$choice[$idx],$val);
4999
                                }
5000
                            }
5001
                        }
5002
                    }
5003
                    $my_exe_id = $exeId;
5004
                }
5005
            }
5006
5007
            if ($answerType == HOT_SPOT || $answerType == HOT_SPOT_ORDER) {
5008
                // We made an extra table for the answers
5009
5010
                if ($show_result) {
5011
                    $relPath = api_get_path(REL_PATH);
5012
                    //	if ($origin != 'learnpath') {
5013
                    echo '</table></td></tr>';
5014
                    echo "
5015
                        <tr>
5016
                            <td colspan=\"2\">
5017
                                <p><em>" . get_lang('HotSpot') . "</em></p>
5018
5019
                                <div id=\"hotspot-solution-$questionId\"></div>
5020
5021
                                <script>
5022
                                    $(document).on('ready', function () {
5023
                                        new HotspotQuestion({
5024
                                            questionId: $questionId,
5025
                                            exerciseId: $exeId,
5026
                                            selector: '#hotspot-solution-$questionId',
5027
                                            for: 'solution',
5028
                                            relPath: '$relPath'
5029
                                        });
5030
                                    });
5031
5032
                                </script>
5033
                            </td>
5034
                        </tr>
5035
                    ";
5036
                    //	}
5037
                }
5038
            }
5039
5040
            //if ($origin != 'learnpath') {
5041
            if ($show_result) {
5042
                echo '</table>';
5043
            }
5044
            //	}
5045
        }
5046
        unset ($objAnswerTmp);
5047
5048
        $totalWeighting += $questionWeighting;
5049
        // Store results directly in the database
5050
        // For all in one page exercises, the results will be
5051
        // stored by exercise_results.php (using the session)
5052
5053
        if ($saved_results) {
5054
            if ($debug) error_log("Save question results $saved_results");
5055
            if ($debug) error_log(print_r($choice ,1 ));
5056
5057
            if (empty($choice)) {
5058
                $choice = 0;
5059
            }
5060
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
5061
                if ($choice != 0) {
5062
                    $reply = array_keys($choice);
5063
                    for ($i = 0; $i < sizeof($reply); $i++) {
5064
                        $ans = $reply[$i];
5065
                        Event::saveQuestionAttempt(
5066
                            $questionScore,
5067
                            $ans . ':' . $choice[$ans],
5068
                            $quesId,
5069
                            $exeId,
5070
                            $i,
5071
                            $this->id
5072
                        );
5073
                        if ($debug) {
5074
                            error_log('result =>' . $questionScore . ' ' . $ans . ':' . $choice[$ans]);
5075
                        }
5076
                    }
5077
                } else {
5078
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5079
                }
5080
            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
5081
                if ($choice != 0) {
5082
                    $reply = array_keys($choice);
5083
5084
                    if ($debug) {
5085
                        error_log("reply " . print_r($reply, 1) . "");
5086
                    }
5087
                    for ($i = 0; $i < sizeof($reply); $i++) {
5088
                        $ans = $reply[$i];
5089
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5090
                    }
5091
                } else {
5092
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5093
                }
5094
            } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
5095
                if ($choice != 0) {
5096
                    $reply = array_keys($choice);
5097
                    for ($i = 0; $i < sizeof($reply); $i++) {
5098
                        $ans = $reply[$i];
5099
                        Event::saveQuestionAttempt($questionScore, $ans, $quesId, $exeId, $i, $this->id);
5100
                    }
5101
                } else {
5102
                    Event::saveQuestionAttempt($questionScore, 0, $quesId, $exeId, 0, $this->id);
5103
                }
5104
            } elseif (in_array($answerType, [MATCHING, DRAGGABLE, MATCHING_DRAGGABLE])) {
5105
                if (isset($matching)) {
5106
                    foreach ($matching as $j => $val) {
5107
                        Event::saveQuestionAttempt($questionScore, $val, $quesId, $exeId, $j, $this->id);
5108
                    }
5109
                }
5110
            } elseif ($answerType == FREE_ANSWER) {
5111
                $answer = $choice;
5112
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5113
            } elseif ($answerType == ORAL_EXPRESSION) {
5114
                $answer = $choice;
5115
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id, $nano);
5116
            } elseif (in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION])) {
5117
                $answer = $choice;
5118
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->id);
5119
                //            } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
5120
            } elseif ($answerType == HOT_SPOT) {
5121
                $answer = [];
5122
5123
                if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) {
5124
                    Database::delete(
5125
                        Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT),
5126
                        [
5127
                            'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [
5128
                                $exeId,
5129
                                $questionId,
5130
                                api_get_course_int_id()
5131
                            ]
5132
                        ]
5133
                    );
5134
5135
                    foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) {
5136
                        $answer[] = $val;
5137
5138
                        Event::saveExerciseAttemptHotspot($exeId, $quesId, $idx, $choice[$idx], $val, false, $this->id);
5139
                    }
5140
                }
5141
5142
                Event::saveQuestionAttempt($questionScore, implode('|', $answer), $quesId, $exeId, 0, $this->id);
5143
            } else {
5144
                Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0,$this->id);
5145
            }
5146
        }
5147
5148
        if ($propagate_neg == 0 && $questionScore < 0) {
5149
            $questionScore = 0;
5150
        }
5151
5152
        if ($saved_results) {
5153
            $stat_table = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5154
            $sql = 'UPDATE ' . $stat_table . ' SET
5155
                        exe_result = exe_result + ' . floatval($questionScore) . '
5156
                    WHERE exe_id = ' . $exeId;
5157
            if ($debug) error_log($sql);
5158
            Database::query($sql);
5159
        }
5160
5161
        $return_array = array(
5162
            'score'         => $questionScore,
5163
            'weight'        => $questionWeighting,
5164
            'extra'         => $extra_data,
5165
            'open_question' => $arrques,
5166
            'open_answer'   => $arrans,
5167
            'answer_type'   => $answerType
5168
        );
5169
5170
        return $return_array;
5171
    }
5172
5173
    /**
5174
     * Sends a notification when a user ends an examn
5175
     *
5176
     */
5177
    public function send_mail_notification_for_exam($question_list_answers, $origin, $exe_id)
5178
    {
5179
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5180
            return null;
5181
        }
5182
        // Email configuration settings
5183
        $courseCode = api_get_course_id();
5184
        $courseInfo = api_get_course_info($courseCode);
5185
        $sessionId = api_get_session_id();
5186
5187
        if (empty($courseInfo)) {
5188
            return false;
5189
        }
5190
5191
        $url_email = api_get_path(WEB_CODE_PATH)
5192
            . 'exercice/exercise_show.php?'
5193
            . api_get_cidreq()
5194
            . '&id_session='
5195
            . $sessionId
5196
            . '&id='
5197
            . $exe_id
5198
            . '&action=qualify';
5199
        $user_info = api_get_user_info(api_get_user_id());
5200
5201
        $msg = get_lang('ExerciseAttempted').'<br /><br />'
5202
                    .get_lang('AttemptDetails').' : <br /><br />'.
5203
                    '<table>'
5204
                        .'<tr>'
5205
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5206
                            .'<td>&nbsp;<b>#course#</b></td>'
5207
                        .'</tr>'
5208
                        .'<tr>'
5209
                            .'<td>'.get_lang('TestAttempted').'</td>'
5210
                            .'<td>&nbsp;#exercise#</td>'
5211
                        .'</tr>'
5212
                        .'<tr>'
5213
                            .'<td>'.get_lang('StudentName').'</td>'
5214
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5215
                        .'</tr>'
5216
                        .'<tr>'
5217
                            .'<td>'.get_lang('StudentEmail').'</td>'
5218
                            .'<td>&nbsp;#email#</td>'
5219
                        .'</tr>'
5220
                    .'</table>';
5221
        $open_question_list = null;
5222
5223
        $msg = str_replace("#email#", $user_info['email'], $msg);
5224
        $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5225
        $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5226
        $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5227
        $msg = str_replace("#course#", $courseInfo['name'], $msg1);
5228
5229
        if ($origin != 'learnpath') {
5230
            $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5231
        }
5232
        $msg1 = str_replace("#url#", $url_email, $msg);
5233
        $mail_content = $msg1;
5234
        $subject = get_lang('ExerciseAttempted');
5235
5236
        if (!empty($sessionId)) {
5237
            $teachers = CourseManager::get_coach_list_from_course_code($courseCode, $sessionId);
5238
        } else {
5239
            $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5240
        }
5241
5242
        if (!empty($teachers)) {
5243
            foreach ($teachers as $user_id => $teacher_data) {
5244
                MessageManager::send_message_simple(
5245
                    $user_id,
5246
                    $subject,
5247
                    $mail_content
5248
                );
5249
            }
5250
        }
5251
    }
5252
5253
    /**
5254
     * Sends a notification when a user ends an examn
5255
     *
5256
     */
5257
    function send_notification_for_open_questions($question_list_answers, $origin, $exe_id)
5258
    {
5259
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5260
            return null;
5261
        }
5262
        // Email configuration settings
5263
        $courseCode     = api_get_course_id();
5264
        $course_info    = api_get_course_info($courseCode);
5265
5266
        $url_email = api_get_path(WEB_CODE_PATH)
5267
            . 'exercice/exercise_show.php?'
5268
            . api_get_cidreq()
5269
            . '&id_session='
5270
            . api_get_session_id()
5271
            . '&id='
5272
            . $exe_id
5273
            . '&action=qualify';
5274
        $user_info = api_get_user_info(api_get_user_id());
5275
5276
        $msg = get_lang('OpenQuestionsAttempted').'<br /><br />'
5277
                    .get_lang('AttemptDetails').' : <br /><br />'
5278
                    .'<table>'
5279
                        .'<tr>'
5280
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5281
                            .'<td>&nbsp;<b>#course#</b></td>'
5282
                        .'</tr>'
5283
                        .'<tr>'
5284
                            .'<td>'.get_lang('TestAttempted').'</td>'
5285
                            .'<td>&nbsp;#exercise#</td>'
5286
                        .'</tr>'
5287
                        .'<tr>'
5288
                            .'<td>'.get_lang('StudentName').'</td>'
5289
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5290
                        .'</tr>'
5291
                        .'<tr>'
5292
                            .'<td>'.get_lang('StudentEmail').'</td>'
5293
                            .'<td>&nbsp;#mail#</td>'
5294
                        .'</tr>'
5295
                    .'</table>';
5296
        $open_question_list = null;
5297
        foreach ($question_list_answers as $item) {
5298
            $question    = $item['question'];
5299
            $answer      = $item['answer'];
5300
            $answer_type = $item['answer_type'];
5301
5302
            if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) {
5303
                $open_question_list .=
5304
                    '<tr>'
5305
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5306
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5307
                    .'</tr>'
5308
                    .'<tr>'
5309
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5310
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5311
                    .'</tr>';
5312
            }
5313
        }
5314
5315
        if (!empty($open_question_list)) {
5316
            $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'.
5317
                    '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">';
5318
            $msg .= $open_question_list;
5319
            $msg .= '</table><br />';
5320
5321
5322
            $msg1   = str_replace("#exercise#",    $this->exercise, $msg);
5323
            $msg    = str_replace("#firstName#",   $user_info['firstname'],$msg1);
5324
            $msg1   = str_replace("#lastName#",    $user_info['lastname'],$msg);
5325
            $msg    = str_replace("#mail#",        $user_info['email'],$msg1);
5326
            $msg    = str_replace("#course#",      $course_info['name'],$msg1);
5327
5328
            if ($origin != 'learnpath') {
5329
                $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5330
            }
5331
            $msg1 = str_replace("#url#", $url_email, $msg);
5332
            $mail_content = $msg1;
5333
            $subject = get_lang('OpenQuestionsAttempted');
5334
5335
            if (api_get_session_id()) {
5336
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5337
            } else {
5338
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5339
            }
5340
5341
            if (!empty($teachers)) {
5342
                foreach ($teachers as $user_id => $teacher_data) {
5343
                    MessageManager::send_message_simple(
5344
                        $user_id,
5345
                        $subject,
5346
                        $mail_content
5347
                    );
5348
                }
5349
            }
5350
        }
5351
    }
5352
5353
    function send_notification_for_oral_questions($question_list_answers, $origin, $exe_id)
5354
    {
5355
        if (api_get_course_setting('email_alert_manager_on_new_quiz') != 1 ) {
5356
            return null;
5357
        }
5358
        // Email configuration settings
5359
        $courseCode     = api_get_course_id();
5360
        $course_info    = api_get_course_info($courseCode);
5361
5362
        $url_email = api_get_path(WEB_CODE_PATH)
5363
            . 'exercice/exercise_show.php?'
5364
            . api_get_cidreq()
5365
            . '&id_session='
5366
            . api_get_session_id()
5367
            . '&id='
5368
            . $exe_id
5369
            . '&action=qualify';
5370
        $user_info = api_get_user_info(api_get_user_id());
5371
5372
        $oral_question_list = null;
5373
        foreach ($question_list_answers as $item) {
5374
            $question    = $item['question'];
5375
            $answer      = $item['answer'];
5376
            $answer_type = $item['answer_type'];
5377
5378
            if (!empty($question) && !empty($answer) && $answer_type == ORAL_EXPRESSION) {
5379
                $oral_question_list.='<br /><table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'
5380
                    .'<tr>'
5381
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Question').'</td>'
5382
                        .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>'
5383
                    .'</tr>'
5384
                    .'<tr>'
5385
                        .'<td width="220" valign="top" bgcolor="#E5EDF8">&nbsp;&nbsp;'.get_lang('Answer').'</td>'
5386
                        .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>'
5387
                    .'</tr></table>';
5388
            }
5389
        }
5390
5391
        if (!empty($oral_question_list)) {
5392
            $msg = get_lang('OralQuestionsAttempted').'<br /><br />
5393
                    '.get_lang('AttemptDetails').' : <br /><br />'
5394
                    .'<table>'
5395
                        .'<tr>'
5396
                            .'<td><em>'.get_lang('CourseName').'</em></td>'
5397
                            .'<td>&nbsp;<b>#course#</b></td>'
5398
                        .'</tr>'
5399
                        .'<tr>'
5400
                            .'<td>'.get_lang('TestAttempted').'</td>'
5401
                            .'<td>&nbsp;#exercise#</td>'
5402
                        .'</tr>'
5403
                        .'<tr>'
5404
                            .'<td>'.get_lang('StudentName').'</td>'
5405
                            .'<td>&nbsp;#firstName# #lastName#</td>'
5406
                        .'</tr>'
5407
                        .'<tr>'
5408
                            .'<td>'.get_lang('StudentEmail').'</td>'
5409
                            .'<td>&nbsp;#mail#</td>'
5410
                        .'</tr>'
5411
                    .'</table>';
5412
            $msg .=  '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'),$oral_question_list).'<br />';
5413
            $msg1 = str_replace("#exercise#", $this->exercise, $msg);
5414
            $msg = str_replace("#firstName#", $user_info['firstname'], $msg1);
5415
            $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg);
5416
            $msg = str_replace("#mail#", $user_info['email'], $msg1);
5417
            $msg = str_replace("#course#", $course_info['name'], $msg1);
5418
5419
            if ($origin != 'learnpath') {
5420
                $msg.= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>';
5421
            }
5422
            $msg1 = str_replace("#url#", $url_email, $msg);
5423
            $mail_content = $msg1;
5424
            $subject = get_lang('OralQuestionsAttempted');
5425
5426
            if (api_get_session_id()) {
5427
                $teachers = CourseManager::get_coach_list_from_course_code($courseCode, api_get_session_id());
5428
            } else {
5429
                $teachers = CourseManager::get_teacher_list_from_course_code($courseCode);
5430
            }
5431
5432
            if (!empty($teachers)) {
5433
                foreach ($teachers as $user_id => $teacher_data) {
5434
                    MessageManager::send_message_simple(
5435
                        $user_id,
5436
                        $subject,
5437
                        $mail_content
5438
                    );
5439
                }
5440
            }
5441
        }
5442
    }
5443
5444
    /**
5445
     * @param array $user_data result of api_get_user_info()
5446
     * @param null $start_date
5447
     * @param null $duration
5448
     * @param string $ip Optional. The user IP
5449
     * @return string
5450
     */
5451
    public function show_exercise_result_header($user_data, $start_date = null, $duration = null, $ip = null)
5452
    {
5453
        $array = array();
5454
5455
        if (!empty($user_data)) {
5456
            $array[] = array('title' => get_lang('Name'), 'content' => $user_data['complete_name']);
5457
            $array[] = array('title' => get_lang('Username'), 'content' => $user_data['username']);
5458
            if (!empty($user_data['official_code'])) {
5459
                $array[] = array(
5460
                    'title' => get_lang('OfficialCode'),
5461
                    'content' => $user_data['official_code']
5462
                );
5463
            }
5464
        }
5465
        // Description can be very long and is generally meant to explain
5466
        //   rules *before* the exam. Leaving here to make display easier if
5467
        //   necessary
5468
        /*
5469
        if (!empty($this->description)) {
5470
            $array[] = array('title' => get_lang("Description"), 'content' => $this->description);
5471
        }
5472
        */
5473
        if (!empty($start_date)) {
5474
            $array[] = array('title' => get_lang('StartDate'), 'content' => $start_date);
5475
        }
5476
5477
        if (!empty($duration)) {
5478
            $array[] = array('title' => get_lang('Duration'), 'content' => $duration);
5479
        }
5480
5481
        if (!empty($ip)) {
5482
            $array[] = array('title' => get_lang('IP'), 'content' => $ip);
5483
        }
5484
        $html  = '<div class="question-result">';
5485
        $html .= Display::page_header(
5486
            Display::return_icon('test-quiz.png', get_lang('Result'),null, ICON_SIZE_MEDIUM).' '.$this->exercise.' : '.get_lang('Result')
5487
        );
5488
        $html .= Display::description($array);
5489
        $html .="</div>";
5490
        return $html;
5491
    }
5492
5493
    /**
5494
     * Create a quiz from quiz data
5495
     * @param string  Title
5496
     * @param int     Time before it expires (in minutes)
5497
     * @param int     Type of exercise
5498
     * @param int     Whether it's randomly picked questions (1) or not (0)
5499
     * @param int     Whether the exercise is visible to the user (1) or not (0)
5500
     * @param int     Whether the results are show to the user (0) or not (1)
5501
     * @param int     Maximum number of attempts (0 if no limit)
5502
     * @param int     Feedback type
5503
     * @todo this was function was added due the import exercise via CSV
5504
     * @return    int New exercise ID
5505
     */
5506
    public function createExercise(
5507
        $title,
5508
        $expired_time = 0,
5509
        $type = 2,
5510
        $random = 0,
5511
        $active = 1,
5512
        $results_disabled = 0,
5513
        $max_attempt = 0,
5514
        $feedback = 3,
5515
        $propagateNegative = 0
5516
    ) {
5517
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
5518
        $type = intval($type);
5519
        $random = intval($random);
5520
        $active = intval($active);
5521
        $results_disabled = intval($results_disabled);
5522
        $max_attempt = intval($max_attempt);
5523
        $feedback = intval($feedback);
5524
        $expired_time = intval($expired_time);
5525
        $title = Database::escape_string($title);
5526
        $propagateNegative = intval($propagateNegative);
5527
        $sessionId = api_get_session_id();
5528
        $course_id = api_get_course_int_id();
5529
        // Save a new quiz
5530
        $sql = "INSERT INTO $tbl_quiz (
5531
                c_id,
5532
                title,
5533
                type,
5534
                random,
5535
                active,
5536
                results_disabled,
5537
                max_attempt,
5538
                start_time,
5539
                end_time,
5540
                feedback_type,
5541
                expired_time,
5542
                session_id,
5543
                propagate_neg
5544
            )
5545
            VALUES (
5546
                '$course_id',
5547
                '$title',
5548
                $type,
5549
                $random,
5550
                $active,
5551
                $results_disabled,
5552
                $max_attempt,
5553
                '',
5554
                '',
5555
                $feedback,
5556
                $expired_time,
5557
                $sessionId,
5558
                $propagateNegative
5559
            )";
5560
        Database::query($sql);
5561
        $quiz_id = Database::insert_id();
5562
5563
        if ($quiz_id) {
5564
5565
            $sql = "UPDATE $tbl_quiz SET id = iid WHERE iid = {$quiz_id} ";
5566
            Database::query($sql);
5567
        }
5568
5569
        return $quiz_id;
5570
    }
5571
5572
    function process_geometry()
5573
    {
5574
5575
    }
5576
5577
    /**
5578
     * Returns the exercise result
5579
     * @param 	int		attempt id
5580
     * @return 	float 	exercise result
5581
     */
5582
    public function get_exercise_result($exe_id)
5583
    {
5584
        $result = array();
5585
        $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id);
5586
5587
        if (!empty($track_exercise_info)) {
5588
            $totalScore = 0;
5589
            $objExercise = new Exercise();
5590
            $objExercise->read($track_exercise_info['exe_exo_id']);
5591
            if (!empty($track_exercise_info['data_tracking'])) {
5592
                $question_list = explode(',', $track_exercise_info['data_tracking']);
5593
            }
5594
            foreach ($question_list as $questionId) {
5595
                $question_result = $objExercise->manage_answer(
5596
                    $exe_id,
5597
                    $questionId,
5598
                    '',
5599
                    'exercise_show',
5600
                    array(),
5601
                    false,
5602
                    true,
5603
                    false,
5604
                    $objExercise->selectPropagateNeg()
5605
                );
5606
                $totalScore      += $question_result['score'];
5607
            }
5608
5609
            if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) {
5610
                $totalScore = 0;
5611
            }
5612
            $result = array(
5613
                'score' => $totalScore,
5614
                'weight' => $track_exercise_info['exe_weighting']
5615
            );
5616
        }
5617
        return $result;
5618
    }
5619
5620
    /**
5621
     * Checks if the exercise is visible due a lot of conditions
5622
     * visibility, time limits, student attempts
5623
     * Return associative array
5624
     * value : true if execise visible
5625
     * message : HTML formated message
5626
     * rawMessage : text message
5627
     * @param int $lpId
5628
     * @param int $lpItemId
5629
     * @param int $lpItemViewId
5630
     * @param bool $filterByAdmin
5631
     * @return array
5632
     */
5633
    public function is_visible(
5634
        $lpId = 0,
5635
        $lpItemId = 0,
5636
        $lpItemViewId = 0,
5637
        $filterByAdmin = true
5638
    ) {
5639
        // 1. By default the exercise is visible
5640
        $isVisible = true;
5641
        $message = null;
5642
5643
        // 1.1 Admins and teachers can access to the exercise
5644
        if ($filterByAdmin) {
5645
            if (api_is_platform_admin() || api_is_course_admin()) {
5646
                return array('value' => true, 'message' => '');
5647
            }
5648
        }
5649
5650
        // Deleted exercise.
5651
        if ($this->active == -1) {
5652
            return array(
5653
                'value' => false,
5654
                'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5655
                'rawMessage' => get_lang('ExerciseNotFound')
5656
            );
5657
        }
5658
5659
        // Checking visibility in the item_property table.
5660
        $visibility = api_get_item_visibility(
5661
            api_get_course_info(),
5662
            TOOL_QUIZ,
5663
            $this->id,
5664
            api_get_session_id()
5665
        );
5666
5667
        if ($visibility == 0 || $visibility == 2) {
5668
            $this->active = 0;
5669
        }
5670
5671
        // 2. If the exercise is not active.
5672
        if (empty($lpId)) {
5673
            // 2.1 LP is OFF
5674
            if ($this->active == 0) {
5675
                return array(
5676
                    'value' => false,
5677
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5678
                    'rawMessage' => get_lang('ExerciseNotFound')
5679
                );
5680
            }
5681
        } else {
5682
            // 2.1 LP is loaded
5683
            if ($this->active == 0 && !learnpath::is_lp_visible_for_student($lpId, api_get_user_id())) {
5684
                return array(
5685
                    'value' => false,
5686
                    'message' => Display::return_message(get_lang('ExerciseNotFound'), 'warning', false),
5687
                    'rawMessage' => get_lang('ExerciseNotFound')
5688
                );
5689
            }
5690
        }
5691
5692
        //3. We check if the time limits are on
5693
        if ((!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00')
5694
            || (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00')) {
5695
            $limitTimeExists = true;
5696
        } else {
5697
            $limitTimeExists = false;
5698
        }
5699
5700
        if ($limitTimeExists) {
5701
            $timeNow = time();
5702
5703
            $existsStartDate = false;
5704
            $nowIsAfterStartDate = true;
5705
            $existsEndDate = false;
5706
            $nowIsBeforeEndDate = true;
5707
5708
5709
            if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') {
5710
                $existsStartDate = true;
5711
            }
5712
5713
            if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') {
5714
                $existsEndDate = true;
5715
            }
5716
5717
            // check if we are before-or-after end-or-start date
5718
            if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) {
5719
                $nowIsAfterStartDate = false;
5720
                    }
5721
5722
            if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) {
5723
                $nowIsBeforeEndDate = false;
5724
                }
5725
5726
            // lets check all cases
5727
            if ($existsStartDate && !$existsEndDate) {
5728
                // exists start date and dont exists end date
5729
                if ($nowIsAfterStartDate) {
5730
                    // after start date, no end date
5731
                    $isVisible = true;
5732
                    $message = sprintf(get_lang('ExerciseAvailableSinceX'),
5733
                        api_convert_and_format_date($this->start_time));
5734
                } else {
5735
                    // before start date, no end date
5736
                    $isVisible = false;
5737
                    $message = sprintf(get_lang('ExerciseAvailableFromX'),
5738
                        api_convert_and_format_date($this->start_time));
5739
            }
5740
            } else if (!$existsStartDate && $existsEndDate) {
5741
                // doesnt exist start date, exists end date
5742
                if ($nowIsBeforeEndDate) {
5743
                    // before end date, no start date
5744
                    $isVisible = true;
5745
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5746
                        api_convert_and_format_date($this->end_time));
5747
                } else {
5748
                    // after end date, no start date
5749
                    $isVisible = false;
5750
                    $message = sprintf(get_lang('ExerciseAvailableUntilX'),
5751
                        api_convert_and_format_date($this->end_time));
5752
                }
5753
            } elseif ($existsStartDate && $existsEndDate) {
5754
                // exists start date and end date
5755
                if ($nowIsAfterStartDate) {
5756
                    if ($nowIsBeforeEndDate) {
5757
                        // after start date and before end date
5758
                        $isVisible = true;
5759
                        $message = sprintf(get_lang('ExerciseIsActivatedFromXToY'),
5760
                    api_convert_and_format_date($this->start_time),
5761
                            api_convert_and_format_date($this->end_time));
5762
                    } else {
5763
                        // after start date and after end date
5764
                        $isVisible = false;
5765
                        $message = sprintf(get_lang('ExerciseWasActivatedFromXToY'),
5766
                            api_convert_and_format_date($this->start_time),
5767
                            api_convert_and_format_date($this->end_time));
5768
                    }
5769
                } else {
5770
                    if ($nowIsBeforeEndDate) {
5771
                        // before start date and before end date
5772
                        $isVisible = false;
5773
                        $message = sprintf(get_lang('ExerciseWillBeActivatedFromXToY'),
5774
                            api_convert_and_format_date($this->start_time),
5775
                            api_convert_and_format_date($this->end_time));
5776
                    }
5777
                    // case before start date and after end date is impossible
5778
                }
5779
            } elseif (!$existsStartDate && !$existsEndDate) {
5780
                // doesnt exist start date nor end date
5781
                $isVisible = true;
5782
                $message = "";
5783
            }
5784
        }
5785
5786
        // 4. We check if the student have attempts
5787
        $exerciseAttempts = $this->selectAttempts();
5788
5789
        if ($isVisible) {
5790
            if ($exerciseAttempts > 0) {
5791
5792
                $attemptCount = Event::get_attempt_count_not_finished(
5793
                    api_get_user_id(),
5794
                    $this->id,
5795
                    $lpId,
5796
                    $lpItemId,
5797
                    $lpItemViewId
5798
                );
5799
5800
                if ($attemptCount >= $exerciseAttempts) {
5801
                    $message = sprintf(
5802
                        get_lang('ReachedMaxAttempts'),
5803
                        $this->name,
5804
                        $exerciseAttempts
5805
                    );
5806
                    $isVisible = false;
5807
                }
5808
            }
5809
        }
5810
5811
        $rawMessage = "";
5812
        if (!empty($message)){
5813
            $rawMessage = $message;
5814
            $message = Display::return_message($message, 'warning', false);
5815
        }
5816
5817
        return array(
5818
            'value' => $isVisible,
5819
            'message' => $message,
5820
            'rawMessage' => $rawMessage
5821
        );
5822
    }
5823
5824
    public function added_in_lp()
5825
    {
5826
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
5827
        $sql = "SELECT max_score FROM $TBL_LP_ITEM
5828
            WHERE c_id = {$this->course_id} AND item_type = '" . TOOL_QUIZ . "' AND path = '{$this->id}'";
5829
        $result = Database::query($sql);
5830
        if (Database::num_rows($result) > 0) {
5831
            return true;
5832
        }
5833
        return false;
5834
    }
5835
5836
    /**
5837
     * Returns an array with the media list
5838
     * @param array question list
5839
     * @example there's 1 question with iid 5 that belongs to the media question with iid = 100
5840
     * <code>
5841
     * array (size=2)
5842
     *  999 =>
5843
     *    array (size=3)
5844
     *      0 => int 7
5845
     *      1 => int 6
5846
     *      2 => int 3254
5847
     *  100 =>
5848
     *   array (size=1)
5849
     *      0 => int 5
5850
     *  </code>
5851
     * @return array
5852
     */
5853
    private function setMediaList($questionList)
5854
    {
5855
        $mediaList = array();
5856
        if (!empty($questionList)) {
5857
            foreach ($questionList as $questionId) {
5858
                $objQuestionTmp = Question::read($questionId, $this->course_id);
5859
5860
                // If a media question exists
5861
                if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) {
5862
                    $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->id;
5863
                } else {
5864
                    //Always the last item
5865
                    $mediaList[999][] = $objQuestionTmp->id;
5866
                }
5867
            }
5868
        }
5869
        $this->mediaList = $mediaList;
5870
    }
5871
5872
    /**
5873
     * Returns an array with this form
5874
     * @example
5875
     * <code>
5876
     * array (size=3)
5877
    999 =>
5878
    array (size=3)
5879
    0 => int 3422
5880
    1 => int 3423
5881
    2 => int 3424
5882
    100 =>
5883
    array (size=2)
5884
    0 => int 3469
5885
    1 => int 3470
5886
    101 =>
5887
    array (size=1)
5888
    0 => int 3482
5889
     * </code>
5890
     * The array inside the key 999 means the question list that belongs to the media id = 999,
5891
     * this case is special because 999 means "no media".
5892
     * @return array
5893
     */
5894
    public function getMediaList()
5895
    {
5896
        return $this->mediaList;
5897
    }
5898
5899
    /**
5900
     * Is media question activated?
5901
     * @return bool
5902
     */
5903
    public function mediaIsActivated()
5904
    {
5905
        $mediaQuestions = $this->getMediaList();
5906
        $active = false;
5907
        if (isset($mediaQuestions) && !empty($mediaQuestions)) {
5908
            $media_count = count($mediaQuestions);
5909
            if ($media_count > 1) {
5910
                return true;
5911
            } elseif ($media_count == 1) {
5912
                if (isset($mediaQuestions[999])) {
5913
                    return false;
5914
                } else {
5915
                    return true;
5916
                }
5917
            }
5918
        }
5919
5920
        return $active;
5921
    }
5922
5923
    /**
5924
     * Gets question list from the exercise
5925
     *
5926
     * @return array
5927
     */
5928
    public function getQuestionList()
5929
    {
5930
        return $this->questionList;
5931
    }
5932
5933
    /**
5934
     * Question list with medias compressed like this
5935
     * @example
5936
     * <code>
5937
     * array(
5938
     *      question_id_1,
5939
     *      question_id_2,
5940
     *      media_id, <- this media id contains question ids
5941
     *      question_id_3,
5942
     * )
5943
     * </code>
5944
     * @return array
5945
     */
5946
    public function getQuestionListWithMediasCompressed()
5947
    {
5948
        return $this->questionList;
5949
    }
5950
5951
    /**
5952
     * Question list with medias uncompressed like this
5953
     * @example
5954
     * <code>
5955
     * array(
5956
     *      question_id,
5957
     *      question_id,
5958
     *      question_id, <- belongs to a media id
5959
     *      question_id, <- belongs to a media id
5960
     *      question_id,
5961
     * )
5962
     * </code>
5963
     * @return array
5964
     */
5965
    public function getQuestionListWithMediasUncompressed()
5966
    {
5967
        return $this->questionListUncompressed;
5968
    }
5969
5970
    /**
5971
     * Sets the question list when the exercise->read() is executed
5972
     */
5973
    public function setQuestionList()
5974
    {
5975
        // Getting question list.
5976
        $questionList = $this->selectQuestionList(true);
5977
5978
        $this->setMediaList($questionList);
5979
5980
        $this->questionList = $this->transformQuestionListWithMedias($questionList, false);
5981
        $this->questionListUncompressed = $this->transformQuestionListWithMedias($questionList, true);
5982
    }
5983
5984
    /**
5985
     *
5986
     * @params array question list
5987
     * @params bool expand or not question list (true show all questions, false show media question id instead of the question ids)
5988
     *
5989
     **/
5990
    public function transformQuestionListWithMedias($question_list, $expand_media_questions = false)
5991
    {
5992
        $new_question_list = array();
5993
        if (!empty($question_list)) {
5994
            $media_questions = $this->getMediaList();
5995
5996
            $media_active = $this->mediaIsActivated($media_questions);
5997
5998
            if ($media_active) {
5999
                $counter = 1;
6000
                foreach ($question_list as $question_id) {
6001
                    $add_question = true;
6002
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6003
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6004
                            $add_question = false;
6005
                            if (!in_array($media_id, $new_question_list)) {
6006
                                $new_question_list[$counter] = $media_id;
6007
                                $counter++;
6008
                            }
6009
                            break;
6010
                        }
6011
                    }
6012
                    if ($add_question) {
6013
                        $new_question_list[$counter] = $question_id;
6014
                        $counter++;
6015
                    }
6016
                }
6017
                if ($expand_media_questions) {
6018
                    $media_key_list = array_keys($media_questions);
6019
                    foreach ($new_question_list as &$question_id) {
6020
                        if (in_array($question_id, $media_key_list)) {
6021
                            $question_id = $media_questions[$question_id];
6022
                        }
6023
                    }
6024
                    $new_question_list = array_flatten($new_question_list);
6025
                }
6026
            } else {
6027
                $new_question_list = $question_list;
6028
            }
6029
        }
6030
6031
        return $new_question_list;
6032
    }
6033
6034
    function get_validated_question_list()
6035
    {
6036
        $tabres = array();
6037
        $isRandomByCategory = $this->isRandomByCat();
6038
        if ($isRandomByCategory == 0) {
6039
            if ($this->isRandom()) {
6040
                $tabres = $this->selectRandomList();
6041
            } else {
6042
                $tabres = $this->selectQuestionList();
6043
            }
6044
        } else {
6045
            if ($this->isRandom()) {
6046
                // USE question categories
6047
                // get questions by category for this exercise
6048
                // we have to choice $objExercise->random question in each array values of $tabCategoryQuestions
6049
                // key of $tabCategoryQuestions are the categopy id (0 for not in a category)
6050
                // value is the array of question id of this category
6051
                $questionList = array();
6052
                $tabCategoryQuestions = TestCategory::getQuestionsByCat($this->id);
6053
                $isRandomByCategory = $this->selectRandomByCat();
6054
                // on tri les categories en fonction du terme entre [] en tete de la description de la categorie
6055
                /*
6056
                 * ex de catégories :
6057
                 * [biologie] Maitriser les mecanismes de base de la genetique
6058
                 * [biologie] Relier les moyens de depenses et les agents infectieux
6059
                 * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme
6060
                 * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur
6061
                 * [chimie] Connaître la denition de la theoie acide/base selon Brönsted
6062
                 * [chimie] Connaître les charges des particules
6063
                 * On veut dans l'ordre des groupes definis par le terme entre crochet au debut du titre de la categorie
6064
                */
6065
                // If test option is Grouped By Categories
6066
                if ($isRandomByCategory == 2) {
6067
                    $tabCategoryQuestions = TestCategory::sortTabByBracketLabel($tabCategoryQuestions);
6068
                }
6069
                while (list($cat_id, $tabquestion) = each($tabCategoryQuestions)) {
6070
                    $number_of_random_question = $this->random;
6071
                    if ($this->random == -1) {
6072
                        $number_of_random_question = count($this->questionList);
6073
                    }
6074
                    $questionList = array_merge(
6075
                        $questionList,
6076
                        TestCategory::getNElementsFromArray(
6077
                            $tabquestion,
6078
                            $number_of_random_question
6079
                        )
6080
                    );
6081
                }
6082
                // shuffle the question list if test is not grouped by categories
6083
                if ($isRandomByCategory == 1) {
6084
                    shuffle($questionList); // or not
6085
                }
6086
                $tabres = $questionList;
6087
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
6088
                // Problem, random by category has been selected and
6089
                // we have no $this->isRandom number of question selected
6090
                // Should not happened
6091
            }
6092
        }
6093
        return $tabres;
6094
    }
6095
6096
    function get_question_list($expand_media_questions = false)
6097
    {
6098
        $question_list = $this->get_validated_question_list();
6099
        $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions);
6100
        return $question_list;
6101
    }
6102
6103
    function transform_question_list_with_medias($question_list, $expand_media_questions = false)
6104
    {
6105
        $new_question_list = array();
6106
        if (!empty($question_list)) {
6107
            $media_questions = $this->getMediaList();
6108
            $media_active = $this->mediaIsActivated($media_questions);
6109
6110
            if ($media_active) {
6111
                $counter = 1;
6112
                foreach ($question_list as $question_id) {
6113
                    $add_question = true;
6114
                    foreach ($media_questions as $media_id => $question_list_in_media) {
6115
                        if ($media_id != 999 && in_array($question_id, $question_list_in_media)) {
6116
                            $add_question = false;
6117
                            if (!in_array($media_id, $new_question_list)) {
6118
                                $new_question_list[$counter] = $media_id;
6119
                                $counter++;
6120
                            }
6121
                            break;
6122
                        }
6123
                    }
6124
                    if ($add_question) {
6125
                        $new_question_list[$counter] = $question_id;
6126
                        $counter++;
6127
                    }
6128
                }
6129
                if ($expand_media_questions) {
6130
                    $media_key_list = array_keys($media_questions);
6131
                    foreach ($new_question_list as &$question_id) {
6132
                        if (in_array($question_id, $media_key_list)) {
6133
                            $question_id = $media_questions[$question_id];
6134
                        }
6135
                    }
6136
                    $new_question_list = array_flatten($new_question_list);
6137
                }
6138
            } else {
6139
                $new_question_list = $question_list;
6140
            }
6141
        }
6142
        return $new_question_list;
6143
    }
6144
6145
    /**
6146
     * @param int $exe_id
6147
     * @return array|mixed
6148
     */
6149
    public function get_stat_track_exercise_info_by_exe_id($exe_id)
6150
    {
6151
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6152
        $exe_id = intval($exe_id);
6153
        $sql_track = "SELECT * FROM $track_exercises WHERE exe_id = $exe_id ";
6154
        $result = Database::query($sql_track);
6155
        $new_array = array();
6156
        if (Database::num_rows($result) > 0 ) {
6157
            $new_array = Database::fetch_array($result, 'ASSOC');
6158
6159
            $new_array['duration'] = null;
6160
6161
            $start_date = api_get_utc_datetime($new_array['start_date'], true);
6162
            $end_date = api_get_utc_datetime($new_array['exe_date'], true);
6163
6164
            if (!empty($start_date) && !empty($end_date)) {
6165
                $start_date = api_strtotime($start_date, 'UTC');
6166
                $end_date = api_strtotime($end_date, 'UTC');
6167
                if ($start_date && $end_date) {
6168
                    $mytime = $end_date- $start_date;
6169
                    $new_learnpath_item = new learnpathItem(null);
6170
                    $time_attemp = $new_learnpath_item->get_scorm_time('js', $mytime);
6171
                    $h = get_lang('h');
6172
                    $time_attemp = str_replace('NaN', '00' . $h . '00\'00"', $time_attemp);
6173
                    $new_array['duration'] = $time_attemp;
6174
                }
6175
            }
6176
        }
6177
        return $new_array;
6178
    }
6179
6180
    public function edit_question_to_remind($exe_id, $question_id, $action = 'add')
6181
    {
6182
        $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exe_id);
6183
        $question_id = intval($question_id);
6184
        $exe_id = intval($exe_id);
6185
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6186
        if ($exercise_info) {
6187
6188
            if (empty($exercise_info['questions_to_check'])) {
6189
                if ($action == 'add') {
6190
                    $sql = "UPDATE $track_exercises SET questions_to_check = '$question_id' WHERE exe_id = $exe_id ";
6191
                    $result = Database::query($sql);
6192
                }
6193
            } else {
6194
                $remind_list = explode(',',$exercise_info['questions_to_check']);
6195
6196
                $remind_list_string = '';
6197
                if ($action == 'add') {
6198
                    if (!in_array($question_id, $remind_list)) {
6199
                        $remind_list[] = $question_id;
6200
                        if (!empty($remind_list)) {
6201
                            sort($remind_list);
6202
                            array_filter($remind_list);
6203
                        }
6204
                        $remind_list_string = implode(',', $remind_list);
6205
                    }
6206
                } elseif ($action == 'delete')  {
6207
                    if (!empty($remind_list)) {
6208
                        if (in_array($question_id, $remind_list)) {
6209
                            $remind_list = array_flip($remind_list);
6210
                            unset($remind_list[$question_id]);
6211
                            $remind_list = array_flip($remind_list);
6212
6213
                            if (!empty($remind_list)) {
6214
                                sort($remind_list);
6215
                                array_filter($remind_list);
6216
                                $remind_list_string = implode(',', $remind_list);
6217
                            }
6218
                        }
6219
                    }
6220
                }
6221
                $remind_list_string = Database::escape_string($remind_list_string);
6222
                $sql = "UPDATE $track_exercises SET questions_to_check = '$remind_list_string' WHERE exe_id = $exe_id ";
6223
                Database::query($sql);
6224
            }
6225
        }
6226
    }
6227
6228
    public function fill_in_blank_answer_to_array($answer)
6229
    {
6230
        api_preg_match_all('/\[[^]]+\]/', $answer, $teacher_answer_list);
6231
        $teacher_answer_list = $teacher_answer_list[0];
6232
        return $teacher_answer_list;
6233
    }
6234
6235
    public function fill_in_blank_answer_to_string($answer)
6236
    {
6237
        $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer);
6238
        $result = '';
6239
        if (!empty($teacher_answer_list)) {
6240
            $i = 0;
6241
            foreach ($teacher_answer_list as $teacher_item) {
6242
                $value = null;
6243
                //Cleaning student answer list
6244
                $value = strip_tags($teacher_item);
6245
                $value = api_substr($value, 1, api_strlen($value) - 2);
6246
                $value = explode('/', $value);
6247
                if (!empty($value[0])) {
6248
                    $value = trim($value[0]);
6249
                    $value = str_replace('&nbsp;', '', $value);
6250
                    $result .= $value;
6251
                }
6252
            }
6253
        }
6254
        return $result;
6255
    }
6256
6257
    function return_time_left_div()
6258
    {
6259
        $html = '<div id="clock_warning" style="display:none">';
6260
        $html .= Display::return_message(
6261
            get_lang('ReachedTimeLimit'),
6262
            'warning'
6263
        );
6264
        $html .= ' ';
6265
        $html .= sprintf(
6266
            get_lang('YouWillBeRedirectedInXSeconds'),
6267
            '<span id="counter_to_redirect" class="red_alert"></span>'
6268
        );
6269
        $html .= '</div>';
6270
        $html .= '<div id="exercise_clock_warning" class="count_down"></div>';
6271
        return $html;
6272
    }
6273
6274
    function get_count_question_list()
6275
    {
6276
        //Real question count
6277
        $question_count = 0;
6278
        $question_list = $this->get_question_list();
6279
        if (!empty($question_list)) {
6280
            $question_count = count($question_list);
6281
        }
6282
        return $question_count;
6283
    }
6284
6285
    function get_exercise_list_ordered()
6286
    {
6287
        $table_exercise_order = Database::get_course_table(TABLE_QUIZ_ORDER);
6288
        $course_id = api_get_course_int_id();
6289
        $session_id = api_get_session_id();
6290
        $sql = "SELECT exercise_id, exercise_order
6291
                FROM $table_exercise_order
6292
                WHERE c_id = $course_id AND session_id = $session_id
6293
                ORDER BY exercise_order";
6294
        $result = Database::query($sql);
6295
        $list = array();
6296
        if (Database::num_rows($result)) {
6297
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6298
                $list[$row['exercise_order']] = $row['exercise_id'];
6299
            }
6300
        }
6301
        return $list;
6302
    }
6303
6304
    /**
6305
     * Get categories added in the exercise--category matrix
6306
     * @return bool
6307
     */
6308
    public function get_categories_in_exercise()
6309
    {
6310
        if (!$this->specialCategoryOrders) {
6311
            return false;
6312
        }
6313
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6314
        if (!empty($this->id)) {
6315
            $sql = "SELECT * FROM $table
6316
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id} ";
6317
            $result = Database::query($sql);
6318
            $list = array();
6319
            if (Database::num_rows($result)) {
6320
                while ($row = Database::fetch_array($result, 'ASSOC')) {
6321
                    $list[$row['category_id']] = $row;
6322
                }
6323
                return $list;
6324
            }
6325
        }
6326
        return false;
6327
    }
6328
6329
    /**
6330
     * @param null $order
6331
     * @return bool
6332
     */
6333
    public function get_categories_with_name_in_exercise($order = null)
6334
    {
6335
        if (!$this->specialCategoryOrders) {
6336
            return false;
6337
        }
6338
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6339
        $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
6340
        $sql = "SELECT * FROM $table qc
6341
                INNER JOIN $table_category c
6342
                ON (category_id = c.iid)
6343
                WHERE exercise_id = {$this->id} AND qc.c_id = {$this->course_id} ";
6344
        if (!empty($order)) {
6345
            $sql .= "ORDER BY $order ";
6346
        }
6347
        $result = Database::query($sql);
6348
        if (Database::num_rows($result)) {
6349
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6350
                $list[$row['category_id']] = $row;
6351
            }
6352
            return $list;
6353
        }
6354
        return false;
6355
    }
6356
6357
    /**
6358
     * Get total number of question that will be parsed when using the category/exercise
6359
     */
6360
    public function getNumberQuestionExerciseCategory()
6361
    {
6362
        if (!$this->specialCategoryOrders) {
6363
            return false;
6364
        }
6365
6366
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6367
        if (!empty($this->id)) {
6368
            $sql = "SELECT SUM(count_questions) count_questions
6369
                    FROM $table
6370
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6371
            $result = Database::query($sql);
6372
            if (Database::num_rows($result)) {
6373
                $row = Database::fetch_array($result);
6374
                return $row['count_questions'];
6375
            }
6376
        }
6377
        return 0;
6378
    }
6379
6380
    /**
6381
     * Save categories in the TABLE_QUIZ_REL_CATEGORY table
6382
     * @param array $categories
6383
     */
6384
    public function save_categories_in_exercise($categories)
6385
    {
6386
        if (!$this->specialCategoryOrders) {
6387
            return false;
6388
        }
6389
        if (!empty($categories) && !empty($this->id)) {
6390
            $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
6391
            $sql = "DELETE FROM $table
6392
                    WHERE exercise_id = {$this->id} AND c_id = {$this->course_id}";
6393
            Database::query($sql);
6394
            if (!empty($categories)) {
6395
                foreach ($categories as $category_id => $count_questions) {
6396
                    $params = array(
6397
                        'c_id' => $this->course_id,
6398
                        'exercise_id' => $this->id,
6399
                        'category_id' => $category_id,
6400
                        'count_questions' => $count_questions
6401
                    );
6402
                    Database::insert($table, $params);
6403
                }
6404
            }
6405
        }
6406
    }
6407
6408
    /**
6409
     * @param array $questionList
6410
     * @param int $currentQuestion
6411
     * @param array $conditions
6412
     * @param string $link
6413
     * @return string
6414
     */
6415
    public function progressExercisePaginationBar($questionList, $currentQuestion, $conditions, $link)
6416
    {
6417
        $mediaQuestions = $this->getMediaList();
6418
6419
        $html = '<div class="exercise_pagination pagination pagination-mini"><ul>';
6420
        $counter = 0;
6421
        $nextValue = 0;
6422
        $wasMedia = false;
6423
        $before = 0;
6424
        $counterNoMedias = 0;
6425
        foreach ($questionList as $questionId) {
6426
            $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false;
6427
6428
            if (!empty($nextValue)) {
6429
                if ($wasMedia) {
6430
                    $nextValue = $nextValue - $before + 1;
6431
                }
6432
            }
6433
6434
            if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) {
6435
                $fixedValue = $counterNoMedias;
6436
6437
                $html .= Display::progressPaginationBar(
6438
                    $nextValue,
6439
                    $mediaQuestions[$questionId],
6440
                    $currentQuestion,
6441
                    $fixedValue,
6442
                    $conditions,
6443
                    $link,
6444
                    true,
6445
                    true
6446
                );
6447
6448
                $counter += count($mediaQuestions[$questionId]) - 1 ;
6449
                $before = count($questionList);
6450
                $wasMedia = true;
6451
                $nextValue += count($questionList);
6452
            } else {
6453
                $html .= Display::parsePaginationItem($questionId, $isCurrent, $conditions, $link, $counter);
6454
                $counter++;
6455
                $nextValue++;
6456
                $wasMedia = false;
6457
            }
6458
            $counterNoMedias++;
6459
        }
6460
        $html .= '</ul></div>';
6461
        return $html;
6462
    }
6463
6464
6465
    /**
6466
     *  Shows a list of numbers that represents the question to answer in a exercise
6467
     *
6468
     * @param array $categories
6469
     * @param int $current
6470
     * @param array $conditions
6471
     * @param string $link
6472
     * @return string
6473
     */
6474
    public function progressExercisePaginationBarWithCategories(
6475
        $categories,
6476
        $current,
6477
        $conditions = array(),
6478
        $link = null
6479
    ) {
6480
        $html = null;
6481
        $counterNoMedias = 0;
6482
        $nextValue = 0;
6483
        $wasMedia = false;
6484
        $before = 0;
6485
6486
        if (!empty($categories)) {
6487
            $selectionType = $this->getQuestionSelectionType();
6488
            $useRootAsCategoryTitle = false;
6489
6490
            // Grouping questions per parent category see BT#6540
6491
6492
            if (in_array(
6493
                $selectionType,
6494
                array(
6495
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED,
6496
                    EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM
6497
                )
6498
            )) {
6499
                $useRootAsCategoryTitle = true;
6500
            }
6501
6502
            // If the exercise is set to only show the titles of the categories
6503
            // at the root of the tree, then pre-order the categories tree by
6504
            // removing children and summing their questions into the parent
6505
            // categories
6506
6507
            if ($useRootAsCategoryTitle) {
6508
                // The new categories list starts empty
6509
                $newCategoryList = array();
6510
                foreach ($categories as $category) {
6511
                    $rootElement = $category['root'];
6512
6513
                    if (isset($category['parent_info'])) {
6514
                        $rootElement = $category['parent_info']['id'];
6515
                    }
6516
6517
                    //$rootElement = $category['id'];
6518
                    // If the current category's ancestor was never seen
6519
                    // before, then declare it and assign the current
6520
                    // category to it.
6521
                    if (!isset($newCategoryList[$rootElement])) {
6522
                        $newCategoryList[$rootElement] = $category;
6523
                    } else {
6524
                        // If it was already seen, then merge the previous with
6525
                        // the current category
6526
                        $oldQuestionList = $newCategoryList[$rootElement]['question_list'];
6527
                        $category['question_list'] = array_merge($oldQuestionList , $category['question_list']);
6528
                        $newCategoryList[$rootElement] = $category;
6529
                    }
6530
                }
6531
                // Now use the newly built categories list, with only parents
6532
                $categories = $newCategoryList;
6533
            }
6534
6535
            foreach ($categories as $category) {
6536
                $questionList = $category['question_list'];
6537
                // Check if in this category there questions added in a media
6538
                $mediaQuestionId = $category['media_question'];
6539
                $isMedia = false;
6540
                $fixedValue = null;
6541
6542
                // Media exists!
6543
                if ($mediaQuestionId != 999) {
6544
                    $isMedia = true;
6545
                    $fixedValue = $counterNoMedias;
6546
                }
6547
6548
                //$categoryName = $category['path']; << show the path
6549
                $categoryName = $category['name'];
6550
6551
                if ($useRootAsCategoryTitle) {
6552
                    if (isset($category['parent_info'])) {
6553
                        $categoryName  = $category['parent_info']['title'];
6554
                    }
6555
                }
6556
                $html .= '<div class="row">';
6557
                $html .= '<div class="span2">'.$categoryName.'</div>';
6558
                $html .= '<div class="span8">';
6559
6560
                if (!empty($nextValue)) {
6561
                    if ($wasMedia) {
6562
                        $nextValue = $nextValue - $before + 1;
6563
                    }
6564
                }
6565
                $html .= Display::progressPaginationBar(
6566
                    $nextValue,
6567
                    $questionList,
6568
                    $current,
6569
                    $fixedValue,
6570
                    $conditions,
6571
                    $link,
6572
                    $isMedia,
6573
                    true
6574
                );
6575
                $html .= '</div>';
6576
                $html .= '</div>';
6577
6578
                if ($mediaQuestionId == 999) {
6579
                    $counterNoMedias += count($questionList);
6580
                } else {
6581
                    $counterNoMedias++;
6582
                }
6583
6584
                $nextValue += count($questionList);
6585
                $before = count($questionList);
6586
6587
                if ($mediaQuestionId != 999) {
6588
                    $wasMedia = true;
6589
                } else {
6590
                    $wasMedia = false;
6591
                }
6592
6593
            }
6594
        }
6595
        return $html;
6596
    }
6597
6598
    /**
6599
     * Renders a question list
6600
     *
6601
     * @param array $questionList (with media questions compressed)
6602
     * @param int $currentQuestion
6603
     * @param array $exerciseResult
6604
     * @param array $attemptList
6605
     * @param array $remindList
6606
     */
6607
    public function renderQuestionList($questionList, $currentQuestion, $exerciseResult, $attemptList, $remindList)
6608
    {
6609
        $mediaQuestions = $this->getMediaList();
6610
        $i = 0;
6611
6612
        // Normal question list render (medias compressed)
6613
        foreach ($questionList as $questionId) {
6614
            $i++;
6615
            // For sequential exercises
6616
            if ($this->type == ONE_PER_PAGE) {
6617
                // If it is not the right question, goes to the next loop iteration
6618
                if ($currentQuestion != $i) {
6619
                    continue;
6620
                } else {
6621
                    if ($this->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
6622
                        // if the user has already answered this question
6623
                        if (isset($exerciseResult[$questionId])) {
6624
                            Display::display_normal_message(get_lang('AlreadyAnswered'));
6625
                            break;
6626
                        }
6627
                    }
6628
                }
6629
            }
6630
6631
            // The $questionList contains the media id we check if this questionId is a media question type
6632
6633
            if (isset($mediaQuestions[$questionId]) && $mediaQuestions[$questionId] != 999) {
6634
6635
                // The question belongs to a media
6636
                $mediaQuestionList = $mediaQuestions[$questionId];
6637
                $objQuestionTmp = Question::read($questionId);
6638
6639
                $counter = 1;
6640
                if ($objQuestionTmp->type == MEDIA_QUESTION) {
6641
                    echo $objQuestionTmp->show_media_content();
6642
6643
                    $countQuestionsInsideMedia = count($mediaQuestionList);
6644
6645
                    // Show questions that belongs to a media
6646
                    if (!empty($mediaQuestionList)) {
6647
                        // In order to parse media questions we use letters a, b, c, etc.
6648
                        $letterCounter = 97;
6649
                        foreach ($mediaQuestionList as $questionIdInsideMedia) {
6650
                            $isLastQuestionInMedia = false;
6651
                            if ($counter == $countQuestionsInsideMedia) {
6652
                                $isLastQuestionInMedia = true;
6653
                            }
6654
                            $this->renderQuestion(
6655
                                $questionIdInsideMedia,
6656
                                $attemptList,
6657
                                $remindList,
6658
                                chr($letterCounter),
6659
                                $currentQuestion,
6660
                                $mediaQuestionList,
6661
                                $isLastQuestionInMedia,
6662
                                $questionList
6663
                            );
6664
                            $letterCounter++;
6665
                            $counter++;
6666
                        }
6667
                    }
6668
                } else {
6669
                    $this->renderQuestion(
6670
                        $questionId,
6671
                        $attemptList,
6672
                        $remindList,
6673
                        $i,
6674
                        $currentQuestion,
6675
                        null,
6676
                        null,
6677
                        $questionList
6678
                    );
6679
                    $i++;
6680
                }
6681
            } else {
6682
                // Normal question render.
6683
                $this->renderQuestion($questionId, $attemptList, $remindList, $i, $currentQuestion, null, null, $questionList);
6684
            }
6685
6686
            // For sequential exercises.
6687
            if ($this->type == ONE_PER_PAGE) {
6688
                // quits the loop
6689
                break;
6690
            }
6691
        }
6692
        // end foreach()
6693
6694
        if ($this->type == ALL_ON_ONE_PAGE) {
6695
            $exercise_actions =  $this->show_button($questionId, $currentQuestion);
6696
            echo Display::div($exercise_actions, array('class'=>'exercise_actions'));
6697
        }
6698
    }
6699
6700
    /**
6701
     * @param int $questionId
6702
     * @param array $attemptList
6703
     * @param array $remindList
6704
     * @param int $i
6705
     * @param int $current_question
6706
     * @param array $questions_in_media
6707
     * @param bool $last_question_in_media
6708
     * @param array $realQuestionList
6709
     * @param bool $generateJS
6710
     * @return null
6711
     */
6712
    public function renderQuestion(
6713
        $questionId,
6714
        $attemptList,
6715
        $remindList,
6716
        $i,
6717
        $current_question,
6718
        $questions_in_media = array(),
6719
        $last_question_in_media = false,
6720
        $realQuestionList,
6721
        $generateJS = true
6722
    ) {
6723
6724
        // With this option on the question is loaded via AJAX
6725
        //$generateJS = true;
6726
        //$this->loadQuestionAJAX = true;
6727
6728
        if ($generateJS && $this->loadQuestionAJAX) {
6729
            $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId;
6730
            $params = array(
6731
                'questionId' => $questionId,
6732
                'attemptList'=> $attemptList,
6733
                'remindList' => $remindList,
6734
                'i' => $i,
6735
                'current_question' => $current_question,
6736
                'questions_in_media' => $questions_in_media,
6737
                'last_question_in_media' => $last_question_in_media
6738
            );
6739
            $params = json_encode($params);
6740
6741
            $script = '<script>
6742
            $(function(){
6743
                var params = '.$params.';
6744
                $.ajax({
6745
                    type: "GET",
6746
                    async: false,
6747
                    data: params,
6748
                    url: "'.$url.'",
6749
                    success: function(return_value) {
6750
                        $("#ajaxquestiondiv'.$questionId.'").html(return_value);
6751
                    }
6752
                });
6753
            });
6754
            </script>
6755
            <div id="ajaxquestiondiv'.$questionId.'"></div>';
6756
            echo $script;
6757
        } else {
6758
6759
            global $origin;
6760
            $question_obj = Question::read($questionId);
6761
            $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null;
6762
6763
            $remind_highlight = null;
6764
6765
            //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
6766
            if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
6767
                $remind_highlight = 'no_remind_highlight';
6768
                if (in_array($question_obj->type, Question::question_type_no_review())) {
6769
                    return null;
6770
                }
6771
            }
6772
6773
            $attributes = array('id' =>'remind_list['.$questionId.']');
6774
            if (is_array($remindList) && in_array($questionId, $remindList)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
6775
                //$attributes['checked'] = 1;
6776
                //$remind_highlight = ' remind_highlight ';
6777
            }
6778
6779
            // Showing the question
6780
6781
            $exercise_actions  = null;
6782
6783
            echo '<a id="questionanchor'.$questionId.'"></a><br />';
6784
            echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
6785
6786
            // Shows the question + possible answers
6787
            $showTitle = $this->getHideQuestionTitle() == 1 ? false : true;
6788
            echo $this->showQuestion($question_obj, false, $origin, $i, $showTitle, false, $user_choice, false, null, false, $this->getModelType(), $this->categoryMinusOne);
6789
6790
            // Button save and continue
6791
            switch ($this->type) {
6792
                case ONE_PER_PAGE:
6793
                    $exercise_actions .= $this->show_button($questionId, $current_question, null, $remindList);
6794
                    break;
6795
                case ALL_ON_ONE_PAGE:
6796
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', null, true, 1); ">'.get_lang('SaveForNow').'</a>';
6797
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6798
                    $exercise_actions .= Display::div($button, array('class'=>'exercise_save_now_button'));
6799
                    break;
6800
            }
6801
6802
            if (!empty($questions_in_media)) {
6803
                $count_of_questions_inside_media = count($questions_in_media);
6804
                if ($count_of_questions_inside_media > 1) {
6805
                    $button  = '<a href="javascript://" class="btn" onclick="save_now(\''.$questionId.'\', false, false, 0); ">'.get_lang('SaveForNow').'</a>';
6806
                    $button .= '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>&nbsp;';
6807
                    $exercise_actions = Display::div($button, array('class'=>'exercise_save_now_button'));
6808
                }
6809
6810
                if ($last_question_in_media && $this->type == ONE_PER_PAGE) {
6811
                    $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media);
6812
                }
6813
            }
6814
6815
            // Checkbox review answers
6816
            if ($this->review_answers && !in_array($question_obj->type, Question::question_type_no_review())) {
6817
                $remind_question_div = Display::tag('label', Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), array('class' => 'checkbox', 'for' =>'remind_list['.$questionId.']'));
6818
                $exercise_actions   .= Display::div($remind_question_div, array('class'=>'exercise_save_now_button'));
6819
            }
6820
6821
            echo Display::div(' ', array('class'=>'clear'));
6822
6823
            $paginationCounter = null;
6824
            if ($this->type == ONE_PER_PAGE) {
6825
                if (empty($questions_in_media)) {
6826
                    $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6827
                } else {
6828
                    if ($last_question_in_media) {
6829
                        $paginationCounter = Display::paginationIndicator($current_question, count($realQuestionList));
6830
                    }
6831
                }
6832
            }
6833
6834
            echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>';
6835
            echo Display::div($exercise_actions, array('class'=>'form-actions'));
6836
            echo '</div>';
6837
        }
6838
    }
6839
6840
    /**
6841
     * Shows a question
6842
     * @param Question $objQuestionTmp
6843
     * @param bool $only_questions if true only show the questions, no exercise title
6844
     * @param bool $origin origin i.e = learnpath
6845
     * @param string $current_item current item from the list of questions
6846
     * @param bool $show_title
6847
     * @param bool $freeze
6848
     * @param array $user_choice
6849
     * @param bool $show_comment
6850
     * @param null $exercise_feedback
6851
     * @param bool $show_answers
6852
     * @param null $modelType
6853
     * @param bool $categoryMinusOne
6854
     * @return bool|null|string
6855
     */
6856
    public function showQuestion(
6857
        Question $objQuestionTmp,
6858
        $only_questions = false,
6859
        $origin = false,
6860
        $current_item = '',
6861
        $show_title = true,
6862
        $freeze = false,
6863
        $user_choice = array(),
6864
        $show_comment = false,
6865
        $exercise_feedback = null,
6866
        $show_answers = false,
6867
        $modelType = null,
6868
        $categoryMinusOne = true
6869
    ) {
6870
        // Text direction for the current language
6871
        //$is_ltr_text_direction = api_get_text_direction() != 'rtl';
6872
        // Change false to true in the following line to enable answer hinting
6873
        $debug_mark_answer = $show_answers; //api_is_allowed_to_edit() && false;
6874
        // Reads question information
6875
        if (!$objQuestionTmp) {
6876
            // Question not found
6877
            return false;
6878
        }
6879
6880
        $html = null;
6881
6882
        $questionId = $objQuestionTmp->id;
6883
6884
        if ($exercise_feedback != EXERCISE_FEEDBACK_TYPE_END) {
6885
            $show_comment = false;
6886
        }
6887
6888
        $answerType = $objQuestionTmp->selectType();
6889
        $pictureName = $objQuestionTmp->selectPicture();
6890
6891
        $s = null;
6892
        $form = new FormValidator('question');
6893
        $renderer = $form->defaultRenderer();
6894
        $form_template = '{content}';
6895
        $renderer->setFormTemplate($form_template);
6896
6897
        if ($answerType != HOT_SPOT && $answerType != HOT_SPOT_DELINEATION) {
6898
            // Question is not a hotspot
6899
            if (!$only_questions) {
6900
                $questionDescription = $objQuestionTmp->selectDescription();
6901
                if ($show_title) {
6902
                    $categoryName = TestCategory::getCategoryNamesForQuestion($objQuestionTmp->id, null, true, $categoryMinusOne);
6903
                    $html .= $categoryName;
6904
                    $html .= Display::div($current_item.'. '.$objQuestionTmp->selectTitle(), array('class' => 'question_title'));
6905
                    if (!empty($questionDescription)) {
6906
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
6907
                    }
6908
                } else {
6909
                    $html .= '<div class="media">';
6910
                    $html .= '<div class="pull-left">';
6911
                    $html .= '<div class="media-object">';
6912
                    $html .= Display::div($current_item, array('class' => 'question_no_title'));
6913
                    $html .= '</div>';
6914
                    $html .= '</div>';
6915
                    $html .= '<div class="media-body">';
6916
                    if (!empty($questionDescription)) {
6917
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
6918
                    }
6919
                    $html .= '</div>';
6920
                    $html .= '</div>';
6921
                }
6922
            }
6923
6924
            if (in_array($answerType, array(FREE_ANSWER, ORAL_EXPRESSION)) && $freeze) {
6925
                return null;
6926
            }
6927
6928
            $html .= '<div class="question_options">';
6929
            // construction of the Answer object (also gets all answers details)
6930
            $objAnswerTmp = new Answer($questionId, null, $this);
6931
6932
            $nbrAnswers = $objAnswerTmp->selectNbrAnswers();
6933
            $course_id = api_get_course_int_id();
6934
            $quiz_question_options = Question::readQuestionOption($questionId, $course_id);
6935
6936
            // For "matching" type here, we need something a little bit special
6937
            // because the match between the suggestions and the answers cannot be
6938
            // done easily (suggestions and answers are in the same table), so we
6939
            // have to go through answers first (elems with "correct" value to 0).
6940
            $select_items = array();
6941
            //This will contain the number of answers on the left side. We call them
6942
            // suggestions here, for the sake of comprehensions, while the ones
6943
            // on the right side are called answers
6944
            $num_suggestions = 0;
6945
6946
            if ($answerType == MATCHING || $answerType == DRAGGABLE) {
6947
                if ($answerType == DRAGGABLE) {
6948
                    $s .= '<div class="ui-widget ui-helper-clearfix">
6949
                            <ul class="drag_question ui-helper-reset ui-helper-clearfix">';
6950
                } else {
6951
                    $s .= '<div id="drag'.$questionId.'_question" class="drag_question">';
6952
                    $s .= '<table class="data_table">';
6953
                }
6954
6955
                $j = 1; //iterate through answers
6956
                $letter = 'A'; //mark letters for each answer
6957
                $answer_matching = array();
6958
                $capital_letter = array();
6959
                //for ($answerId=1; $answerId <= $nbrAnswers; $answerId++) {
6960
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
6961
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
6962
                    $answer = $objAnswerTmp->selectAnswer($answerId);
6963
                    if ($answerCorrect == 0) {
6964
                        // options (A, B, C, ...) that will be put into the list-box
6965
                        // have the "correct" field set to 0 because they are answer
6966
                        $capital_letter[$j] = $letter;
6967
                        //$answer_matching[$j]=$objAnswerTmp->selectAnswerByAutoId($numAnswer);
6968
                        $answer_matching[$j] = array('id' => $answerId, 'answer' => $answer);
6969
                        $j++;
6970
                        $letter++;
6971
                    }
6972
                }
6973
6974
                $i = 1;
6975
6976
                $select_items[0]['id'] = 0;
6977
                $select_items[0]['letter'] = '--';
6978
                $select_items[0]['answer'] = '';
6979
6980
                foreach ($answer_matching as $id => $value) {
6981
                    $select_items[$i]['id'] = $value['id'];
6982
                    $select_items[$i]['letter'] = $capital_letter[$id];
6983
                    $select_items[$i]['answer'] = $value['answer'];
6984
                    $i++;
6985
                }
6986
                $num_suggestions = ($nbrAnswers - $j) + 1;
6987
            } elseif ($answerType == FREE_ANSWER) {
6988
                $content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
6989
                $toolBar = 'TestFreeAnswer';
6990
                if ($modelType == EXERCISE_MODEL_TYPE_COMMITTEE) {
6991
                    $toolBar = 'TestFreeAnswerStrict';
6992
                }
6993
                $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => $toolBar));
6994
                $form->setDefaults(array("choice[".$questionId."]" => $content));
6995
                $s .= $form->return_form();
6996
            } elseif ($answerType == ORAL_EXPRESSION) {
6997
                // Add nanogong
6998
                if (api_get_setting('document.enable_nanogong') == 'true') {
6999
7000
                    //@todo pass this as a parameter
7001
                    global $exercise_stat_info, $exerciseId;
7002
7003
                    if (!empty($exercise_stat_info)) {
7004
                        $params = array(
7005
                            'exercise_id' => $exercise_stat_info['exe_exo_id'],
7006
                            'exe_id' => $exercise_stat_info['exe_id'],
7007
                            'question_id' => $questionId
7008
                        );
7009
                    } else {
7010
                        $params = array(
7011
                            'exercise_id' => $exerciseId,
7012
                            'exe_id' => 'temp_exe',
7013
                            'question_id' => $questionId
7014
                        );
7015
                    }
7016
7017
                    $nano = new Nanogong($params);
7018
                    $s .= $nano->show_button();
7019
                }
7020
7021
                $form->addElement('html_editor', "choice[".$questionId."]", null, array('id' => "choice[".$questionId."]"), array('ToolbarSet' => 'TestFreeAnswer'));
7022
                //$form->setDefaults(array("choice[".$questionId."]" => $content));
7023
                $s .= $form->return_form();
7024
            }
7025
7026
            // Now navigate through the possible answers, using the max number of
7027
            // answers for the question as a limiter
7028
            $lines_count = 1; // a counter for matching-type answers
7029
7030
            if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7031
                $header = Display::tag('th', get_lang('Options'));
7032
                foreach ($objQuestionTmp->options as $item) {
7033
                    $header .= Display::tag('th', $item);
7034
                }
7035
                if ($show_comment) {
7036
                    $header .= Display::tag('th', get_lang('Feedback'));
7037
                }
7038
                $s .= '<table class="data_table">';
7039
                $s .= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7040
            }
7041
7042
            if ($show_comment) {
7043
                if (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_COMBINATION, UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION, GLOBAL_MULTIPLE_ANSWER))) {
7044
                    $header = Display::tag('th', get_lang('Options'));
7045
                    if ($exercise_feedback == EXERCISE_FEEDBACK_TYPE_END) {
7046
                        $header .= Display::tag('th', get_lang('Feedback'));
7047
                    }
7048
                    $s .= '<table class="data_table">';
7049
                    $s.= Display::tag('tr', $header, array('style' => 'text-align:left;'));
7050
                }
7051
            }
7052
7053
            $matching_correct_answer = 0;
7054
            $user_choice_array = array();
7055
            if (!empty($user_choice)) {
7056
                foreach ($user_choice as $item) {
7057
                    $user_choice_array[] = $item['answer'];
7058
                }
7059
            }
7060
7061
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7062
                $answer = $objAnswerTmp->selectAnswer($answerId);
7063
                $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7064
                $comment = $objAnswerTmp->selectComment($answerId);
7065
7066
                //$numAnswer       = $objAnswerTmp->selectAutoId($answerId);
7067
                $numAnswer = $answerId;
7068
7069
                $attributes = array();
7070
                // Unique answer
7071
                if (in_array($answerType, array(UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION))) {
7072
7073
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7074
                    if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) {
7075
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7076
                    } else {
7077
                        $attributes = array('id' => $input_id);
7078
                    }
7079
7080
                    if ($debug_mark_answer) {
7081
                        if ($answerCorrect) {
7082
                            $attributes['checked'] = 1;
7083
                            $attributes['selected'] = 1;
7084
                        }
7085
                    }
7086
7087
                    $answer = Security::remove_XSS($answer);
7088
                    $s .= Display::input('hidden', 'choice2['.$questionId.']', '0');
7089
7090
                    $answer_input = null;
7091
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7092
                        $attributes['style'] = 'display:none';
7093
                        $answer_input .= '<div id="answer'.$questionId.$numAnswer.'" style="float:left" class="highlight_image_default highlight_image">';
7094
                    }
7095
7096
                    $answer_input .= '<label class="radio">';
7097
                    $answer_input .= Display::input('radio', 'choice['.$questionId.']', $numAnswer, $attributes);
7098
                    $answer_input .= $answer;
7099
                    $answer_input .= '</label>';
7100
7101
                    if ($answerType == UNIQUE_ANSWER_IMAGE) {
7102
                        $answer_input .= "</div>";
7103
                    }
7104
7105
                    if ($show_comment) {
7106
                        $s .= '<tr><td>';
7107
                        $s .= $answer_input;
7108
                        $s .= '</td>';
7109
                        $s .= '<td>';
7110
                        $s .= $comment;
7111
                        $s .= '</td>';
7112
                        $s .= '</tr>';
7113
                    } else {
7114
                        $s .= $answer_input;
7115
                    }
7116
7117
                } elseif (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_TRUE_FALSE, GLOBAL_MULTIPLE_ANSWER))) {
7118
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7119
                    $answer = Security::remove_XSS($answer);
7120
7121
                    if (in_array($numAnswer, $user_choice_array)) {
7122
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7123
                    } else {
7124
                        $attributes = array('id' => $input_id);
7125
                    }
7126
7127
                    if ($debug_mark_answer) {
7128
                        if ($answerCorrect) {
7129
                            $attributes['checked'] = 1;
7130
                            $attributes['selected'] = 1;
7131
                        }
7132
                    }
7133
7134
                    if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) {
7135
                        $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7136
7137
                        $answer_input = '<label class="checkbox">';
7138
                        $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', $numAnswer, $attributes);
7139
                        $answer_input .= $answer;
7140
                        $answer_input .= '</label>';
7141
7142
                        if ($show_comment) {
7143
                            $s .= '<tr><td>';
7144
                            $s .= $answer_input;
7145
                            $s .= '</td>';
7146
                            $s .= '<td>';
7147
                            $s .= $comment;
7148
                            $s .= '</td>';
7149
                            $s .='</tr>';
7150
                        } else {
7151
                            $s .= $answer_input;
7152
                        }
7153
                    } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) {
7154
7155
                        $my_choice = array();
7156
                        if (!empty($user_choice_array)) {
7157
                            foreach ($user_choice_array as $item) {
7158
                                $item = explode(':', $item);
7159
                                $my_choice[$item[0]] = $item[1];
7160
                            }
7161
                        }
7162
7163
                        $s .='<tr>';
7164
                        $s .= Display::tag('td', $answer);
7165
7166
                        if (!empty($quiz_question_options)) {
7167
                            foreach ($quiz_question_options as $id => $item) {
7168
                                $id = $item['iid'];
7169
                                if (isset($my_choice[$numAnswer]) && $id == $my_choice[$numAnswer]) {
7170
                                    $attributes = array('checked' => 1, 'selected' => 1);
7171
                                } else {
7172
                                    $attributes = array();
7173
                                }
7174
7175
                                if ($debug_mark_answer) {
7176
                                    if ($id == $answerCorrect) {
7177
                                        $attributes['checked'] = 1;
7178
                                        $attributes['selected'] = 1;
7179
                                    }
7180
                                }
7181
                                $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $id, $attributes), array('style' => ''));
7182
                            }
7183
                        }
7184
7185
                        if ($show_comment) {
7186
                            $s .= '<td>';
7187
                            $s .= $comment;
7188
                            $s .= '</td>';
7189
                        }
7190
                        $s.='</tr>';
7191
                    }
7192
7193
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
7194
7195
                    // multiple answers
7196
                    $input_id = 'choice-'.$questionId.'-'.$answerId;
7197
7198
                    if (in_array($numAnswer, $user_choice_array)) {
7199
                        $attributes = array('id' => $input_id, 'checked' => 1, 'selected' => 1);
7200
                    } else {
7201
                        $attributes = array('id' => $input_id);
7202
                    }
7203
7204
                    if ($debug_mark_answer) {
7205
                        if ($answerCorrect) {
7206
                            $attributes['checked'] = 1;
7207
                            $attributes['selected'] = 1;
7208
                        }
7209
                    }
7210
7211
                    $answer = Security::remove_XSS($answer);
7212
                    $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7213
                    $answer_input .= '<label class="checkbox">';
7214
                    $answer_input .= Display::input('checkbox', 'choice['.$questionId.']['.$numAnswer.']', 1, $attributes);
7215
                    $answer_input .= $answer;
7216
                    $answer_input .= '</label>';
7217
7218
                    if ($show_comment) {
7219
                        $s.= '<tr>';
7220
                        $s .= '<td>';
7221
                        $s.= $answer_input;
7222
                        $s .= '</td>';
7223
                        $s .= '<td>';
7224
                        $s .= $comment;
7225
                        $s .= '</td>';
7226
                        $s.= '</tr>';
7227
                    } else {
7228
                        $s.= $answer_input;
7229
                    }
7230
                } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7231
                    $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />';
7232
7233
                    $my_choice = array();
7234
                    if (!empty($user_choice_array)) {
7235
                        foreach ($user_choice_array as $item) {
7236
                            $item = explode(':', $item);
7237
                            $my_choice[$item[0]] = $item[1];
7238
                        }
7239
                    }
7240
                    $answer = Security::remove_XSS($answer);
7241
                    $s .='<tr>';
7242
                    $s .= Display::tag('td', $answer);
7243
7244
                    foreach ($objQuestionTmp->options as $key => $item) {
7245
                        if (isset($my_choice[$numAnswer]) && $key == $my_choice[$numAnswer]) {
7246
                            $attributes = array('checked' => 1, 'selected' => 1);
7247
                        } else {
7248
                            $attributes = array();
7249
                        }
7250
7251
                        if ($debug_mark_answer) {
7252
                            if ($key == $answerCorrect) {
7253
                                $attributes['checked'] = 1;
7254
                                $attributes['selected'] = 1;
7255
                            }
7256
                        }
7257
                        $s .= Display::tag('td', Display::input('radio', 'choice['.$questionId.']['.$numAnswer.']', $key, $attributes));
7258
                    }
7259
7260
                    if ($show_comment) {
7261
                        $s .= '<td>';
7262
                        $s .= $comment;
7263
                        $s .= '</td>';
7264
                    }
7265
                    $s.='</tr>';
7266
                } elseif ($answerType == FILL_IN_BLANKS) {
7267
                    list($answer) = explode('::', $answer);
7268
7269
                    //Correct answer
7270
                    api_preg_match_all('/\[[^]]+\]/', $answer, $correct_answer_list);
7271
7272
                    //Student's answezr
7273
                    if (isset($user_choice[0]['answer'])) {
7274
                        api_preg_match_all('/\[[^]]+\]/', $user_choice[0]['answer'], $student_answer_list);
7275
                        $student_answer_list = $student_answer_list[0];
7276
                    }
7277
7278
                    //If debug
7279
                    if ($debug_mark_answer) {
7280
                        $student_answer_list = $correct_answer_list[0];
7281
                    }
7282
7283
                    if (!empty($correct_answer_list) && !empty($student_answer_list)) {
7284
                        $correct_answer_list = $correct_answer_list[0];
7285
                        $i = 0;
7286
                        foreach ($correct_answer_list as $correct_item) {
7287
                            $value = null;
7288
                            if (isset($student_answer_list[$i]) && !empty($student_answer_list[$i])) {
7289
7290
                                //Cleaning student answer list
7291
                                $value = strip_tags($student_answer_list[$i]);
7292
                                $value = api_substr($value, 1, api_strlen($value) - 2);
7293
                                $value = explode('/', $value);
7294
7295
                                if (!empty($value[0])) {
7296
                                    $value = str_replace('&nbsp;', '', trim($value[0]));
7297
                                }
7298
                                $correct_item = preg_quote($correct_item);
7299
                                $correct_item = api_preg_replace('|/|', '\/', $correct_item);   // to prevent error if there is a / in the text to find
7300
                                $answer = api_preg_replace('/'.$correct_item.'/', Display::input('text', "choice[$questionId][]", $value), $answer, 1);
7301
                            }
7302
                            $i++;
7303
                        }
7304
                    } else {
7305
                        $answer = api_preg_replace('/\[[^]]+\]/', Display::input('text', "choice[$questionId][]", '', $attributes), $answer);
7306
                    }
7307
                    $s .= $answer;
7308
                } elseif ($answerType == MATCHING) {
7309
                    // matching type, showing suggestions and answers
7310
                    // TODO: replace $answerId by $numAnswer
7311
7312
                    if ($lines_count == 1) {
7313
                        $s .= $objAnswerTmp->getJs();
7314
                    }
7315
                    if ($answerCorrect != 0) {
7316
                        // only show elements to be answered (not the contents of
7317
                        // the select boxes, who are correct = 0)
7318
                        $s .= '<tr><td width="45%">';
7319
                        $parsed_answer = $answer;
7320
                        $windowId = $questionId.'_'.$lines_count;
7321
                        //left part questions
7322
                        $s .= ' <div id="window_'.$windowId.'" class="window window_left_question window'.$questionId.'_question">
7323
                                    <b>'.$lines_count.'</b>.&nbsp'.$parsed_answer.'
7324
                                </div>
7325
                                </td>';
7326
7327
                        // middle part (matches selects)
7328
7329
                        $s .= '<td width="10%" align="center">&nbsp;&nbsp;';
7330
                        $s .= '<div style="display:block">';
7331
7332
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']">';
7333
                        $selectedValue = 0;
7334
                        // fills the list-box
7335
                        $item = 0;
7336
                        foreach ($select_items as $val) {
7337
                            // set $debug_mark_answer to true at public static function start to
7338
                            // show the correct answer with a suffix '-x'
7339
                            $selected = '';
7340
                            if ($debug_mark_answer) {
7341
                                if ($val['id'] == $answerCorrect) {
7342
                                    $selected = 'selected="selected"';
7343
                                    $selectedValue = $val['id'];
7344
                                }
7345
                            }
7346
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7347
                                $selected = 'selected="selected"';
7348
                                $selectedValue = $val['id'];
7349
                            }
7350
                            //$s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>';
7351
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7352
                            $item++;
7353
                        }
7354
7355
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7356
                            $s.= '<script>
7357
                                jsPlumb.ready(function() {
7358
                                    jsPlumb.connect({
7359
                                        source: "window_'.$windowId.'",
7360
                                        target: "window_'.$questionId.'_'.$selectedValue.'_answer",
7361
                                        endpoint:["Blank", { radius:15 }],
7362
                                        anchor:["RightMiddle","LeftMiddle"],
7363
                                        paintStyle:{ strokeStyle:"#8a8888" , lineWidth:8 },
7364
                                        connector: [connectorType, { curviness: curvinessValue } ],
7365
                                    })
7366
                                });
7367
                                </script>';
7368
                        }
7369
                        $s .= '</select></div></td>';
7370
7371
                        $s.='<td width="45%" valign="top" >';
7372
7373
                        if (isset($select_items[$lines_count])) {
7374
                            $s.= '<div id="window_'.$windowId.'_answer" class="window window_right_question">
7375
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7376
                                  </div>';
7377
                        } else {
7378
                            $s.='&nbsp;';
7379
                        }
7380
7381
                        $s .= '</td>';
7382
                        $s .= '</tr>';
7383
                        $lines_count++;
7384
                        //if the left side of the "matching" has been completely
7385
                        // shown but the right side still has values to show...
7386
                        if (($lines_count - 1) == $num_suggestions) {
7387
                            // if it remains answers to shown at the right side
7388
                            while (isset($select_items[$lines_count])) {
7389
                                $s .= '<tr>
7390
                                      <td colspan="2"></td>
7391
                                      <td valign="top">';
7392
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7393
                                $s .= $select_items[$lines_count]['answer'];
7394
                                $s.="</td>
7395
                                </tr>";
7396
                                $lines_count++;
7397
                            } // end while()
7398
                        }  // end if()
7399
                        $matching_correct_answer++;
7400
                    }
7401
                } elseif ($answerType ==  DRAGGABLE) {
7402
                    // matching type, showing suggestions and answers
7403
                    // TODO: replace $answerId by $numAnswer
7404
7405
                    if ($answerCorrect != 0) {
7406
                        // only show elements to be answered (not the contents of
7407
                        // the select boxes, who are correct = 0)
7408
                        $s .= '<td>';
7409
                        $parsed_answer = $answer;
7410
                        $windowId = $questionId.'_'.$numAnswer; //67_293 - 67_294
7411
7412
                        //left part questions
7413
                        $s .= '<li class="ui-state-default" id="'.$windowId.'">';
7414
                        $s .= ' <div id="window_'.$windowId.'" class="window'.$questionId.'_question_draggable question_draggable">
7415
                                   '.$parsed_answer.'
7416
                                </div>';
7417
7418
                        $s .= '<div style="display:none">';
7419
                        $s .= '<select id="window_'.$windowId.'_select" name="choice['.$questionId.']['.$numAnswer.']" class="select_option">';
7420
                        $selectedValue = 0;
7421
                        // fills the list-box
7422
                        $item = 0;
7423
                        foreach ($select_items as $val) {
7424
                            // set $debug_mark_answer to true at function start to
7425
                            // show the correct answer with a suffix '-x'
7426
                            $selected = '';
7427
                            if ($debug_mark_answer) {
7428
                                if ($val['id'] == $answerCorrect) {
7429
                                    $selected = 'selected="selected"';
7430
                                    $selectedValue = $val['id'];
7431
                                }
7432
                            }
7433
                            if (isset($user_choice[$matching_correct_answer]) && $val['id'] == $user_choice[$matching_correct_answer]['answer']) {
7434
                                $selected = 'selected="selected"';
7435
                                $selectedValue = $val['id'];
7436
                            }
7437
                            $s .= '<option value="'.$item.'" '.$selected.'>'.$val['letter'].'</option>';
7438
                            $item++;
7439
                        }
7440
                        $s .= '</select>';
7441
7442
                        if (!empty($answerCorrect) && !empty($selectedValue)) {
7443
                            $s.= '<script>
7444
                                $(function() {
7445
                                    deleteItem($("#'.$questionId.'_'.$selectedValue.'"), $("#drop_'.$windowId.'"));
7446
                                });
7447
                                </script>';
7448
                        }
7449
7450
                        if (isset($select_items[$lines_count])) {
7451
                            $s.= '<div id="window_'.$windowId.'_answer" class="">
7452
                                    <b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'].'
7453
                                  </div>';
7454
                        } else {
7455
                            $s.='&nbsp;';
7456
                        }
7457
                        $lines_count++;
7458
                        //if the left side of the "matching" has been completely
7459
                        // shown but the right side still has values to show...
7460
7461
                        if (($lines_count - 1) == $num_suggestions) {
7462
                            // if it remains answers to shown at the right side
7463
                            while (isset($select_items[$lines_count])) {
7464
                                $s.='<b>'.$select_items[$lines_count]['letter'].'.</b>';
7465
                                $s .= $select_items[$lines_count]['answer'];
7466
                                $lines_count++;
7467
                            }
7468
                        }
7469
                        $s .= '</div>';
7470
                        $matching_correct_answer++;
7471
                        $s .= '</li>';
7472
                    }
7473
                }
7474
            } // end for()
7475
7476
            if ($show_comment) {
7477
                $s .= '</table>';
7478
            } else {
7479
                if ($answerType == MATCHING || $answerType == UNIQUE_ANSWER_NO_OPTION || $answerType == MULTIPLE_ANSWER_TRUE_FALSE ||
7480
                    $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) {
7481
                    $s .= '</table>';
7482
                }
7483
            }
7484
7485
            if ($answerType == DRAGGABLE) {
7486
                $s .= '</ul><div class="clear"></div>';
7487
7488
                $counterAnswer = 1;
7489
                foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7490
                    $answerCorrect = $objAnswerTmp->isCorrect($answerId);
7491
                    $windowId = $questionId.'_'.$counterAnswer;
7492
                    if ($answerCorrect == 0) {
7493
                        $s .= '<div id="drop_'.$windowId.'" class="droppable ui-state-default">'.$counterAnswer.'</div>';
7494
                        $counterAnswer++;
7495
                    }
7496
                }
7497
            }
7498
7499
            if ($answerType == MATCHING) {
7500
                $s .= '</div>';
7501
            }
7502
7503
            $s .= '</div>';
7504
7505
            // destruction of the Answer object
7506
            unset($objAnswerTmp);
7507
7508
            // destruction of the Question object
7509
            unset($objQuestionTmp);
7510
7511
            $html .= $s;
7512
            return $html;
7513
        } elseif ($answerType == HOT_SPOT || $answerType == HOT_SPOT_DELINEATION) {
7514
            // Question is a HOT_SPOT
7515
            //checking document/images visibility
7516
            if (api_is_platform_admin() || api_is_course_admin()) {
7517
                $course = api_get_course_info();
7518
                $doc_id = DocumentManager::get_document_id($course, '/images/'.$pictureName);
7519
                if (is_numeric($doc_id)) {
7520
                    $images_folder_visibility = api_get_item_visibility($course, 'document', $doc_id, api_get_session_id());
7521
                    if (!$images_folder_visibility) {
7522
                        //This message is shown only to the course/platform admin if the image is set to visibility = false
7523
                        Display::display_warning_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'));
7524
                    }
7525
                }
7526
            }
7527
            $questionName = $objQuestionTmp->selectTitle();
7528
            $questionDescription = $objQuestionTmp->selectDescription();
7529
7530
            if ($freeze) {
7531
                $s .= Display::img($objQuestionTmp->selectPicturePath());
7532
                $html .= $s;
7533
                return $html;
7534
            }
7535
7536
            // Get the answers, make a list
7537
            $objAnswerTmp = new Answer($questionId);
7538
7539
            // get answers of hotpost
7540
            $answers_hotspot = array();
7541
            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
7542
                //$answers = $objAnswerTmp->selectAnswerByAutoId($objAnswerTmp->selectAutoId($answerId));
7543
                $answers_hotspot[$answerId] = $objAnswerTmp->selectAnswer($answerId);
7544
            }
7545
7546
            // display answers of hotpost order by id
7547
            $answer_list = '<div style="padding: 10px; margin-left: 0px; border: 1px solid #A4A4A4; height: 408px; width: 200px;"><b>'.get_lang('HotspotZones').'</b><dl>';
7548
            if (!empty($answers_hotspot)) {
7549
                ksort($answers_hotspot);
7550
                foreach ($answers_hotspot as $key => $value) {
7551
                    $answer_list .= '<dt>'.$key.'.- '.$value.'</dt><br />';
7552
                }
7553
            }
7554
            $answer_list .= '</dl></div>';
7555
7556
            if ($answerType == HOT_SPOT_DELINEATION) {
7557
                $answer_list = '';
7558
                $swf_file = 'hotspot_delineation_user';
7559
                $swf_height = 405;
7560
            } else {
7561
                $swf_file = 'hotspot_user';
7562
                $swf_height = 436;
7563
            }
7564
7565
            if (!$only_questions) {
7566
                if ($show_title) {
7567
                    $html .=  TestCategory::getCategoryNamesForQuestion($objQuestionTmp->id);
7568
                    $html .=  '<div class="question_title">'.$current_item.'. '.$questionName.'</div>';
7569
                    $html .=  $questionDescription;
7570
                } else {
7571
                    $html .= '<div class="media">';
7572
                    $html .= '<div class="pull-left">';
7573
                    $html .= '<div class="media-object">';
7574
                    $html .= Display::div($current_item.'. ', array('class' => 'question_no_title'));
7575
                    $html .= '</div>';
7576
                    $html .= '</div>';
7577
                    $html .= '<div class="media-body">';
7578
                    if (!empty($questionDescription)) {
7579
                        $html .= Display::div($questionDescription, array('class' => 'question_description'));
7580
                    }
7581
                    $html .= '</div>';
7582
                    $html .= '</div>';
7583
                }
7584
                //@todo I need to the get the feedback type
7585
                $html .=  '<input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" />';
7586
                $html .=  '<table class="exercise_questions">
7587
                           <tr>
7588
                            <td valign="top" colspan="2">';
7589
                $html .=  '</td></tr>';
7590
            }
7591
7592
            $canClick = isset($_GET['editQuestion']) ? '0' : (isset($_GET['modifyAnswers']) ? '0' : '1');
7593
7594
            $s .= ' <script type="text/javascript" src="../plugin/hotspot/JavaScriptFlashGateway.js"></script>
7595
                    <script src="../plugin/hotspot/hotspot.js" type="text/javascript" ></script>
7596
                    <script type="text/javascript">
7597
                    <!--
7598
                    // Globals
7599
                    // Major version of Flash required
7600
                    var requiredMajorVersion = 7;
7601
                    // Minor version of Flash required
7602
                    var requiredMinorVersion = 0;
7603
                    // Minor version of Flash required
7604
                    var requiredRevision = 0;
7605
                    // the version of javascript supported
7606
                    var jsVersion = 1.0;
7607
                    // -->
7608
                    </script>
7609
                    <script language="VBScript" type="text/vbscript">
7610
                    <!-- // Visual basic helper required to detect Flash Player ActiveX control version information
7611
                    Function VBGetSwfVer(i)
7612
                      on error resume next
7613
                      Dim swControl, swVersion
7614
                      swVersion = 0
7615
7616
                      set swControl = CreateObject("ShockwaveFlash.ShockwaveFlash." + CStr(i))
7617
                      if (IsObject(swControl)) then
7618
                        swVersion = swControl.GetVariable("$version")
7619
                      end if
7620
                      VBGetSwfVer = swVersion
7621
                    End Function
7622
                    // -->
7623
                    </script>
7624
7625
                    <script language="JavaScript1.1" type="text/javascript">
7626
                    <!-- // Detect Client Browser type
7627
                    var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
7628
                    var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
7629
                    var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
7630
                    jsVersion = 1.1;
7631
                    // JavaScript helper required to detect Flash Player PlugIn version information
7632
                    function JSGetSwfVer(i) {
7633
                        // NS/Opera version >= 3 check for Flash plugin in plugin array
7634
                        if (navigator.plugins != null && navigator.plugins.length > 0) {
7635
                            if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
7636
                                var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
7637
                                var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
7638
                                descArray = flashDescription.split(" ");
7639
                                tempArrayMajor = descArray[2].split(".");
7640
                                versionMajor = tempArrayMajor[0];
7641
                                versionMinor = tempArrayMajor[1];
7642
                                if ( descArray[3] != "" ) {
7643
                                    tempArrayMinor = descArray[3].split("r");
7644
                                } else {
7645
                                    tempArrayMinor = descArray[4].split("r");
7646
                                }
7647
                                versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;
7648
                                flashVer = versionMajor + "." + versionMinor + "." + versionRevision;
7649
                            } else {
7650
                                flashVer = -1;
7651
                            }
7652
                        }
7653
                        // MSN/WebTV 2.6 supports Flash 4
7654
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4;
7655
                        // WebTV 2.5 supports Flash 3
7656
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3;
7657
                        // older WebTV supports Flash 2
7658
                        else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2;
7659
                        // Can\'t detect in all other cases
7660
                        else {
7661
                            flashVer = -1;
7662
                        }
7663
                        return flashVer;
7664
                    }
7665
                    // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available
7666
7667
                    function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) {
7668
                        reqVer = parseFloat(reqMajorVer + "." + reqRevision);
7669
                        // loop backwards through the versions until we find the newest version
7670
                        for (i=25;i>0;i--) {
7671
                            if (isIE && isWin && !isOpera) {
7672
                                versionStr = VBGetSwfVer(i);
7673
                            } else {
7674
                                versionStr = JSGetSwfVer(i);
7675
                            }
7676
                            if (versionStr == -1 ) {
7677
                                return false;
7678
                            } else if (versionStr != 0) {
7679
                                if(isIE && isWin && !isOpera) {
7680
                                    tempArray         = versionStr.split(" ");
7681
                                    tempString        = tempArray[1];
7682
                                    versionArray      = tempString .split(",");
7683
                                } else {
7684
                                    versionArray      = versionStr.split(".");
7685
                                }
7686
                                versionMajor      = versionArray[0];
7687
                                versionMinor      = versionArray[1];
7688
                                versionRevision   = versionArray[2];
7689
7690
                                versionString     = versionMajor + "." + versionRevision;   // 7.0r24 == 7.24
7691
                                versionNum        = parseFloat(versionString);
7692
                                // is the major.revision >= requested major.revision AND the minor version >= requested minor
7693
                                if ( (versionMajor > reqMajorVer) && (versionNum >= reqVer) ) {
7694
                                    return true;
7695
                                } else {
7696
                                    return ((versionNum >= reqVer && versionMinor >= reqMinorVer) ? true : false );
7697
                                }
7698
                            }
7699
                        }
7700
                    }
7701
                    // -->
7702
                    </script>';
7703
            $s .= '<tr><td valign="top" colspan="2" width="520"><table><tr><td width="520">
7704
                    <script>
7705
                        // Version check based upon the values entered above in "Globals"
7706
                        var hasReqestedVersion = DetectFlashVer(requiredMajorVersion, requiredMinorVersion, requiredRevision);
7707
7708
                        // Check to see if the version meets the requirements for playback
7709
                        if (hasReqestedVersion) {  // if we\'ve detected an acceptable version
7710
                            var oeTags = \'<object type="application/x-shockwave-flash" data="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" width="600" height="'.$swf_height.'">\'
7711
                                        + \'<param name="wmode" value="transparent">\'
7712
                                        + \'<param name="movie" value="../plugin/hotspot/'.$swf_file.'.swf?modifyAnswers='.$questionId.'&amp;canClick:'.$canClick.'" />\'
7713
                                        + \'<\/object>\';
7714
                            document.write(oeTags);   // embed the Flash Content SWF when all tests are passed
7715
                        } else {  // flash is too old or we can\'t detect the plugin
7716
                            var alternateContent = "Error<br \/>"
7717
                                + "Hotspots requires Macromedia Flash 7.<br \/>"
7718
                                + "<a href=\"http://www.macromedia.com/go/getflash/\">Get Flash<\/a>";
7719
                            document.write(alternateContent);  // insert non-flash content
7720
                        }
7721
                    </script>
7722
                    </td>
7723
                    <td valign="top" align="left">'.$answer_list.'</td></tr>
7724
                    </table>
7725
            </td></tr>';
7726
            $html .= $s;
7727
            $html .= '</table>';
7728
            return $html;
7729
        }
7730
        return $nbrAnswers;
7731
    }
7732
7733
    /**
7734
     * @param int $exeId
7735
     * @return array
7736
     */
7737
    public function returnQuestionListByAttempt($exeId)
7738
    {
7739
        return $this->displayQuestionListByAttempt($exeId, false, true);
7740
    }
7741
7742
    /**
7743
     * Display the exercise results
7744
     * @param int  $exe_id
7745
     * @param bool $saveUserResult save users results (true) or just show the results (false)
7746
     * @param bool $returnExerciseResult return array with exercise result info
7747
     * @return mixed
7748
     */
7749
    public function displayQuestionListByAttempt($exe_id, $saveUserResult = false, $returnExerciseResult = false)
7750
    {
7751
        global $origin, $debug;
7752
7753
        //Getting attempt info
7754
        $exercise_stat_info = $this->getStatTrackExerciseInfoByExeId($exe_id);
7755
7756
        //Getting question list
7757
        $question_list = array();
7758
        if (!empty($exercise_stat_info['data_tracking'])) {
7759
            $question_list = explode(',', $exercise_stat_info['data_tracking']);
7760
        } else {
7761
            //Try getting the question list only if save result is off
7762
            if ($saveUserResult == false) {
7763
                $question_list = $this->selectQuestionList();
7764
            }
7765
            error_log("Data tracking is empty! exe_id: $exe_id");
7766
        }
7767
7768
        $counter = 1;
7769
        $total_score = 0;
7770
        $total_weight = 0;
7771
7772
        $exercise_content = null;
7773
7774
        // Hide results
7775
        $show_results = false;
7776
        $show_only_score = false;
7777
7778
        if ($this->results_disabled == RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS) {
7779
            $show_results = true;
7780
        }
7781
7782
        $showScoreOptions = [
7783
            RESULT_DISABLE_SHOW_SCORE_ONLY,
7784
            RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES,
7785
            RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT
7786
        ];
7787
7788
        if (in_array($this->results_disabled, $showScoreOptions)) {
7789
            $show_only_score = true;
7790
        }
7791
7792
        if ($show_results || $show_only_score) {
7793
            $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
7794
            // Shows exercise header.
7795
            echo $this->show_exercise_result_header(
7796
                $user_info['complete_name'],
7797
                api_convert_and_format_date($exercise_stat_info['start_date'], DATE_TIME_FORMAT_LONG),
7798
                $exercise_stat_info['duration']
7799
            );
7800
        }
7801
7802
        // Display text when test is finished #4074 and for LP #4227
7803
        $end_of_message = $this->selectTextWhenFinished();
7804
        if (!empty($end_of_message)) {
7805
            Display::display_normal_message($end_of_message, false);
7806
            echo "<div class='clear'>&nbsp;</div>";
7807
        }
7808
7809
        $question_list_answers = array();
7810
        $media_list = array();
7811
        $category_list = array();
7812
        $tempParentId = null;
7813
        $mediaCounter = 0;
7814
7815
        $exerciseResultInfo = array();
7816
7817
        // Loop over all question to show results for each of them, one by one
7818
        if (!empty($question_list)) {
7819
            if ($debug) {
7820
                error_log('Looping question_list '.print_r($question_list, 1));
7821
            }
7822
7823
            foreach ($question_list as $questionId) {
7824
7825
                // Creates a temporary Question object
7826
                $objQuestionTmp = Question::read($questionId);
7827
7828
                // This variable commes from exercise_submit_modal.php
7829
                ob_start();
7830
                $hotspot_delineation_result = null;
7831
7832
                // We're inside *one* question. Go through each possible answer for this question
7833
                $result = $this->manageAnswers(
7834
                    $exercise_stat_info['exe_id'],
7835
                    $questionId,
7836
                    null,
7837
                    'exercise_result',
7838
                    array(),
7839
                    $saveUserResult,
7840
                    true,
7841
                    $show_results,
7842
                    $hotspot_delineation_result
7843
                );
7844
7845
                if (empty($result)) {
7846
                    continue;
7847
                }
7848
7849
                $total_score += $result['score'];
7850
                $total_weight += $result['weight'];
7851
7852
                $question_list_answers[] = array(
7853
                    'question' => $result['open_question'],
7854
                    'answer' => $result['open_answer'],
7855
                    'answer_type' => $result['answer_type']
7856
                );
7857
7858
                $my_total_score = $result['score'];
7859
                $my_total_weight = $result['weight'];
7860
7861
                // Category report
7862
                $category_was_added_for_this_test = false;
7863
                $categoryExerciseList = $this->getListOfCategoriesWithQuestionForTest();
7864
7865
                $category_list = array();
7866
                if (isset($categoryExerciseList) && !empty($categoryExerciseList)) {
7867
                    foreach ($categoryExerciseList as $category_id => $categoryInfo) {
7868
                        if (!isset($category_list[$category_id])) {
7869
                            $category_list[$category_id] = array();
7870
                            $category_list[$category_id]['score'] = 0;
7871
                            $category_list[$category_id]['total'] = 0;
7872
                        }
7873
                        $category_list[$category_id]['score'] += $my_total_score;
7874
                        $category_list[$category_id]['total'] += $my_total_weight;
7875
                        $category_was_added_for_this_test = true;
7876
                    }
7877
                }
7878
7879
                // No category for this question!
7880
                if ($category_was_added_for_this_test == false) {
7881
                    if (!isset($category_list['none'])) {
7882
                        $category_list['none'] = array();
7883
                        $category_list['none']['score'] = 0;
7884
                        $category_list['none']['total'] = 0;
7885
                    }
7886
7887
                    $category_list['none']['score'] += $my_total_score;
7888
                    $category_list['none']['total'] += $my_total_weight;
7889
                }
7890
7891
                if ($this->selectPropagateNeg() == 0 && $my_total_score < 0) {
7892
                    $my_total_score = 0;
7893
                }
7894
7895
                $comnt = null;
7896
                if ($show_results) {
7897
                    $comnt = get_comments($exe_id, $questionId);
7898
                    if (!empty($comnt)) {
7899
                        echo '<b>'.get_lang('Feedback').'</b>';
7900
                        echo '<div id="question_feedback">'.$comnt.'</div>';
7901
                    }
7902
                }
7903
7904
                $score = array();
7905
                $score['result'] = get_lang('Score')." : ".ExerciseLib::show_score($my_total_score, $my_total_weight, false, true);
7906
                $score['pass'] = $my_total_score >= $my_total_weight ? true : false;
7907
                $score['score'] = $my_total_score;
7908
                $score['weight'] = $my_total_weight;
7909
                $score['comments'] = $comnt;
7910
7911
                $exerciseResultInfo[$questionId]['score'] = $score;
7912
                $exerciseResultInfo[$questionId]['details'] = $result;
7913
7914
                // If no results we hide the results
7915
                if ($show_results == false) {
7916
                    $score = array();
7917
                }
7918
                $contents = ob_get_clean();
7919
7920
                $question_content = '<div class="question_row">';
7921
7922
                if ($show_results) {
7923
7924
                    $show_media = false;
7925
                    $counterToShow = $counter;
7926
                    if ($objQuestionTmp->parent_id != 0) {
7927
7928
                        if (!in_array($objQuestionTmp->parent_id, $media_list)) {
7929
                            $media_list[] = $objQuestionTmp->parent_id;
7930
                            $show_media = true;
7931
                        }
7932
                        if ($tempParentId == $objQuestionTmp->parent_id) {
7933
                            $mediaCounter++;
7934
                        } else {
7935
                            $mediaCounter = 0;
7936
                        }
7937
                        $counterToShow = chr(97 + $mediaCounter);
7938
                        $tempParentId = $objQuestionTmp->parent_id;
7939
                    }
7940
7941
                    // Shows question title an description.
7942
                    $question_content .= $objQuestionTmp->return_header(null, $counterToShow, $score, $show_media, $this->getHideQuestionTitle());
7943
7944
                    // display question category, if any
7945
                    $question_content .= TestCategory::getCategoryNamesForQuestion($questionId, null, true, $this->categoryMinusOne);
7946
                }
7947
                $counter++;
7948
7949
                $question_content .= $contents;
7950
                $question_content .= '</div>';
7951
7952
                $exercise_content .= $question_content;
7953
            } // end foreach() block that loops over all questions
7954
        }
7955
7956
        $total_score_text = null;
7957
7958
        if ($returnExerciseResult) {
7959
            return $exerciseResultInfo;
7960
        }
7961
7962
        if ($origin != 'learnpath') {
7963
            if ($show_results || $show_only_score) {
7964
                $total_score_text .= $this->get_question_ribbon($total_score, $total_weight, true);
7965
            }
7966
        }
7967
7968
        if (!empty($category_list) && ($show_results || $show_only_score)) {
7969
            //Adding total
7970
            $category_list['total'] = array('score' => $total_score, 'total' => $total_weight);
7971
            echo TestCategory::get_stats_table_by_attempt($this->id, $category_list, $this->categoryMinusOne);
7972
        }
7973
7974
        echo $total_score_text;
7975
        echo $exercise_content;
7976
7977
        if (!$show_only_score) {
7978
            echo $total_score_text;
7979
        }
7980
7981
        if ($saveUserResult) {
7982
7983
            // Tracking of results
7984
            $learnpath_id = $exercise_stat_info['orig_lp_id'];
7985
            $learnpath_item_id = $exercise_stat_info['orig_lp_item_id'];
7986
            $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
7987
7988
            if (api_is_allowed_to_session_edit()) {
7989
                update_event_exercise(
7990
                    $exercise_stat_info['exe_id'],
7991
                    $this->selectId(),
7992
                    $total_score,
7993
                    $total_weight,
7994
                    api_get_session_id(),
7995
                    $learnpath_id,
7996
                    $learnpath_item_id,
7997
                    $learnpath_item_view_id,
7998
                    $exercise_stat_info['exe_duration'],
7999
                    '',
8000
                    array()
8001
                );
8002
            }
8003
8004
            // Send notification.
8005
            if (!api_is_allowed_to_edit(null, true)) {
8006
                $isSuccess = ExerciseLib::is_success_exercise_result($total_score, $total_weight, $this->selectPassPercentage());
8007
                $this->sendCustomNotification($exe_id, $exerciseResultInfo, $isSuccess);
8008
                $this->sendNotificationForOpenQuestions($question_list_answers, $origin, $exe_id);
8009
                $this->sendNotificationForOralQuestions($question_list_answers, $origin, $exe_id);
8010
            }
8011
        }
8012
    }
8013
8014
    /**
8015
     * Returns an HTML ribbon to show on top of the exercise result, with
8016
     * colouring depending on the success or failure of the student
8017
     * @param $score
8018
     * @param $weight
8019
     * @param bool $check_pass_percentage
8020
     * @return string
8021
     */
8022
    public function get_question_ribbon($score, $weight, $check_pass_percentage = false)
8023
    {
8024
        $eventMessage = null;
8025
        $ribbon = '<div class="question_row">';
8026
        $ribbon .= '<div class="ribbon">';
8027
        if ($check_pass_percentage) {
8028
            $is_success = ExerciseLib::is_success_exercise_result($score, $weight, $this->selectPassPercentage());
8029
            // Color the final test score if pass_percentage activated
8030
            $ribbon_total_success_or_error = "";
8031
            if (ExerciseLib::is_pass_pourcentage_enabled($this->selectPassPercentage())) {
8032
                if ($is_success) {
8033
                    $eventMessage = $this->getOnSuccessMessage();
8034
                    $ribbon_total_success_or_error = ' ribbon-total-success';
8035
                } else {
8036
                    $eventMessage = $this->getOnFailedMessage();
8037
                    $ribbon_total_success_or_error = ' ribbon-total-error';
8038
                }
8039
            }
8040
            $ribbon .= '<div class="rib rib-total '.$ribbon_total_success_or_error.'">';
8041
        } else {
8042
            $ribbon .= '<div class="rib rib-total">';
8043
        }
8044
        $ribbon .= '<h3>'.get_lang('YourTotalScore').":&nbsp;";
8045
        $ribbon .= ExerciseLib::show_score($score, $weight, false, true);
8046
        $ribbon .= '</h3>';
8047
        $ribbon .= '</div>';
8048
8049
        if ($check_pass_percentage) {
8050
            $ribbon .= ExerciseLib::show_success_message($score, $weight, $this->selectPassPercentage());
8051
        }
8052
        $ribbon .= '</div>';
8053
        $ribbon .= '</div>';
8054
8055
        $ribbon .= $eventMessage;
8056
8057
        return $ribbon;
8058
    }
8059
8060
    /**
8061
     * Returns an array of categories details for the questions of the current
8062
     * exercise.
8063
     * @return array
8064
     */
8065
    public function getQuestionWithCategories()
8066
    {
8067
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
8068
        $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
8069
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
8070
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
8071
        $sql = "SELECT DISTINCT cat.*
8072
                FROM $TBL_EXERCICE_QUESTION e
8073
                INNER JOIN $TBL_QUESTIONS q
8074
                ON (e.question_id = q.id AND e.c_id = q.c_id)
8075
                INNER JOIN $categoryRelTable catRel
8076
                ON (catRel.question_id = e.question_id)
8077
                INNER JOIN $categoryTable cat
8078
                ON (cat.id = catRel.category_id)
8079
                WHERE
8080
                  e.c_id = {$this->course_id} AND
8081
                  e.exercice_id	= ".intval($this->id);
8082
8083
        $result = Database::query($sql);
8084
        $categoriesInExercise = array();
8085
        if (Database::num_rows($result)) {
8086
            $categoriesInExercise = Database::store_result($result, 'ASSOC');
8087
        }
8088
8089
        return $categoriesInExercise;
8090
    }
8091
8092
    /**
8093
     * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option
8094
     */
8095
    public function get_max_score()
8096
    {
8097
        $out_max_score = 0;
8098
        // list of question's id !!! the array key start at 1 !!!
8099
        $questionList = $this->selectQuestionList(true);
8100
8101
        // test is randomQuestions - see field random of test
8102
        if ($this->random > 0 && $this->randomByCat == 0) {
8103
            $numberRandomQuestions = $this->random;
8104
            $questionScoreList = array();
8105
            for ($i = 1; $i <= count($questionList); $i++) {
8106
                $tmpobj_question = Question::read($questionList[$i]);
8107
                $questionScoreList[] = $tmpobj_question->weighting;
8108
            }
8109
            rsort($questionScoreList);
8110
            // add the first $numberRandomQuestions value of score array to get max_score
8111
            for ($i = 0; $i < min($numberRandomQuestions, count($questionScoreList)); $i++) {
8112
                $out_max_score += $questionScoreList[$i];
8113
            }
8114
        } else if ($this->random > 0 && $this->randomByCat > 0) {
8115
            // test is random by category
8116
            // get the $numberRandomQuestions best score question of each category
8117
8118
            $numberRandomQuestions = $this->random;
8119
            $tab_categories_scores = array();
8120
            for ($i = 1; $i <= count($questionList); $i++) {
8121
                $question_category_id = TestCategory::getCategoryForQuestion($questionList[$i]);
8122
                if (!is_array($tab_categories_scores[$question_category_id])) {
8123
                    $tab_categories_scores[$question_category_id] = array();
8124
                }
8125
                $tmpobj_question = Question::read($questionList[$i]);
8126
                $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting;
8127
            }
8128
8129
            // here we've got an array with first key, the category_id, second key, score of question for this cat
8130
            while (list($key, $tab_scores) = each($tab_categories_scores)) {
8131
                rsort($tab_scores);
8132
                for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) {
8133
                    $out_max_score += $tab_scores[$i];
8134
                }
8135
            }
8136
        } else {
8137
            // standard test, just add each question score
8138
            foreach ($questionList as $questionId) {
8139
                $question = Question::read($questionId, $this->course_id);
8140
                $out_max_score += $question->weighting;
8141
            }
8142
        }
8143
8144
        return $out_max_score;
8145
    }
8146
8147
    /**
8148
    * @return string
8149
    */
8150
    public function get_formated_title()
8151
    {
8152
        return api_html_entity_decode($this->selectTitle());
8153
    }
8154
8155
    /**
8156
     * @param $in_title
8157
     * @return string
8158
     */
8159
    public static function get_formated_title_variable($in_title)
8160
    {
8161
        return api_html_entity_decode($in_title);
8162
    }
8163
8164
    /**
8165
     * @return string
8166
     */
8167
    public function format_title()
8168
    {
8169
        return api_htmlentities($this->title);
8170
    }
8171
8172
    /**
8173
     * @param $in_title
8174
     * @return string
8175
     */
8176
    public static function format_title_variable($in_title)
8177
    {
8178
        return api_htmlentities($in_title);
8179
    }
8180
8181
    /**
8182
     * @param int $courseId
8183
     * @param int $sessionId
8184
     * @return array exercises
8185
     */
8186
    public function getExercisesByCouseSession($courseId, $sessionId)
8187
    {
8188
        $courseId = intval($courseId);
8189
        $sessionId = intval($sessionId);
8190
8191
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8192
        $sql = "SELECT * FROM $tbl_quiz cq
8193
                WHERE
8194
                    cq.c_id = %s AND
8195
                    (cq.session_id = %s OR cq.session_id = 0) AND
8196
                    cq.active = 0
8197
                ORDER BY cq.id";
8198
        $sql = sprintf($sql, $courseId, $sessionId);
8199
8200
        $result = Database::query($sql);
8201
8202
        $rows = array();
8203
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8204
            $rows[] = $row;
8205
        }
8206
8207
        return $rows;
8208
    }
8209
8210
    /**
8211
     *
8212
     * @param int $courseId
8213
     * @param int $sessionId
8214
     * @param array $quizId
8215
     * @return array exercises
8216
     */
8217
    public function getExerciseAndResult($courseId, $sessionId, $quizId = array())
8218
    {
8219
        if (empty($quizId)) {
8220
            return array();
8221
        }
8222
8223
        $sessionId = intval($sessionId);
8224
8225
        $ids = is_array($quizId) ? $quizId : array($quizId);
8226
        $ids = array_map('intval', $ids);
8227
        $ids = implode(',', $ids);
8228
        $track_exercises = Database :: get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8229
        if ($sessionId != 0) {
8230
            $sql = "SELECT * FROM $track_exercises te
8231
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8232
              WHERE
8233
              te.id = %s AND
8234
              te.session_id = %s AND
8235
              cq.id IN (%s)
8236
              ORDER BY cq.id";
8237
8238
            $sql = sprintf($sql, $courseId, $sessionId, $ids);
8239
        } else {
8240
            $sql = "SELECT * FROM $track_exercises te
8241
              INNER JOIN c_quiz cq ON cq.id = te.exe_exo_id AND te.c_id = cq.c_id
8242
              WHERE
8243
              te.id = %s AND
8244
              cq.id IN (%s)
8245
              ORDER BY cq.id";
8246
            $sql = sprintf($sql, $courseId, $ids);
8247
        }
8248
        $result = Database::query($sql);
8249
        $rows = array();
8250
        while ($row = Database::fetch_array($result, 'ASSOC')) {
8251
            $rows[] = $row;
8252
        }
8253
8254
        return $rows;
8255
    }
8256
8257
    /**
8258
     * @param $exeId
8259
     * @param $exercise_stat_info
8260
     * @param $remindList
8261
     * @param $currentQuestion
8262
     * @return int|null
8263
     */
8264
    public static function getNextQuestionId($exeId, $exercise_stat_info, $remindList, $currentQuestion)
8265
    {
8266
        $result = get_exercise_results_by_attempt($exeId, 'incomplete');
8267
8268
        if (isset($result[$exeId])) {
8269
            $result = $result[$exeId];
8270
        } else {
8271
            return null;
8272
        }
8273
8274
        $data_tracking  = $exercise_stat_info['data_tracking'];
8275
        $data_tracking  = explode(',', $data_tracking);
8276
8277
        // if this is the final question do nothing.
8278
        if ($currentQuestion == count($data_tracking)) {
8279
            return null;
8280
        }
8281
8282
        $currentQuestion = $currentQuestion - 1;
8283
8284
        if (!empty($result['question_list'])) {
8285
            $answeredQuestions = array();
8286
8287
            foreach ($result['question_list'] as $question) {
8288
                if (!empty($question['answer'])) {
8289
                    $answeredQuestions[] = $question['question_id'];
8290
                }
8291
            }
8292
8293
            // Checking answered questions
8294
8295
            $counterAnsweredQuestions = 0;
8296
            foreach ($data_tracking as $questionId) {
8297
                if (!in_array($questionId, $answeredQuestions)) {
8298
                    if ($currentQuestion != $counterAnsweredQuestions) {
8299
                        break;
8300
                    }
8301
                }
8302
                $counterAnsweredQuestions++;
8303
            }
8304
8305
            $counterRemindListQuestions = 0;
8306
            // Checking questions saved in the reminder list
8307
8308
            if (!empty($remindList)) {
8309
                foreach ($data_tracking as $questionId) {
8310
                    if (in_array($questionId, $remindList)) {
8311
                        // Skip the current question
8312
                        if ($currentQuestion != $counterRemindListQuestions) {
8313
                            break;
8314
                        }
8315
                    }
8316
                    $counterRemindListQuestions++;
8317
                }
8318
8319
                if ($counterRemindListQuestions < $currentQuestion) {
8320
                    return null;
8321
                }
8322
8323
                if (!empty($counterRemindListQuestions)) {
8324
                    if ($counterRemindListQuestions > $counterAnsweredQuestions) {
8325
                        return $counterAnsweredQuestions;
8326
                    } else {
8327
                        return $counterRemindListQuestions;
8328
                    }
8329
                }
8330
            }
8331
8332
            return $counterAnsweredQuestions;
8333
        }
8334
    }
8335
8336
    /**
8337
     * Gets the position of a questionId in the question list
8338
     * @param $questionId
8339
     * @return int
8340
     */
8341
    public function getPositionInCompressedQuestionList($questionId)
8342
    {
8343
        $questionList = $this->getQuestionListWithMediasCompressed();
8344
        $mediaQuestions = $this->getMediaList();
8345
        $position = 1;
8346
        foreach ($questionList as $id) {
8347
            if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) {
8348
                $mediaQuestionList = $mediaQuestions[$id];
8349
                if (in_array($questionId, $mediaQuestionList)) {
8350
                    return $position;
8351
                } else {
8352
                    $position++;
8353
                }
8354
            } else {
8355
                if ($id == $questionId) {
8356
                    return $position;
8357
                } else {
8358
                    $position++;
8359
                }
8360
            }
8361
        }
8362
        return 1;
8363
    }
8364
}
8365