Completed
Push — master ( 3cd2b7...bcef33 )
by Julito
09:26
created

Question::getPicture()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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