Passed
Push — master ( e739b7...1cdc43 )
by Julito
10:11
created

Question::getCountExercise()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 0
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CourseBundle\Entity\CDocument;
5
use Chamilo\CourseBundle\Entity\CQuizAnswer;
6
7
/**
8
 * Class Question.
9
 *
10
 * This class allows to instantiate an object of type Question
11
 *
12
 * @author Olivier Brouckaert, original author
13
 * @author Patrick Cool, LaTeX support
14
 * @author Julio Montoya <[email protected]> lot of bug fixes
15
 * @author [email protected] - add question categories
16
 *
17
 * @package chamilo.exercise
18
 */
19
abstract class Question
20
{
21
    public $id;
22
    public $iid;
23
    public $question;
24
    public $description;
25
    public $weighting;
26
    public $position;
27
    public $type;
28
    public $level;
29
    public $picture;
30
    public $exerciseList; // array with the list of exercises which this question is in
31
    public $category_list;
32
    public $parent_id;
33
    public $category;
34
    public $isContent;
35
    public $course;
36
    public $feedback;
37
    public static $typePicture = 'new_question.png';
38
    public static $explanationLangVar = '';
39
    public $question_table_class = 'table table-striped';
40
    public $questionTypeWithFeedback;
41
    public $extra;
42
    public $export = false;
43
    public $code;
44
    public static $questionTypes = [
45
        UNIQUE_ANSWER => ['unique_answer.class.php', 'UniqueAnswer'],
46
        MULTIPLE_ANSWER => ['multiple_answer.class.php', 'MultipleAnswer'],
47
        FILL_IN_BLANKS => ['fill_blanks.class.php', 'FillBlanks'],
48
        MATCHING => ['matching.class.php', 'Matching'],
49
        FREE_ANSWER => ['freeanswer.class.php', 'FreeAnswer'],
50
        ORAL_EXPRESSION => ['oral_expression.class.php', 'OralExpression'],
51
        HOT_SPOT => ['hotspot.class.php', 'HotSpot'],
52
        HOT_SPOT_DELINEATION => ['hotspot.class.php', 'HotspotDelineation'],
53
        MULTIPLE_ANSWER_COMBINATION => ['multiple_answer_combination.class.php', 'MultipleAnswerCombination'],
54
        UNIQUE_ANSWER_NO_OPTION => ['unique_answer_no_option.class.php', 'UniqueAnswerNoOption'],
55
        MULTIPLE_ANSWER_TRUE_FALSE => ['multiple_answer_true_false.class.php', 'MultipleAnswerTrueFalse'],
56
        MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY => [
57
            'MultipleAnswerTrueFalseDegreeCertainty.php',
58
            'MultipleAnswerTrueFalseDegreeCertainty',
59
        ],
60
        MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE => [
61
            'multiple_answer_combination_true_false.class.php',
62
            'MultipleAnswerCombinationTrueFalse',
63
        ],
64
        GLOBAL_MULTIPLE_ANSWER => ['global_multiple_answer.class.php', 'GlobalMultipleAnswer'],
65
        CALCULATED_ANSWER => ['calculated_answer.class.php', 'CalculatedAnswer'],
66
        UNIQUE_ANSWER_IMAGE => ['UniqueAnswerImage.php', 'UniqueAnswerImage'],
67
        DRAGGABLE => ['Draggable.php', 'Draggable'],
68
        MATCHING_DRAGGABLE => ['MatchingDraggable.php', 'MatchingDraggable'],
69
        //MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion')
70
        ANNOTATION => ['Annotation.php', 'Annotation'],
71
        READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'],
72
    ];
73
74
    /**
75
     * constructor of the class.
76
     *
77
     * @author Olivier Brouckaert
78
     */
79
    public function __construct()
80
    {
81
        $this->id = 0;
82
        $this->iid = 0;
83
        $this->question = '';
84
        $this->description = '';
85
        $this->weighting = 0;
86
        $this->position = 1;
87
        $this->picture = '';
88
        $this->level = 1;
89
        $this->category = 0;
90
        // This variable is used when loading an exercise like an scenario with
91
        // an special hotspot: final_overlap, final_missing, final_excess
92
        $this->extra = '';
93
        $this->exerciseList = [];
94
        $this->course = api_get_course_info();
95
        $this->category_list = [];
96
        $this->parent_id = 0;
97
        // See BT#12611
98
        $this->questionTypeWithFeedback = [
99
            MATCHING,
100
            MATCHING_DRAGGABLE,
101
            DRAGGABLE,
102
            FILL_IN_BLANKS,
103
            FREE_ANSWER,
104
            ORAL_EXPRESSION,
105
            CALCULATED_ANSWER,
106
            ANNOTATION,
107
        ];
108
    }
109
110
    /**
111
     * @return int|null
112
     */
113
    public function getIsContent()
114
    {
115
        $isContent = null;
116
        if (isset($_REQUEST['isContent'])) {
117
            $isContent = intval($_REQUEST['isContent']);
118
        }
119
120
        return $this->isContent = $isContent;
121
    }
122
123
    /**
124
     * Reads question information from the data base.
125
     *
126
     * @param int   $id              - question ID
127
     * @param array $course_info
128
     * @param bool  $getExerciseList
129
     *
130
     * @return Question
131
     *
132
     * @author Olivier Brouckaert
133
     */
134
    public static function read($id, $course_info = [], $getExerciseList = true)
135
    {
136
        $id = (int) $id;
137
        if (empty($course_info)) {
138
            $course_info = api_get_course_info();
139
        }
140
        $course_id = $course_info['real_id'];
141
142
        if (empty($course_id) || $course_id == -1) {
143
            return false;
144
        }
145
146
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
147
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
148
149
        $sql = "SELECT *
150
                FROM $TBL_QUESTIONS
151
                WHERE c_id = $course_id AND id = $id ";
152
        $result = Database::query($sql);
153
154
        // if the question has been found
155
        if ($object = Database::fetch_object($result)) {
156
            $objQuestion = self::getInstance($object->type);
157
            if (!empty($objQuestion)) {
158
                $objQuestion->id = (int) $id;
159
                $objQuestion->iid = (int) $object->iid;
160
                $objQuestion->question = $object->question;
161
                $objQuestion->description = $object->description;
162
                $objQuestion->weighting = $object->ponderation;
163
                $objQuestion->position = $object->position;
164
                $objQuestion->type = (int) $object->type;
165
                $objQuestion->picture = $object->picture;
166
                $objQuestion->level = (int) $object->level;
167
                $objQuestion->extra = $object->extra;
168
                $objQuestion->course = $course_info;
169
                $objQuestion->feedback = isset($object->feedback) ? $object->feedback : '';
170
                $objQuestion->category = TestCategory::getCategoryForQuestion($id, $course_id);
171
                $objQuestion->code = isset($object->code) ? $object->code : '';
172
173
                if ($getExerciseList) {
174
                    $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
175
                    $sql = "SELECT DISTINCT q.exercice_id
176
                            FROM $TBL_EXERCISE_QUESTION q
177
                            INNER JOIN $tblQuiz e
178
                            ON e.c_id = q.c_id AND e.id = q.exercice_id
179
                            WHERE
180
                                q.c_id = $course_id AND
181
                                q.question_id = $id AND
182
                                e.active >= 0";
183
184
                    $result = Database::query($sql);
185
186
                    // fills the array with the exercises which this question is in
187
                    if ($result) {
0 ignored issues
show
introduced by
$result is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
188
                        while ($obj = Database::fetch_object($result)) {
189
                            $objQuestion->exerciseList[] = $obj->exercice_id;
190
                        }
191
                    }
192
                }
193
194
                return $objQuestion;
195
            }
196
        }
197
198
        // question not found
199
        return false;
200
    }
201
202
    /**
203
     * returns the question ID.
204
     *
205
     * @author Olivier Brouckaert
206
     *
207
     * @return int - question ID
208
     */
209
    public function selectId()
210
    {
211
        return $this->id;
212
    }
213
214
    /**
215
     * returns the question title.
216
     *
217
     * @author Olivier Brouckaert
218
     *
219
     * @return string - question title
220
     */
221
    public function selectTitle()
222
    {
223
        if (!api_get_configuration_value('save_titles_as_html')) {
224
            return $this->question;
225
        }
226
227
        return Display::div($this->question, ['style' => 'display: inline-block;']);
228
    }
229
230
    /**
231
     * @param int $itemNumber
232
     *
233
     * @return string
234
     */
235
    public function getTitleToDisplay($itemNumber)
236
    {
237
        $showQuestionTitleHtml = api_get_configuration_value('save_titles_as_html');
238
        $title = '';
239
        if (api_get_configuration_value('show_question_id')) {
240
            $title .= '<h4>#'.$this->course['code'].'-'.$this->iid.'</h4>';
241
        }
242
243
        $title .= $showQuestionTitleHtml ? '' : '<strong>';
244
        $title .= $itemNumber.'. '.$this->selectTitle();
245
        $title .= $showQuestionTitleHtml ? '' : '</strong>';
246
247
        return Display::div(
248
            $title,
249
            ['class' => 'question_title']
250
        );
251
    }
252
253
    /**
254
     * returns the question description.
255
     *
256
     * @author Olivier Brouckaert
257
     *
258
     * @return string - question description
259
     */
260
    public function selectDescription()
261
    {
262
        return $this->description;
263
    }
264
265
    /**
266
     * returns the question weighting.
267
     *
268
     * @author Olivier Brouckaert
269
     *
270
     * @return int - question weighting
271
     */
272
    public function selectWeighting()
273
    {
274
        return $this->weighting;
275
    }
276
277
    /**
278
     * returns the question position.
279
     *
280
     * @author Olivier Brouckaert
281
     *
282
     * @return int - question position
283
     */
284
    public function selectPosition()
285
    {
286
        return $this->position;
287
    }
288
289
    /**
290
     * returns the answer type.
291
     *
292
     * @author Olivier Brouckaert
293
     *
294
     * @return int - answer type
295
     */
296
    public function selectType()
297
    {
298
        return $this->type;
299
    }
300
301
    /**
302
     * returns the level of the question.
303
     *
304
     * @author Nicolas Raynaud
305
     *
306
     * @return int - level of the question, 0 by default
307
     */
308
    public function getLevel()
309
    {
310
        return $this->level;
311
    }
312
313
    /**
314
     * returns the picture name.
315
     *
316
     * @author Olivier Brouckaert
317
     *
318
     * @return string - picture name
319
     */
320
    public function selectPicture()
321
    {
322
        return $this->picture;
323
    }
324
325
    /**
326
     * @return string
327
     */
328
    public function selectPicturePath()
329
    {
330
        if (!empty($this->picture)) {
331
            $router = \Chamilo\CoreBundle\Framework\Container::getRouter();
332
            // this "filter" param is used to resize the image to width 800px see config/packages/liip_imagine.yaml
333
            $params = [
334
                'file' => 'images/'.$this->getPictureFilename(),
335
                'type' => 'show',
336
                'filter' => 'hotspot_question',
337
                'course' => api_get_course_id(),
338
            ];
339
            $url = $router->generate('core_tool_document', $params);
340
341
            return $url;
342
343
            /*return api_get_path(WEB_COURSE_PATH).
344
                $this->course['directory'].'/document/images/'.$this->getPictureFilename().'?'.api_get_cidreq().'&type=show&filter=hotspot_question';*/
345
        }
346
347
        return '';
348
    }
349
350
    /**
351
     * @return int|string
352
     */
353
    public function getPictureId()
354
    {
355
        // for backward compatibility
356
        // when in field picture we had the filename not the document id
357
        if (preg_match("/quiz-.*/", $this->picture)) {
358
            return DocumentManager::get_document_id(
359
                $this->course,
360
                $this->selectPicturePath(),
361
                api_get_session_id()
362
            );
363
        }
364
365
        return $this->picture;
366
    }
367
368
    /**
369
     * @param int $courseId
370
     * @param int $sessionId
371
     *
372
     * @return string
373
     */
374
    public function getPictureFilename($courseId = 0, $sessionId = 0)
375
    {
376
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
377
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
378
379
        if (empty($courseId)) {
380
            return '';
381
        }
382
        // for backward compatibility
383
        // when in field picture we had the filename not the document id
384
        if (preg_match("/quiz-.*/", $this->picture)) {
385
            return $this->picture;
386
        }
387
388
        $pictureId = $this->getPictureId();
389
        $courseInfo = $this->course;
390
        $documentInfo = DocumentManager::get_document_data_by_id(
391
            $pictureId,
392
            $courseInfo['code'],
393
            false,
394
            $sessionId
395
        );
396
        $documentFilename = '';
397
        if ($documentInfo) {
398
            // document in document/images folder
399
            $documentFilename = pathinfo(
400
                $documentInfo['path'],
401
                PATHINFO_BASENAME
402
            );
403
        }
404
405
        return $documentFilename;
406
    }
407
408
    /**
409
     * @param int $courseId
410
     * @param int $sessionId
411
     *
412
     * @return false|CDocument
413
     */
414
    public function getPicture($courseId = 0, $sessionId = 0)
415
    {
416
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
417
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
418
419
        if (empty($courseId)) {
420
            return false;
421
        }
422
423
        $pictureId = $this->getPictureId();
424
        $courseInfo = $this->course;
425
        $documentInfo = DocumentManager::get_document_data_by_id(
426
            $pictureId,
427
            $courseInfo['code'],
428
            false,
429
            $sessionId
430
        );
431
432
        if ($documentInfo) {
433
            $em = Database::getManager();
434
435
            /** @var CDocument $document */
436
            $document = $em->getRepository('ChamiloCourseBundle:CDocument')->find($documentInfo['iid']);
437
438
            return $document;
439
        }
440
441
        return false;
442
    }
443
444
    /**
445
     * returns the array with the exercise ID list.
446
     *
447
     * @author Olivier Brouckaert
448
     *
449
     * @return array - list of exercise ID which the question is in
450
     */
451
    public function selectExerciseList()
452
    {
453
        return $this->exerciseList;
454
    }
455
456
    /**
457
     * returns the number of exercises which this question is in.
458
     *
459
     * @author Olivier Brouckaert
460
     *
461
     * @return int - number of exercises
462
     */
463
    public function selectNbrExercises()
464
    {
465
        return count($this->exerciseList);
466
    }
467
468
    /**
469
     * changes the question title.
470
     *
471
     * @param string $title - question title
472
     *
473
     * @author Olivier Brouckaert
474
     */
475
    public function updateTitle($title)
476
    {
477
        $this->question = $title;
478
    }
479
480
    /**
481
     * @param int $id
482
     */
483
    public function updateParentId($id)
484
    {
485
        $this->parent_id = (int) $id;
486
    }
487
488
    /**
489
     * changes the question description.
490
     *
491
     * @param string $description - question description
492
     *
493
     * @author Olivier Brouckaert
494
     */
495
    public function updateDescription($description)
496
    {
497
        $this->description = $description;
498
    }
499
500
    /**
501
     * changes the question weighting.
502
     *
503
     * @param int $weighting - question weighting
504
     *
505
     * @author Olivier Brouckaert
506
     */
507
    public function updateWeighting($weighting)
508
    {
509
        $this->weighting = $weighting;
510
    }
511
512
    /**
513
     * @param array $category
514
     *
515
     * @author Hubert Borderiou 12-10-2011
516
     */
517
    public function updateCategory($category)
518
    {
519
        $this->category = $category;
520
    }
521
522
    /**
523
     * @param int $value
524
     *
525
     * @author Hubert Borderiou 12-10-2011
526
     */
527
    public function updateScoreAlwaysPositive($value)
528
    {
529
        $this->scoreAlwaysPositive = $value;
530
    }
531
532
    /**
533
     * @param int $value
534
     *
535
     * @author Hubert Borderiou 12-10-2011
536
     */
537
    public function updateUncheckedMayScore($value)
538
    {
539
        $this->uncheckedMayScore = $value;
540
    }
541
542
    /**
543
     * Save category of a question.
544
     *
545
     * A question can have n categories if category is empty,
546
     * then question has no category then delete the category entry
547
     *
548
     * @param array $category_list
549
     *
550
     * @author Julio Montoya - Adding multiple cat support
551
     */
552
    public function saveCategories($category_list)
553
    {
554
        if (!empty($category_list)) {
555
            $this->deleteCategory();
556
            $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
557
558
            // update or add category for a question
559
            foreach ($category_list as $category_id) {
560
                $category_id = (int) $category_id;
561
                $question_id = (int) $this->id;
562
                $sql = "SELECT count(*) AS nb
563
                        FROM $table
564
                        WHERE
565
                            category_id = $category_id
566
                            AND question_id = $question_id
567
                            AND c_id=".api_get_course_int_id();
568
                $res = Database::query($sql);
569
                $row = Database::fetch_array($res);
570
                if ($row['nb'] > 0) {
571
                    // DO nothing
572
                } else {
573
                    $sql = "INSERT INTO $table (c_id, question_id, category_id)
574
                            VALUES (".api_get_course_int_id().", $question_id, $category_id)";
575
                    Database::query($sql);
576
                }
577
            }
578
        }
579
    }
580
581
    /**
582
     * in this version, a question can only have 1 category
583
     * if category is 0, then question has no category then delete the category entry.
584
     *
585
     * @param int $categoryId
586
     * @param int $courseId
587
     *
588
     * @return bool
589
     *
590
     * @author Hubert Borderiou 12-10-2011
591
     */
592
    public function saveCategory($categoryId, $courseId = 0)
593
    {
594
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
595
596
        if (empty($courseId)) {
597
            return false;
598
        }
599
600
        if ($categoryId <= 0) {
601
            $this->deleteCategory($courseId);
602
        } else {
603
            // update or add category for a question
604
            $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
605
            $categoryId = (int) $categoryId;
606
            $question_id = (int) $this->id;
607
            $sql = "SELECT count(*) AS nb FROM $table
608
                    WHERE
609
                        question_id = $question_id AND
610
                        c_id = ".$courseId;
611
            $res = Database::query($sql);
612
            $row = Database::fetch_array($res);
613
            if ($row['nb'] > 0) {
614
                $sql = "UPDATE $table
615
                        SET category_id = $categoryId
616
                        WHERE
617
                            question_id = $question_id AND
618
                            c_id = ".$courseId;
619
                Database::query($sql);
620
            } else {
621
                $sql = "INSERT INTO $table (c_id, question_id, category_id)
622
                        VALUES (".$courseId.", $question_id, $categoryId)";
623
                Database::query($sql);
624
            }
625
626
            return true;
627
        }
628
    }
629
630
    /**
631
     * @author hubert borderiou 12-10-2011
632
     *
633
     * @param int $courseId
634
     *                      delete any category entry for question id
635
     *                      delete the category for question
636
     *
637
     * @return bool
638
     */
639
    public function deleteCategory($courseId = 0)
640
    {
641
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
642
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
643
        $questionId = (int) $this->id;
644
        if (empty($courseId) || empty($questionId)) {
645
            return false;
646
        }
647
        $sql = "DELETE FROM $table
648
                WHERE
649
                    question_id = $questionId AND
650
                    c_id = ".$courseId;
651
        Database::query($sql);
652
653
        return true;
654
    }
655
656
    /**
657
     * changes the question position.
658
     *
659
     * @param int $position - question position
660
     *
661
     * @author Olivier Brouckaert
662
     */
663
    public function updatePosition($position)
664
    {
665
        $this->position = $position;
666
    }
667
668
    /**
669
     * changes the question level.
670
     *
671
     * @param int $level - question level
672
     *
673
     * @author Nicolas Raynaud
674
     */
675
    public function updateLevel($level)
676
    {
677
        $this->level = $level;
678
    }
679
680
    /**
681
     * changes the answer type. If the user changes the type from "unique answer" to "multiple answers"
682
     * (or conversely) answers are not deleted, otherwise yes.
683
     *
684
     * @param int $type - answer type
685
     *
686
     * @author Olivier Brouckaert
687
     */
688
    public function updateType($type)
689
    {
690
        $table = Database::get_course_table(TABLE_QUIZ_ANSWER);
691
        $course_id = $this->course['real_id'];
692
693
        if (empty($course_id)) {
694
            $course_id = api_get_course_int_id();
695
        }
696
        // if we really change the type
697
        if ($type != $this->type) {
698
            // if we don't change from "unique answer" to "multiple answers" (or conversely)
699
            if (!in_array($this->type, [UNIQUE_ANSWER, MULTIPLE_ANSWER]) ||
700
                !in_array($type, [UNIQUE_ANSWER, MULTIPLE_ANSWER])
701
            ) {
702
                // removes old answers
703
                $sql = "DELETE FROM $table
704
                        WHERE c_id = $course_id AND question_id = ".intval($this->id);
705
                Database::query($sql);
706
            }
707
708
            $this->type = $type;
709
        }
710
    }
711
712
    /**
713
     * Get default hot spot folder in documents.
714
     *
715
     * @param array $courseInfo
716
     *
717
     * @return CDocument
718
     */
719
    public function getHotSpotFolderInCourse($courseInfo = [])
720
    {
721
        $courseInfo = empty($courseInfo) ? $this->course : $courseInfo;
722
723
        if (empty($courseInfo) || empty($courseInfo['directory'])) {
724
            // Stop everything if course is not set.
725
            api_not_allowed();
726
        }
727
728
        $pictureAbsolutePath = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'].'/document/images/';
729
        $picturePath = basename($pictureAbsolutePath);
730
731
        $folder = create_unexisting_directory(
732
            $courseInfo,
733
            api_get_user_id(),
734
            0,
735
            0,
736
            0,
737
            dirname($pictureAbsolutePath),
738
            '/'.$picturePath,
739
            $picturePath,
740
            '',
741
            false,
742
            false
743
        );
744
745
        return $folder;
746
    }
747
748
    /**
749
     * adds a picture to the question.
750
     *
751
     * @param array $picture - picture to upload
752
     *
753
     * @return bool - true if uploaded, otherwise false
754
     */
755
    public function uploadPicture($picture)
756
    {
757
        $folder = $this->getHotSpotFolderInCourse();
758
759
        // if the question has got an ID
760
        if ($folder && $this->id) {
761
            $document = DocumentManager::upload_document(
762
                ['imageUpload' => $picture],
763
                '/images',
764
                '',
765
                '',
766
                false,
767
                'overwrite',
768
                false,
769
                true,
770
                'imageUpload',
771
                true,
772
                $folder->getId()
773
            );
774
775
            if ($document) {
776
                $this->picture = $document->getIid();
777
778
                return true;
779
            }
780
        }
781
782
        return false;
783
    }
784
785
    /**
786
     * return the name for image use in hotspot question
787
     * to be unique, name is quiz-[utc unix timestamp].jpg.
788
     *
789
     * @param string $prefix
790
     * @param string $extension
791
     *
792
     * @return string
793
     */
794
    public function generatePictureName($prefix = 'quiz-', $extension = 'jpg')
795
    {
796
        // image name is quiz-xxx.jpg in folder images/
797
        $utcTime = time();
798
799
        return $prefix.$utcTime.'.'.$extension;
800
    }
801
802
    /**
803
     *  Deletes a hot spot picture.
804
     *
805
     * @return bool - true
806
     */
807
    public function removePicture()
808
    {
809
        $picture = $this->getPicture();
810
811
        if ($picture) {
812
            $manager = Database::getManager();
813
            $manager->remove($picture);
814
            $manager->flush();
815
816
            return true;
817
        }
818
819
        return false;
820
    }
821
822
    /**
823
     * Exports a picture to another question.
824
     *
825
     * @author Olivier Brouckaert
826
     *
827
     * @param int   $questionId - ID of the target question
828
     * @param array $courseInfo destination course info
829
     *
830
     * @return bool - true if copied, otherwise false
831
     */
832
    public function exportPicture($questionId, $courseInfo)
833
    {
834
        if (empty($questionId) || empty($courseInfo)) {
835
            return false;
836
        }
837
838
        $course_id = $courseInfo['real_id'];
839
        $destination_path = $this->getHotSpotFolderInCourse($courseInfo);
840
841
        if (empty($destination_path)) {
842
            return false;
843
        }
844
845
        $source_path = $this->getHotSpotFolderInCourse();
846
847
        // if the question has got an ID and if the picture exists
848
        if (!$this->id || empty($this->picture)) {
849
            return false;
850
        }
851
852
        $sourcePictureName = $this->getPictureFilename($course_id);
853
        $picture = $this->generatePictureName();
854
        $result = false;
855
        if (file_exists($source_path.'/'.$sourcePictureName)) {
856
            // for backward compatibility
857
            $result = copy(
858
                $source_path.'/'.$sourcePictureName,
859
                $destination_path.'/'.$picture
860
            );
861
        } else {
862
            $imageInfo = DocumentManager::get_document_data_by_id(
863
                $this->picture,
864
                $courseInfo['code']
865
            );
866
            if (file_exists($imageInfo['absolute_path'])) {
867
                $result = @copy(
868
                    $imageInfo['absolute_path'],
869
                    $destination_path.'/'.$picture
870
                );
871
            }
872
        }
873
874
        // If copy was correct then add to the database
875
        if (!$result) {
876
            return false;
877
        }
878
879
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION);
880
        $sql = "UPDATE $table SET
881
                picture = '".Database::escape_string($picture)."'
882
                WHERE c_id = $course_id AND id='".intval($questionId)."'";
883
        Database::query($sql);
884
885
        $documentId = DocumentManager::addDocument(
886
            $courseInfo,
887
            '/images/'.$picture,
888
            'file',
889
            filesize($destination_path.'/'.$picture),
890
            $picture
891
        );
892
893
        if (!$documentId) {
894
            return false;
895
        }
896
897
        return true;
898
    }
899
900
    /**
901
     * Set title.
902
     *
903
     * @param string $title
904
     */
905
    public function setTitle($title)
906
    {
907
        $this->question = $title;
908
    }
909
910
    /**
911
     * Sets extra info.
912
     *
913
     * @param string $extra
914
     */
915
    public function setExtra($extra)
916
    {
917
        $this->extra = $extra;
918
    }
919
920
    /**
921
     * updates the question in the data base
922
     * if an exercise ID is provided, we add that exercise ID into the exercise list.
923
     *
924
     * @author Olivier Brouckaert
925
     *
926
     * @param Exercise $exercise
927
     */
928
    public function save($exercise)
929
    {
930
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
931
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
932
        $em = Database::getManager();
933
        $exerciseId = $exercise->id;
934
935
        $id = $this->id;
936
        $question = $this->question;
937
        $description = $this->description;
938
        $weighting = $this->weighting;
939
        $position = $this->position;
940
        $type = $this->type;
941
        $picture = $this->picture;
942
        $level = $this->level;
943
        $extra = $this->extra;
944
        $c_id = $this->course['real_id'];
945
        $categoryId = $this->category;
946
947
        // question already exists
948
        if (!empty($id)) {
949
            $params = [
950
                'question' => $question,
951
                'description' => $description,
952
                'ponderation' => $weighting,
953
                'position' => $position,
954
                'type' => $type,
955
                'picture' => $picture,
956
                'extra' => $extra,
957
                'level' => $level,
958
            ];
959
            if ($exercise->questionFeedbackEnabled) {
960
                $params['feedback'] = $this->feedback;
961
            }
962
963
            Database::update(
964
                $TBL_QUESTIONS,
965
                $params,
966
                ['c_id = ? AND id = ?' => [$c_id, $id]]
967
            );
968
969
            Event::addEvent(
970
                LOG_QUESTION_UPDATED,
971
                LOG_QUESTION_ID,
972
                $this->iid
973
            );
974
            $this->saveCategory($categoryId);
975
976
            if (!empty($exerciseId)) {
977
                api_item_property_update(
978
                    $this->course,
979
                    TOOL_QUIZ,
980
                    $id,
981
                    'QuizQuestionUpdated',
982
                    api_get_user_id()
983
                );
984
            }
985
            if (api_get_setting('search_enabled') === 'true') {
986
                $this->search_engine_edit($exerciseId);
987
            }
988
        } else {
989
            // Creates a new question
990
            $sql = "SELECT max(position)
991
                    FROM $TBL_QUESTIONS as question,
992
                    $TBL_EXERCISE_QUESTION as test_question
993
                    WHERE
994
                        question.id = test_question.question_id AND
995
                        test_question.exercice_id = ".$exerciseId." AND
996
                        question.c_id = $c_id AND
997
                        test_question.c_id = $c_id ";
998
            $result = Database::query($sql);
999
            $current_position = Database::result($result, 0, 0);
1000
            $this->updatePosition($current_position + 1);
1001
            $position = $this->position;
1002
1003
            $params = [
1004
                'c_id' => $c_id,
1005
                'question' => $question,
1006
                'description' => $description,
1007
                'ponderation' => $weighting,
1008
                'position' => $position,
1009
                'type' => $type,
1010
                'picture' => $picture,
1011
                'extra' => $extra,
1012
                'level' => $level,
1013
            ];
1014
1015
            if ($exercise->questionFeedbackEnabled) {
1016
                $params['feedback'] = $this->feedback;
1017
            }
1018
            $this->id = Database::insert($TBL_QUESTIONS, $params);
1019
1020
            if ($this->id) {
1021
                $sql = "UPDATE $TBL_QUESTIONS SET id = iid WHERE iid = {$this->id}";
1022
                Database::query($sql);
1023
1024
                Event::addEvent(
1025
                    LOG_QUESTION_CREATED,
1026
                    LOG_QUESTION_ID,
1027
                    $this->id
1028
                );
1029
1030
                api_item_property_update(
1031
                    $this->course,
1032
                    TOOL_QUIZ,
1033
                    $this->id,
1034
                    'QuizQuestionAdded',
1035
                    api_get_user_id()
1036
                );
1037
1038
                // If hotspot, create first answer
1039
                if ($type == HOT_SPOT || $type == HOT_SPOT_ORDER) {
1040
                    $quizAnswer = new CQuizAnswer();
1041
                    $quizAnswer
1042
                        ->setCId($c_id)
1043
                        ->setQuestionId($this->id)
1044
                        ->setAnswer('')
1045
                        ->setPonderation(10)
1046
                        ->setPosition(1)
1047
                        ->setHotspotCoordinates('0;0|0|0')
1048
                        ->setHotspotType('square');
1049
1050
                    $em->persist($quizAnswer);
1051
                    $em->flush();
1052
1053
                    $id = $quizAnswer->getIid();
1054
1055
                    if ($id) {
1056
                        $quizAnswer
1057
                            ->setId($id)
1058
                            ->setIdAuto($id);
1059
1060
                        $em->merge($quizAnswer);
1061
                        $em->flush();
1062
                    }
1063
                }
1064
1065
                if ($type == HOT_SPOT_DELINEATION) {
1066
                    $quizAnswer = new CQuizAnswer();
1067
                    $quizAnswer
1068
                        ->setCId($c_id)
1069
                        ->setQuestionId($this->id)
1070
                        ->setAnswer('')
1071
                        ->setPonderation(10)
1072
                        ->setPosition(1)
1073
                        ->setHotspotCoordinates('0;0|0|0')
1074
                        ->setHotspotType('delineation');
1075
1076
                    $em->persist($quizAnswer);
1077
                    $em->flush();
1078
1079
                    $id = $quizAnswer->getIid();
1080
1081
                    if ($id) {
1082
                        $quizAnswer
1083
                            ->setId($id)
1084
                            ->setIdAuto($id);
1085
1086
                        $em->merge($quizAnswer);
1087
                        $em->flush();
1088
                    }
1089
                }
1090
1091
                if (api_get_setting('search_enabled') === 'true') {
1092
                    $this->search_engine_edit($exerciseId, true);
1093
                }
1094
            }
1095
        }
1096
1097
        // if the question is created in an exercise
1098
        if (!empty($exerciseId)) {
1099
            // adds the exercise into the exercise list of this question
1100
            $this->addToList($exerciseId, true);
1101
        }
1102
    }
1103
1104
    /**
1105
     * @param int  $exerciseId
1106
     * @param bool $addQs
1107
     * @param bool $rmQs
1108
     */
1109
    public function search_engine_edit(
1110
        $exerciseId,
1111
        $addQs = false,
1112
        $rmQs = false
1113
    ) {
1114
        // update search engine and its values table if enabled
1115
        if (!empty($exerciseId) && api_get_setting('search_enabled') == 'true' &&
1116
            extension_loaded('xapian')
1117
        ) {
1118
            $course_id = api_get_course_id();
1119
            // get search_did
1120
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1121
            if ($addQs || $rmQs) {
1122
                //there's only one row per question on normal db and one document per question on search engine db
1123
                $sql = 'SELECT * FROM %s
1124
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=%s LIMIT 1';
1125
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
1126
            } else {
1127
                $sql = 'SELECT * FROM %s
1128
                    WHERE course_code=\'%s\' AND tool_id=\'%s\'
1129
                    AND ref_id_high_level=%s AND ref_id_second_level=%s LIMIT 1';
1130
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
1131
            }
1132
            $res = Database::query($sql);
1133
1134
            if (Database::num_rows($res) > 0 || $addQs) {
1135
                $di = new ChamiloIndexer();
1136
                if ($addQs) {
1137
                    $question_exercises = [(int) $exerciseId];
1138
                } else {
1139
                    $question_exercises = [];
1140
                }
1141
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
1142
                $di->connectDb(null, null, $lang);
1143
1144
                // retrieve others exercise ids
1145
                $se_ref = Database::fetch_array($res);
1146
                $se_doc = $di->get_document((int) $se_ref['search_did']);
1147
                if ($se_doc !== false) {
1148
                    if (($se_doc_data = $di->get_document_data($se_doc)) !== false) {
1149
                        $se_doc_data = UnserializeApi::unserialize(
1150
                            'not_allowed_classes',
1151
                            $se_doc_data
1152
                        );
1153
                        if (isset($se_doc_data[SE_DATA]['type']) &&
1154
                            $se_doc_data[SE_DATA]['type'] == SE_DOCTYPE_EXERCISE_QUESTION
1155
                        ) {
1156
                            if (isset($se_doc_data[SE_DATA]['exercise_ids']) &&
1157
                                is_array($se_doc_data[SE_DATA]['exercise_ids'])
1158
                            ) {
1159
                                foreach ($se_doc_data[SE_DATA]['exercise_ids'] as $old_value) {
1160
                                    if (!in_array($old_value, $question_exercises)) {
1161
                                        $question_exercises[] = $old_value;
1162
                                    }
1163
                                }
1164
                            }
1165
                        }
1166
                    }
1167
                }
1168
                if ($rmQs) {
1169
                    while (($key = array_search($exerciseId, $question_exercises)) !== false) {
1170
                        unset($question_exercises[$key]);
1171
                    }
1172
                }
1173
1174
                // build the chunk to index
1175
                $ic_slide = new IndexableChunk();
1176
                $ic_slide->addValue("title", $this->question);
1177
                $ic_slide->addCourseId($course_id);
1178
                $ic_slide->addToolId(TOOL_QUIZ);
1179
                $xapian_data = [
1180
                    SE_COURSE_ID => $course_id,
1181
                    SE_TOOL_ID => TOOL_QUIZ,
1182
                    SE_DATA => [
1183
                        'type' => SE_DOCTYPE_EXERCISE_QUESTION,
1184
                        'exercise_ids' => $question_exercises,
1185
                        'question_id' => (int) $this->id,
1186
                    ],
1187
                    SE_USER => (int) api_get_user_id(),
1188
                ];
1189
                $ic_slide->xapian_data = serialize($xapian_data);
1190
                $ic_slide->addValue("content", $this->description);
1191
1192
                //TODO: index answers, see also form validation on question_admin.inc.php
1193
1194
                $di->remove_document($se_ref['search_did']);
1195
                $di->addChunk($ic_slide);
1196
1197
                //index and return search engine document id
1198
                if (!empty($question_exercises)) { // if empty there is nothing to index
1199
                    $did = $di->index();
1200
                    unset($di);
1201
                }
1202
                if ($did || $rmQs) {
1203
                    // save it to db
1204
                    if ($addQs || $rmQs) {
1205
                        $sql = "DELETE FROM %s
1206
                            WHERE course_code = '%s' AND tool_id = '%s' AND ref_id_second_level = '%s'";
1207
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
1208
                    } else {
1209
                        $sql = "DELETE FROM %S
1210
                            WHERE
1211
                                course_code = '%s'
1212
                                AND tool_id = '%s'
1213
                                AND tool_id = '%s'
1214
                                AND ref_id_high_level = '%s'
1215
                                AND ref_id_second_level = '%s'";
1216
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
1217
                    }
1218
                    Database::query($sql);
1219
                    if ($rmQs) {
1220
                        if (!empty($question_exercises)) {
1221
                            $sql = "INSERT INTO %s (
1222
                                    id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
1223
                                )
1224
                                VALUES (
1225
                                    NULL, '%s', '%s', %s, %s, %s
1226
                                )";
1227
                            $sql = sprintf(
1228
                                $sql,
1229
                                $tbl_se_ref,
1230
                                $course_id,
1231
                                TOOL_QUIZ,
1232
                                array_shift($question_exercises),
1233
                                $this->id,
1234
                                $did
1235
                            );
1236
                            Database::query($sql);
1237
                        }
1238
                    } else {
1239
                        $sql = "INSERT INTO %s (
1240
                                id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
1241
                            )
1242
                            VALUES (
1243
                                NULL , '%s', '%s', %s, %s, %s
1244
                            )";
1245
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id, $did);
1246
                        Database::query($sql);
1247
                    }
1248
                }
1249
            }
1250
        }
1251
    }
1252
1253
    /**
1254
     * adds an exercise into the exercise list.
1255
     *
1256
     * @author Olivier Brouckaert
1257
     *
1258
     * @param int  $exerciseId - exercise ID
1259
     * @param bool $fromSave   - from $this->save() or not
1260
     */
1261
    public function addToList($exerciseId, $fromSave = false)
1262
    {
1263
        $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1264
        $id = (int) $this->id;
1265
        $exerciseId = (int) $exerciseId;
1266
1267
        // checks if the exercise ID is not in the list
1268
        if (!empty($exerciseId) && !in_array($exerciseId, $this->exerciseList)) {
1269
            $this->exerciseList[] = $exerciseId;
1270
            $courseId = isset($this->course['real_id']) ? $this->course['real_id'] : 0;
1271
            $newExercise = new Exercise($courseId);
1272
            $newExercise->read($exerciseId, false);
1273
            $count = $newExercise->getQuestionCount();
1274
            $count++;
1275
            $sql = "INSERT INTO $exerciseRelQuestionTable (c_id, question_id, exercice_id, question_order)
1276
                    VALUES ({$this->course['real_id']}, ".$id.", ".$exerciseId.", '$count')";
1277
            Database::query($sql);
1278
1279
            // we do not want to reindex if we had just saved adnd indexed the question
1280
            if (!$fromSave) {
1281
                $this->search_engine_edit($exerciseId, true);
1282
            }
1283
        }
1284
    }
1285
1286
    /**
1287
     * removes an exercise from the exercise list.
1288
     *
1289
     * @author Olivier Brouckaert
1290
     *
1291
     * @param int $exerciseId - exercise ID
1292
     * @param int $courseId
1293
     *
1294
     * @return bool - true if removed, otherwise false
1295
     */
1296
    public function removeFromList($exerciseId, $courseId = 0)
1297
    {
1298
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1299
        $id = (int) $this->id;
1300
        $exerciseId = (int) $exerciseId;
1301
1302
        // searches the position of the exercise ID in the list
1303
        $pos = array_search($exerciseId, $this->exerciseList);
1304
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
1305
1306
        // exercise not found
1307
        if ($pos === false) {
1308
            return false;
1309
        } else {
1310
            // deletes the position in the array containing the wanted exercise ID
1311
            unset($this->exerciseList[$pos]);
1312
            //update order of other elements
1313
            $sql = "SELECT question_order
1314
                    FROM $table
1315
                    WHERE
1316
                        c_id = $courseId AND 
1317
                        question_id = $id AND 
1318
                        exercice_id = $exerciseId";
1319
            $res = Database::query($sql);
1320
            if (Database::num_rows($res) > 0) {
1321
                $row = Database::fetch_array($res);
1322
                if (!empty($row['question_order'])) {
1323
                    $sql = "UPDATE $table
1324
                            SET question_order = question_order-1
1325
                            WHERE
1326
                                c_id = $courseId AND 
1327
                                exercice_id = $exerciseId AND 
1328
                                question_order > ".$row['question_order'];
1329
                    Database::query($sql);
1330
                }
1331
            }
1332
1333
            $sql = "DELETE FROM $table
1334
                    WHERE
1335
                        c_id = $courseId AND 
1336
                        question_id = $id AND 
1337
                        exercice_id = $exerciseId";
1338
            Database::query($sql);
1339
1340
            return true;
1341
        }
1342
    }
1343
1344
    /**
1345
     * Deletes a question from the database
1346
     * the parameter tells if the question is removed from all exercises (value = 0),
1347
     * or just from one exercise (value = exercise ID).
1348
     *
1349
     * @author Olivier Brouckaert
1350
     *
1351
     * @param int $deleteFromEx - exercise ID if the question is only removed from one exercise
1352
     *
1353
     * @return bool
1354
     */
1355
    public function delete($deleteFromEx = 0)
1356
    {
1357
        if (empty($this->course)) {
1358
            return false;
1359
        }
1360
1361
        $courseId = $this->course['real_id'];
1362
1363
        if (empty($courseId)) {
1364
            return false;
1365
        }
1366
1367
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1368
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1369
        $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
1370
        $TBL_QUIZ_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
1371
1372
        $id = (int) $this->id;
1373
1374
        // if the question must be removed from all exercises
1375
        if (!$deleteFromEx) {
1376
            //update the question_order of each question to avoid inconsistencies
1377
            $sql = "SELECT exercice_id, question_order 
1378
                    FROM $TBL_EXERCISE_QUESTION
1379
                    WHERE c_id = $courseId AND question_id = ".$id;
1380
1381
            $res = Database::query($sql);
1382
            if (Database::num_rows($res) > 0) {
1383
                while ($row = Database::fetch_array($res)) {
1384
                    if (!empty($row['question_order'])) {
1385
                        $sql = "UPDATE $TBL_EXERCISE_QUESTION
1386
                                SET question_order = question_order-1
1387
                                WHERE
1388
                                    c_id = $courseId AND 
1389
                                    exercice_id = ".intval($row['exercice_id'])." AND 
1390
                                    question_order > ".$row['question_order'];
1391
                        Database::query($sql);
1392
                    }
1393
                }
1394
            }
1395
1396
            $sql = "DELETE FROM $TBL_EXERCISE_QUESTION
1397
                    WHERE c_id = $courseId AND question_id = ".$id;
1398
            Database::query($sql);
1399
1400
            $sql = "DELETE FROM $TBL_QUESTIONS
1401
                    WHERE c_id = $courseId AND id = ".$id;
1402
            Database::query($sql);
1403
1404
            $sql = "DELETE FROM $TBL_REPONSES
1405
                    WHERE c_id = $courseId AND question_id = ".$id;
1406
            Database::query($sql);
1407
1408
            // remove the category of this question in the question_rel_category table
1409
            $sql = "DELETE FROM $TBL_QUIZ_QUESTION_REL_CATEGORY
1410
                    WHERE 
1411
                        c_id = $courseId AND 
1412
                        question_id = ".$id;
1413
            Database::query($sql);
1414
1415
            api_item_property_update(
1416
                $this->course,
1417
                TOOL_QUIZ,
1418
                $id,
1419
                'QuizQuestionDeleted',
1420
                api_get_user_id()
1421
            );
1422
            $this->removePicture();
1423
        } else {
1424
            // just removes the exercise from the list
1425
            $this->removeFromList($deleteFromEx, $courseId);
1426
            if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1427
                // disassociate question with this exercise
1428
                $this->search_engine_edit($deleteFromEx, false, true);
1429
            }
1430
1431
            api_item_property_update(
1432
                $this->course,
1433
                TOOL_QUIZ,
1434
                $id,
1435
                'QuizQuestionDeleted',
1436
                api_get_user_id()
1437
            );
1438
        }
1439
1440
        return true;
1441
    }
1442
1443
    /**
1444
     * Duplicates the question.
1445
     *
1446
     * @author Olivier Brouckaert
1447
     *
1448
     * @param array $courseInfo Course info of the destination course
1449
     *
1450
     * @return false|string ID of the new question
1451
     */
1452
    public function duplicate($courseInfo = [])
1453
    {
1454
        $courseInfo = empty($courseInfo) ? $this->course : $courseInfo;
1455
1456
        if (empty($courseInfo)) {
1457
            return false;
1458
        }
1459
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
1460
        $TBL_QUESTION_OPTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1461
1462
        $question = $this->question;
1463
        $description = $this->description;
1464
        $weighting = $this->weighting;
1465
        $position = $this->position;
1466
        $type = $this->type;
1467
        $level = (int) $this->level;
1468
        $extra = $this->extra;
1469
1470
        // Using the same method used in the course copy to transform URLs
1471
        if ($this->course['id'] != $courseInfo['id']) {
1472
            $description = DocumentManager::replaceUrlWithNewCourseCode(
1473
                $description,
1474
                $this->course['code'],
1475
                $courseInfo['id']
1476
            );
1477
            $question = DocumentManager::replaceUrlWithNewCourseCode(
1478
                $question,
1479
                $this->course['code'],
1480
                $courseInfo['id']
1481
            );
1482
        }
1483
1484
        $course_id = $courseInfo['real_id'];
1485
1486
        // Read the source options
1487
        $options = self::readQuestionOption($this->id, $this->course['real_id']);
1488
1489
        // Inserting in the new course db / or the same course db
1490
        $params = [
1491
            'c_id' => $course_id,
1492
            'question' => $question,
1493
            'description' => $description,
1494
            'ponderation' => $weighting,
1495
            'position' => $position,
1496
            'type' => $type,
1497
            'level' => $level,
1498
            'extra' => $extra,
1499
        ];
1500
        $newQuestionId = Database::insert($questionTable, $params);
1501
1502
        if ($newQuestionId) {
1503
            $sql = "UPDATE $questionTable 
1504
                    SET id = iid
1505
                    WHERE iid = $newQuestionId";
1506
            Database::query($sql);
1507
1508
            if (!empty($options)) {
1509
                // Saving the quiz_options
1510
                foreach ($options as $item) {
1511
                    $item['question_id'] = $newQuestionId;
1512
                    $item['c_id'] = $course_id;
1513
                    unset($item['id']);
1514
                    unset($item['iid']);
1515
                    $id = Database::insert($TBL_QUESTION_OPTIONS, $item);
1516
                    if ($id) {
1517
                        $sql = "UPDATE $TBL_QUESTION_OPTIONS 
1518
                                SET id = iid
1519
                                WHERE iid = $id";
1520
                        Database::query($sql);
1521
                    }
1522
                }
1523
            }
1524
1525
            // Duplicates the picture of the hotspot
1526
            $this->exportPicture($newQuestionId, $courseInfo);
1527
        }
1528
1529
        return $newQuestionId;
1530
    }
1531
1532
    /**
1533
     * @return string
1534
     */
1535
    public function get_question_type_name()
1536
    {
1537
        $key = self::$questionTypes[$this->type];
1538
1539
        return get_lang($key[1]);
1540
    }
1541
1542
    /**
1543
     * @param string $type
1544
     */
1545
    public static function get_question_type($type)
1546
    {
1547
        if ($type == ORAL_EXPRESSION && api_get_setting('enable_record_audio') !== 'true') {
1548
            return null;
1549
        }
1550
1551
        return self::$questionTypes[$type];
1552
    }
1553
1554
    /**
1555
     * @return array
1556
     */
1557
    public static function get_question_type_list()
1558
    {
1559
        if (api_get_setting('enable_record_audio') !== 'true') {
1560
            self::$questionTypes[ORAL_EXPRESSION] = null;
1561
            unset(self::$questionTypes[ORAL_EXPRESSION]);
1562
        }
1563
        if (api_get_setting('enable_quiz_scenario') !== 'true') {
1564
            self::$questionTypes[HOT_SPOT_DELINEATION] = null;
1565
            unset(self::$questionTypes[HOT_SPOT_DELINEATION]);
1566
        }
1567
1568
        return self::$questionTypes;
1569
    }
1570
1571
    /**
1572
     * Returns an instance of the class corresponding to the type.
1573
     *
1574
     * @param int $type the type of the question
1575
     *
1576
     * @return $this instance of a Question subclass (or of Questionc class by default)
1577
     */
1578
    public static function getInstance($type)
1579
    {
1580
        if (!is_null($type)) {
1581
            list($fileName, $className) = self::get_question_type($type);
1582
            if (!empty($fileName)) {
1583
                if (class_exists($className)) {
1584
                    return new $className();
1585
                } else {
1586
                    echo 'Can\'t instanciate class '.$className.' of type '.$type;
1587
                }
1588
            }
1589
        }
1590
1591
        return null;
1592
    }
1593
1594
    /**
1595
     * Creates the form to create / edit a question
1596
     * A subclass can redefine this function to add fields...
1597
     *
1598
     * @param FormValidator $form
1599
     * @param Exercise      $exercise
1600
     */
1601
    public function createForm(&$form, $exercise)
1602
    {
1603
        echo '<style>
1604
                .media { display:none;}
1605
            </style>';
1606
1607
        // question name
1608
        if (api_get_configuration_value('save_titles_as_html')) {
1609
            $editorConfig = ['ToolbarSet' => 'Minimal'];
1610
            $form->addHtmlEditor(
1611
                'questionName',
1612
                get_lang('Question'),
1613
                false,
1614
                false,
1615
                $editorConfig,
1616
                true
1617
            );
1618
        } else {
1619
            $form->addElement('text', 'questionName', get_lang('Question'));
1620
        }
1621
1622
        $form->addRule('questionName', get_lang('GiveQuestion'), 'required');
1623
1624
        // default content
1625
        $isContent = isset($_REQUEST['isContent']) ? (int) $_REQUEST['isContent'] : null;
1626
1627
        // Question type
1628
        $answerType = isset($_REQUEST['answerType']) ? (int) $_REQUEST['answerType'] : null;
1629
        $form->addElement('hidden', 'answerType', $answerType);
1630
1631
        // html editor
1632
        $editorConfig = [
1633
            'ToolbarSet' => 'TestQuestionDescription',
1634
            'Height' => '150',
1635
        ];
1636
1637
        if (!api_is_allowed_to_edit(null, true)) {
1638
            $editorConfig['UserStatus'] = 'student';
1639
        }
1640
1641
        $form->addButtonAdvancedSettings('advanced_params');
1642
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1643
        $form->addHtmlEditor(
1644
            'questionDescription',
1645
            get_lang('QuestionDescription'),
1646
            false,
1647
            false,
1648
            $editorConfig
1649
        );
1650
1651
        if ($this->type != MEDIA_QUESTION) {
1652
            // Advanced parameters
1653
            $select_level = self::get_default_levels();
1654
            $form->addElement(
1655
                'select',
1656
                'questionLevel',
1657
                get_lang('Difficulty'),
1658
                $select_level
1659
            );
1660
1661
            // Categories
1662
            $tabCat = TestCategory::getCategoriesIdAndName();
1663
            $form->addElement(
1664
                'select',
1665
                'questionCategory',
1666
                get_lang('Category'),
1667
                $tabCat
1668
            );
1669
1670
            global $text;
1671
1672
            switch ($this->type) {
1673
                case UNIQUE_ANSWER:
1674
                    $buttonGroup = [];
1675
                    $buttonGroup[] = $form->addButtonSave(
1676
                        $text,
1677
                        'submitQuestion',
1678
                        true
1679
                    );
1680
                    $buttonGroup[] = $form->addButton(
1681
                        'convertAnswer',
1682
                        get_lang('ConvertToMultipleAnswer'),
1683
                        'dot-circle-o',
1684
                        'default',
1685
                        null,
1686
                        null,
1687
                        null,
1688
                        true
1689
                    );
1690
                    $form->addGroup($buttonGroup);
1691
                    break;
1692
                case MULTIPLE_ANSWER:
1693
                    $buttonGroup = [];
1694
                    $buttonGroup[] = $form->addButtonSave(
1695
                        $text,
1696
                        'submitQuestion',
1697
                        true
1698
                    );
1699
                    $buttonGroup[] = $form->addButton(
1700
                        'convertAnswer',
1701
                        get_lang('ConvertToUniqueAnswer'),
1702
                        'check-square-o',
1703
                        'default',
1704
                        null,
1705
                        null,
1706
                        null,
1707
                        true
1708
                    );
1709
                    $form->addGroup($buttonGroup);
1710
                    break;
1711
            }
1712
            //Medias
1713
            //$course_medias = self::prepare_course_media_select(api_get_course_int_id());
1714
            //$form->addElement('select', 'parent_id', get_lang('AttachToMedia'), $course_medias);
1715
        }
1716
1717
        $form->addElement('html', '</div>');
1718
1719
        if (!isset($_GET['fromExercise'])) {
1720
            switch ($answerType) {
1721
                case 1:
1722
                    $this->question = get_lang('DefaultUniqueQuestion');
1723
                    break;
1724
                case 2:
1725
                    $this->question = get_lang('DefaultMultipleQuestion');
1726
                    break;
1727
                case 3:
1728
                    $this->question = get_lang('DefaultFillBlankQuestion');
1729
                    break;
1730
                case 4:
1731
                    $this->question = get_lang('DefaultMathingQuestion');
1732
                    break;
1733
                case 5:
1734
                    $this->question = get_lang('DefaultOpenQuestion');
1735
                    break;
1736
                case 9:
1737
                    $this->question = get_lang('DefaultMultipleQuestion');
1738
                    break;
1739
            }
1740
        }
1741
1742
        if (!is_null($exercise)) {
1743
            if ($exercise->questionFeedbackEnabled && $this->showFeedback($exercise)) {
1744
                $form->addTextarea('feedback', get_lang('FeedbackIfNotCorrect'));
1745
            }
1746
        }
1747
1748
        // default values
1749
        $defaults = [];
1750
        $defaults['questionName'] = $this->question;
1751
        $defaults['questionDescription'] = $this->description;
1752
        $defaults['questionLevel'] = $this->level;
1753
        $defaults['questionCategory'] = $this->category;
1754
        $defaults['feedback'] = $this->feedback;
1755
1756
        // Came from he question pool
1757
        if (isset($_GET['fromExercise'])) {
1758
            $form->setDefaults($defaults);
1759
        }
1760
1761
        if (!isset($_GET['newQuestion']) || $isContent) {
1762
            $form->setDefaults($defaults);
1763
        }
1764
1765
        /*if (!empty($_REQUEST['myid'])) {
1766
            $form->setDefaults($defaults);
1767
        } else {
1768
            if ($isContent == 1) {
1769
                $form->setDefaults($defaults);
1770
            }
1771
        }*/
1772
    }
1773
1774
    /**
1775
     * function which process the creation of questions.
1776
     *
1777
     * @param FormValidator $form
1778
     * @param Exercise      $exercise
1779
     */
1780
    public function processCreation($form, $exercise)
1781
    {
1782
        $this->updateTitle($form->getSubmitValue('questionName'));
1783
        $this->updateDescription($form->getSubmitValue('questionDescription'));
1784
        $this->updateLevel($form->getSubmitValue('questionLevel'));
1785
        $this->updateCategory($form->getSubmitValue('questionCategory'));
1786
        $this->setFeedback($form->getSubmitValue('feedback'));
1787
1788
        //Save normal question if NOT media
1789
        if ($this->type != MEDIA_QUESTION) {
1790
            $this->save($exercise);
1791
            // modify the exercise
1792
            $exercise->addToList($this->id);
1793
            $exercise->update_question_positions();
1794
        }
1795
    }
1796
1797
    /**
1798
     * abstract function which creates the form to create / edit the answers of the question.
1799
     *
1800
     * @param FormValidator $form
1801
     */
1802
    abstract public function createAnswersForm($form);
1803
1804
    /**
1805
     * abstract function which process the creation of answers.
1806
     *
1807
     * @param FormValidator $form
1808
     * @param Exercise      $exercise
1809
     */
1810
    abstract public function processAnswersCreation($form, $exercise);
1811
1812
    /**
1813
     * Displays the menu of question types.
1814
     *
1815
     * @param Exercise $objExercise
1816
     */
1817
    public static function displayTypeMenu($objExercise)
1818
    {
1819
        $feedback_type = $objExercise->feedback_type;
1820
        $exerciseId = $objExercise->id;
1821
1822
        // 1. by default we show all the question types
1823
        $question_type_custom_list = self::get_question_type_list();
1824
1825
        if (!isset($feedback_type)) {
1826
            $feedback_type = 0;
1827
        }
1828
1829
        if ($feedback_type == 1) {
1830
            //2. but if it is a feedback DIRECT we only show the UNIQUE_ANSWER type that is currently available
1831
            $question_type_custom_list = [
1832
                UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
1833
                HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION],
1834
            ];
1835
        } else {
1836
            unset($question_type_custom_list[HOT_SPOT_DELINEATION]);
1837
        }
1838
1839
        echo '<div class="card card-exercise">';
1840
        echo '<div class="card-body">';
1841
        echo '<ul class="list-exercise">';
1842
        foreach ($question_type_custom_list as $i => $a_type) {
1843
            // @todo remove require_once classes are already loaded using composer
1844
            // include the class of the type
1845
            require_once $a_type[0];
1846
            // get the picture of the type and the langvar which describes it
1847
            $img = $explanation = '';
1848
            eval('$img = '.$a_type[1].'::$typePicture;');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1849
            eval('$explanation = get_lang('.$a_type[1].'::$explanationLangVar);');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1850
            echo '<li>';
1851
            echo '<div class="icon-image">';
1852
            $icon = '<a href="admin.php?'.api_get_cidreq().'&newQuestion=yes&answerType='.$i.'">'.
1853
                Display::return_icon($img, $explanation, null, ICON_SIZE_BIG).'</a>';
1854
1855
            if ($objExercise->force_edit_exercise_in_lp === false) {
1856
                if ($objExercise->exercise_was_added_in_lp == true) {
1857
                    $img = pathinfo($img);
1858
                    $img = $img['filename'].'_na.'.$img['extension'];
1859
                    $icon = Display::return_icon($img, $explanation, null, ICON_SIZE_BIG);
1860
                }
1861
            }
1862
1863
            echo $icon;
1864
            echo '</div>';
1865
            echo '</li>';
1866
        }
1867
1868
        echo '<li>';
1869
        echo '<div class="icon_image_content">';
1870
        if ($objExercise->exercise_was_added_in_lp == true) {
1871
            echo Display::return_icon(
1872
                'database_na.png',
1873
                get_lang('GetExistingQuestion'),
1874
                null,
1875
                ICON_SIZE_BIG
1876
            );
1877
        } else {
1878
            if ($feedback_type == 1) {
1879
                echo $url = "<a href=\"question_pool.php?".api_get_cidreq()."&type=1&fromExercise=$exerciseId\">";
1880
            } else {
1881
                echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&fromExercise='.$exerciseId.'">';
1882
            }
1883
            echo Display::return_icon(
1884
                'database.png',
1885
                get_lang('GetExistingQuestion'),
1886
                null,
1887
                ICON_SIZE_BIG
1888
            );
1889
        }
1890
        echo '</a>';
1891
        echo '</div></li>';
1892
        echo '</ul>';
1893
        echo '</div>';
1894
        echo '</div>';
1895
    }
1896
1897
    /**
1898
     * @param int    $question_id
1899
     * @param string $name
1900
     * @param int    $course_id
1901
     * @param int    $position
1902
     *
1903
     * @return false|string
1904
     */
1905
    public static function saveQuestionOption($question_id, $name, $course_id, $position = 0)
1906
    {
1907
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1908
        $params['question_id'] = (int) $question_id;
1909
        $params['name'] = $name;
1910
        $params['position'] = $position;
1911
        $params['c_id'] = $course_id;
1912
        $result = self::readQuestionOption($question_id, $course_id);
1913
        $last_id = Database::insert($table, $params);
1914
        if ($last_id) {
1915
            $sql = "UPDATE $table SET id = iid WHERE iid = $last_id";
1916
            Database::query($sql);
1917
        }
1918
1919
        return $last_id;
1920
    }
1921
1922
    /**
1923
     * @param int $question_id
1924
     * @param int $course_id
1925
     */
1926
    public static function deleteAllQuestionOptions($question_id, $course_id)
1927
    {
1928
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1929
        Database::delete(
1930
            $table,
1931
            [
1932
                'c_id = ? AND question_id = ?' => [
1933
                    $course_id,
1934
                    $question_id,
1935
                ],
1936
            ]
1937
        );
1938
    }
1939
1940
    /**
1941
     * @param int   $id
1942
     * @param array $params
1943
     * @param int   $course_id
1944
     *
1945
     * @return bool|int
1946
     */
1947
    public static function updateQuestionOption($id, $params, $course_id)
1948
    {
1949
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1950
        $result = Database::update(
1951
            $table,
1952
            $params,
1953
            ['c_id = ? AND id = ?' => [$course_id, $id]]
1954
        );
1955
1956
        return $result;
1957
    }
1958
1959
    /**
1960
     * @param int $question_id
1961
     * @param int $course_id
1962
     *
1963
     * @return array
1964
     */
1965
    public static function readQuestionOption($question_id, $course_id)
1966
    {
1967
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1968
        $result = Database::select(
1969
            '*',
1970
            $table,
1971
            [
1972
                'where' => [
1973
                    'c_id = ? AND question_id = ?' => [
1974
                        $course_id,
1975
                        $question_id,
1976
                    ],
1977
                ],
1978
                'order' => 'id ASC',
1979
            ]
1980
        );
1981
1982
        return $result;
1983
    }
1984
1985
    /**
1986
     * Shows question title an description.
1987
     *
1988
     * @param Exercise $exercise
1989
     * @param int      $counter
1990
     * @param array    $score
1991
     *
1992
     * @return string HTML string with the header of the question (before the answers table)
1993
     */
1994
    public function return_header(Exercise $exercise, $counter = null, $score = [])
1995
    {
1996
        $counterLabel = '';
1997
        if (!empty($counter)) {
1998
            $counterLabel = (int) $counter;
1999
        }
2000
2001
        $scoreLabel = get_lang('Wrong');
2002
2003
        if ($exercise->results_disabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
2004
            $scoreLabel = get_lang('QuizWrongAnswerHereIsTheCorrectOne');
2005
        }
2006
2007
        $class = 'error';
2008
        if (isset($score['pass']) && $score['pass'] == true) {
2009
            $scoreLabel = get_lang('Correct');
2010
2011
            if ($exercise->results_disabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
2012
                $scoreLabel = get_lang('CorrectAnswer');
2013
            }
2014
2015
            $class = 'success';
2016
        }
2017
2018
        switch ($this->type) {
2019
            case FREE_ANSWER:
2020
            case ORAL_EXPRESSION:
2021
            case ANNOTATION:
2022
                $score['revised'] = isset($score['revised']) ? $score['revised'] : false;
2023
                if ($score['revised'] == true) {
2024
                    $scoreLabel = get_lang('Revised');
2025
                    $class = '';
2026
                } else {
2027
                    $scoreLabel = get_lang('NotRevised');
2028
                    $class = 'warning';
2029
                    if (isset($score['weight'])) {
2030
                        $weight = float_format($score['weight'], 1);
2031
                        $score['result'] = ' ? / '.$weight;
2032
                    }
2033
                    $model = ExerciseLib::getCourseScoreModel();
2034
                    if (!empty($model)) {
2035
                        $score['result'] = ' ? ';
2036
                    }
2037
2038
                    $hide = api_get_configuration_value('hide_free_question_score');
2039
                    if ($hide === true) {
2040
                        $score['result'] = '-';
2041
                    }
2042
                }
2043
                break;
2044
            case UNIQUE_ANSWER:
2045
                if ($exercise->results_disabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
2046
                    if (isset($score['user_answered'])) {
2047
                        if ($score['user_answered'] === false) {
2048
                            $scoreLabel = get_lang('Unanswered');
2049
                            $class = 'info';
2050
                        }
2051
                    }
2052
                }
2053
                break;
2054
        }
2055
2056
        // display question category, if any
2057
        $header = '';
2058
        if ($exercise->display_category_name) {
2059
            $header = TestCategory::returnCategoryAndTitle($this->id);
2060
        }
2061
        $show_media = '';
2062
        if ($show_media) {
2063
            $header .= $this->show_media_content();
2064
        }
2065
2066
        $scoreCurrent = [
2067
            'used' => isset($score['score']) ? $score['score'] : '',
2068
            'missing' => isset($score['weight']) ? $score['weight'] : '',
2069
        ];
2070
        $header .= Display::page_subheader2($counterLabel.'. '.$this->question);
2071
2072
        // dont display score for certainty degree questions
2073
        if ($this->type != MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
2074
            if (isset($score['result'])) {
2075
                if ($exercise->results_disabled == RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER) {
2076
                    $score['result'] = null;
2077
                }
2078
                $header .= $exercise->getQuestionRibbon($class, $scoreLabel, $score['result'], $scoreCurrent);
2079
            }
2080
        }
2081
2082
        if ($this->type != READING_COMPREHENSION) {
2083
            // Do not show the description (the text to read) if the question is of type READING_COMPREHENSION
2084
            $header .= Display::div(
2085
                $this->description,
2086
                ['class' => 'question_description']
2087
            );
2088
        } else {
2089
            if ($score['pass'] == true) {
2090
                $message = Display::div(
2091
                    sprintf(
2092
                        get_lang('ReadingQuestionCongratsSpeedXReachedForYWords'),
2093
                        ReadingComprehension::$speeds[$this->level],
2094
                        $this->getWordsCount()
2095
                    )
2096
                );
2097
            } else {
2098
                $message = Display::div(
2099
                    sprintf(
2100
                        get_lang('ReadingQuestionCongratsSpeedXNotReachedForYWords'),
2101
                        ReadingComprehension::$speeds[$this->level],
2102
                        $this->getWordsCount()
2103
                    )
2104
                );
2105
            }
2106
            $header .= $message.'<br />';
2107
        }
2108
2109
        if (isset($score['pass']) && $score['pass'] === false) {
2110
            if ($this->showFeedback($exercise)) {
2111
                $header .= $this->returnFormatFeedback();
2112
            }
2113
        }
2114
2115
        return $header;
2116
    }
2117
2118
    /**
2119
     * @deprecated
2120
     * Create a question from a set of parameters.
2121
     *
2122
     * @param   int     Quiz ID
2123
     * @param   string  Question name
2124
     * @param   int     Maximum result for the question
2125
     * @param   int     Type of question (see constants at beginning of question.class.php)
2126
     * @param   int     Question level/category
2127
     * @param string $quiz_id
2128
     */
2129
    public function create_question(
2130
        $quiz_id,
2131
        $question_name,
2132
        $question_description = '',
2133
        $max_score = 0,
2134
        $type = 1,
2135
        $level = 1
2136
    ) {
2137
        $course_id = api_get_course_int_id();
2138
        $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2139
        $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
2140
2141
        $quiz_id = intval($quiz_id);
2142
        $max_score = (float) $max_score;
2143
        $type = intval($type);
2144
        $level = intval($level);
2145
2146
        // Get the max position
2147
        $sql = "SELECT max(position) as max_position
2148
                FROM $tbl_quiz_question q 
2149
                INNER JOIN $tbl_quiz_rel_question r
2150
                ON
2151
                    q.id = r.question_id AND
2152
                    exercice_id = $quiz_id AND
2153
                    q.c_id = $course_id AND
2154
                    r.c_id = $course_id";
2155
        $rs_max = Database::query($sql);
2156
        $row_max = Database::fetch_object($rs_max);
2157
        $max_position = $row_max->max_position + 1;
2158
2159
        $params = [
2160
            'c_id' => $course_id,
2161
            'question' => $question_name,
2162
            'description' => $question_description,
2163
            'ponderation' => $max_score,
2164
            'position' => $max_position,
2165
            'type' => $type,
2166
            'level' => $level,
2167
        ];
2168
        $question_id = Database::insert($tbl_quiz_question, $params);
2169
2170
        if ($question_id) {
2171
            $sql = "UPDATE $tbl_quiz_question  
2172
                    SET id = iid WHERE iid = $question_id";
2173
            Database::query($sql);
2174
2175
            // Get the max question_order
2176
            $sql = "SELECT max(question_order) as max_order
2177
                    FROM $tbl_quiz_rel_question
2178
                    WHERE c_id = $course_id AND exercice_id = $quiz_id ";
2179
            $rs_max_order = Database::query($sql);
2180
            $row_max_order = Database::fetch_object($rs_max_order);
2181
            $max_order = $row_max_order->max_order + 1;
2182
            // Attach questions to quiz
2183
            $sql = "INSERT INTO $tbl_quiz_rel_question (c_id, question_id, exercice_id, question_order)
2184
                    VALUES($course_id, $question_id, $quiz_id, $max_order)";
2185
            Database::query($sql);
2186
        }
2187
2188
        return $question_id;
2189
    }
2190
2191
    /**
2192
     * @return array the image filename of the question type
2193
     */
2194
    public function get_type_icon_html()
2195
    {
2196
        $type = $this->selectType();
2197
        $tabQuestionList = self::get_question_type_list(); // [0]=file to include [1]=type name
2198
2199
        require_once $tabQuestionList[$type][0];
2200
2201
        $img = $explanation = null;
2202
        eval('$img = '.$tabQuestionList[$type][1].'::$typePicture;');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
2203
        eval('$explanation = get_lang('.$tabQuestionList[$type][1].'::$explanationLangVar);');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
2204
2205
        return [$img, $explanation];
2206
    }
2207
2208
    /**
2209
     * Get course medias.
2210
     *
2211
     * @param int course id
2212
     * @param int $course_id
2213
     *
2214
     * @return array
2215
     */
2216
    public static function get_course_medias(
2217
        $course_id,
2218
        $start = 0,
2219
        $limit = 100,
2220
        $sidx = "question",
2221
        $sord = "ASC",
2222
        $where_condition = []
2223
    ) {
2224
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2225
        $default_where = [
2226
            'c_id = ? AND parent_id = 0 AND type = ?' => [
2227
                $course_id,
2228
                MEDIA_QUESTION,
2229
            ],
2230
        ];
2231
        $result = Database::select(
2232
            '*',
2233
            $table_question,
2234
            [
2235
                'limit' => " $start, $limit",
2236
                'where' => $default_where,
2237
                'order' => "$sidx $sord",
2238
            ]
2239
        );
2240
2241
        return $result;
2242
    }
2243
2244
    /**
2245
     * Get count course medias.
2246
     *
2247
     * @param int course id
2248
     *
2249
     * @return int
2250
     */
2251
    public static function get_count_course_medias($course_id)
2252
    {
2253
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2254
        $result = Database::select(
2255
            'count(*) as count',
2256
            $table_question,
2257
            [
2258
                'where' => [
2259
                    'c_id = ? AND parent_id = 0 AND type = ?' => [
2260
                        $course_id,
2261
                        MEDIA_QUESTION,
2262
                    ],
2263
                ],
2264
            ],
2265
            'first'
2266
        );
2267
2268
        if ($result && isset($result['count'])) {
2269
            return $result['count'];
2270
        }
2271
2272
        return 0;
2273
    }
2274
2275
    /**
2276
     * @param int $course_id
2277
     *
2278
     * @return array
2279
     */
2280
    public static function prepare_course_media_select($course_id)
2281
    {
2282
        $medias = self::get_course_medias($course_id);
2283
        $media_list = [];
2284
        $media_list[0] = get_lang('NoMedia');
2285
2286
        if (!empty($medias)) {
2287
            foreach ($medias as $media) {
2288
                $media_list[$media['id']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
2289
            }
2290
        }
2291
2292
        return $media_list;
2293
    }
2294
2295
    /**
2296
     * @return array
2297
     */
2298
    public static function get_default_levels()
2299
    {
2300
        $levels = [
2301
            1 => 1,
2302
            2 => 2,
2303
            3 => 3,
2304
            4 => 4,
2305
            5 => 5,
2306
        ];
2307
2308
        return $levels;
2309
    }
2310
2311
    /**
2312
     * @return string
2313
     */
2314
    public function show_media_content()
2315
    {
2316
        $html = '';
2317
        if ($this->parent_id != 0) {
2318
            $parent_question = self::read($this->parent_id);
2319
            $html = $parent_question->show_media_content();
2320
        } else {
2321
            $html .= Display::page_subheader($this->selectTitle());
2322
            $html .= $this->selectDescription();
2323
        }
2324
2325
        return $html;
2326
    }
2327
2328
    /**
2329
     * Swap between unique and multiple type answers.
2330
     *
2331
     * @return UniqueAnswer|MultipleAnswer
2332
     */
2333
    public function swapSimpleAnswerTypes()
2334
    {
2335
        $oppositeAnswers = [
2336
            UNIQUE_ANSWER => MULTIPLE_ANSWER,
2337
            MULTIPLE_ANSWER => UNIQUE_ANSWER,
2338
        ];
2339
        $this->type = $oppositeAnswers[$this->type];
2340
        Database::update(
2341
            Database::get_course_table(TABLE_QUIZ_QUESTION),
2342
            ['type' => $this->type],
2343
            ['c_id = ? AND id = ?' => [$this->course['real_id'], $this->id]]
2344
        );
2345
        $answerClasses = [
2346
            UNIQUE_ANSWER => 'UniqueAnswer',
2347
            MULTIPLE_ANSWER => 'MultipleAnswer',
2348
        ];
2349
        $swappedAnswer = new $answerClasses[$this->type]();
2350
        foreach ($this as $key => $value) {
2351
            $swappedAnswer->$key = $value;
2352
        }
2353
2354
        return $swappedAnswer;
2355
    }
2356
2357
    /**
2358
     * @param array $score
2359
     *
2360
     * @return bool
2361
     */
2362
    public function isQuestionWaitingReview($score)
2363
    {
2364
        $isReview = false;
2365
        if (!empty($score)) {
2366
            if (!empty($score['comments']) || $score['score'] > 0) {
2367
                $isReview = true;
2368
            }
2369
        }
2370
2371
        return $isReview;
2372
    }
2373
2374
    /**
2375
     * @param string $value
2376
     */
2377
    public function setFeedback($value)
2378
    {
2379
        $this->feedback = $value;
2380
    }
2381
2382
    /**
2383
     * @param Exercise $exercise
2384
     *
2385
     * @return bool
2386
     */
2387
    public function showFeedback($exercise)
2388
    {
2389
        return
2390
            in_array($this->type, $this->questionTypeWithFeedback) &&
2391
            $exercise->feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM;
2392
    }
2393
2394
    /**
2395
     * @return string
2396
     */
2397
    public function returnFormatFeedback()
2398
    {
2399
        return '<br />'.Display::return_message($this->feedback, 'normal', false);
2400
    }
2401
2402
    /**
2403
     * Check if this question exists in another exercise.
2404
     *
2405
     * @throws \Doctrine\ORM\Query\QueryException
2406
     *
2407
     * @return bool
2408
     */
2409
    public function existsInAnotherExercise()
2410
    {
2411
        $count = $this->getCountExercise();
2412
2413
        return $count > 1;
2414
    }
2415
2416
    /**
2417
     * @throws \Doctrine\ORM\Query\QueryException
2418
     *
2419
     * @return int
2420
     */
2421
    public function getCountExercise()
2422
    {
2423
        $em = Database::getManager();
2424
2425
        $count = $em
2426
            ->createQuery('
2427
                SELECT COUNT(qq.iid) FROM ChamiloCourseBundle:CQuizRelQuestion qq
2428
                WHERE qq.questionId = :id
2429
            ')
2430
            ->setParameters(['id' => (int) $this->id])
2431
            ->getSingleScalarResult();
2432
2433
        return (int) $count;
2434
    }
2435
2436
    /**
2437
     * Check if this question exists in another exercise.
2438
     *
2439
     * @throws \Doctrine\ORM\Query\QueryException
2440
     *
2441
     * @return mixed
2442
     */
2443
    public function getExerciseListWhereQuestionExists()
2444
    {
2445
        $em = Database::getManager();
2446
2447
        $result = $em
2448
            ->createQuery('
2449
                SELECT e 
2450
                FROM ChamiloCourseBundle:CQuizRelQuestion qq
2451
                JOIN ChamiloCourseBundle:CQuiz e                
2452
                WHERE e.iid = qq.exerciceId AND qq.questionId = :id 
2453
            ')
2454
            ->setParameters(['id' => (int) $this->id])
2455
            ->getResult();
2456
2457
        return $result;
2458
    }
2459
2460
    public function getHotSpotData()
2461
    {
2462
    }
2463
}
2464