Completed
Push — master ( 6033c5...af0fd1 )
by Julito
10:22
created

Question::getIsContent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\Resource\ResourceLink;
5
use Chamilo\CoreBundle\Framework\Container;
6
use Chamilo\CourseBundle\Entity\CDocument;
7
use Chamilo\CourseBundle\Entity\CQuizAnswer;
8
use Chamilo\CourseBundle\Entity\CQuizQuestion;
9
10
/**
11
 * Class Question.
12
 *
13
 * This class allows to instantiate an object of type Question
14
 *
15
 * @author Olivier Brouckaert, original author
16
 * @author Patrick Cool, LaTeX support
17
 * @author Julio Montoya <[email protected]> lot of bug fixes
18
 * @author [email protected] - add question categories
19
 *
20
 * @package chamilo.exercise
21
 */
22
abstract class Question
23
{
24
    public $id;
25
    public $iid;
26
    public $question;
27
    public $description;
28
    public $weighting;
29
    public $position;
30
    public $type;
31
    public $level;
32
    public $picture;
33
    public $exerciseList; // array with the list of exercises which this question is in
34
    public $category_list;
35
    public $parent_id;
36
    public $category;
37
    public $isContent;
38
    public $course;
39
    public $feedback;
40
    public $typePicture = 'new_question.png';
41
    public $explanationLangVar = '';
42
    public $question_table_class = 'table table-striped';
43
    public $questionTypeWithFeedback;
44
    public $extra;
45
    public $export = false;
46
    public $code;
47
    public static $questionTypes = [
48
        UNIQUE_ANSWER => ['unique_answer.class.php', 'UniqueAnswer'],
49
        MULTIPLE_ANSWER => ['multiple_answer.class.php', 'MultipleAnswer'],
50
        FILL_IN_BLANKS => ['fill_blanks.class.php', 'FillBlanks'],
51
        MATCHING => ['matching.class.php', 'Matching'],
52
        FREE_ANSWER => ['freeanswer.class.php', 'FreeAnswer'],
53
        ORAL_EXPRESSION => ['oral_expression.class.php', 'OralExpression'],
54
        HOT_SPOT => ['hotspot.class.php', 'HotSpot'],
55
        HOT_SPOT_DELINEATION => ['HotSpotDelineation.php', 'HotSpotDelineation'],
56
        MULTIPLE_ANSWER_COMBINATION => ['multiple_answer_combination.class.php', 'MultipleAnswerCombination'],
57
        UNIQUE_ANSWER_NO_OPTION => ['unique_answer_no_option.class.php', 'UniqueAnswerNoOption'],
58
        MULTIPLE_ANSWER_TRUE_FALSE => ['multiple_answer_true_false.class.php', 'MultipleAnswerTrueFalse'],
59
        MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY => [
60
            'MultipleAnswerTrueFalseDegreeCertainty.php',
61
            'MultipleAnswerTrueFalseDegreeCertainty',
62
        ],
63
        MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE => [
64
            'multiple_answer_combination_true_false.class.php',
65
            'MultipleAnswerCombinationTrueFalse',
66
        ],
67
        GLOBAL_MULTIPLE_ANSWER => ['global_multiple_answer.class.php', 'GlobalMultipleAnswer'],
68
        CALCULATED_ANSWER => ['calculated_answer.class.php', 'CalculatedAnswer'],
69
        UNIQUE_ANSWER_IMAGE => ['UniqueAnswerImage.php', 'UniqueAnswerImage'],
70
        DRAGGABLE => ['Draggable.php', 'Draggable'],
71
        MATCHING_DRAGGABLE => ['MatchingDraggable.php', 'MatchingDraggable'],
72
        //MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion')
73
        ANNOTATION => ['Annotation.php', 'Annotation'],
74
        READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'],
75
    ];
76
77
    /**
78
     * constructor of the class.
79
     *
80
     * @author Olivier Brouckaert
81
     */
82
    public function __construct()
83
    {
84
        $this->id = 0;
85
        $this->iid = 0;
86
        $this->question = '';
87
        $this->description = '';
88
        $this->weighting = 0;
89
        $this->position = 1;
90
        $this->picture = '';
91
        $this->level = 1;
92
        $this->category = 0;
93
        // This variable is used when loading an exercise like an scenario with
94
        // an special hotspot: final_overlap, final_missing, final_excess
95
        $this->extra = '';
96
        $this->exerciseList = [];
97
        $this->course = api_get_course_info();
98
        $this->category_list = [];
99
        $this->parent_id = 0;
100
        // See BT#12611
101
        $this->questionTypeWithFeedback = [
102
            MATCHING,
103
            MATCHING_DRAGGABLE,
104
            DRAGGABLE,
105
            FILL_IN_BLANKS,
106
            FREE_ANSWER,
107
            ORAL_EXPRESSION,
108
            CALCULATED_ANSWER,
109
            ANNOTATION,
110
        ];
111
    }
112
113
    /**
114
     * @return int|null
115
     */
116
    public function getIsContent()
117
    {
118
        $isContent = null;
119
        if (isset($_REQUEST['isContent'])) {
120
            $isContent = intval($_REQUEST['isContent']);
121
        }
122
123
        return $this->isContent = $isContent;
124
    }
125
126
    /**
127
     * Reads question information from the data base.
128
     *
129
     * @param int   $id              - question ID
130
     * @param array $course_info
131
     * @param bool  $getExerciseList
132
     *
133
     * @return Question
134
     *
135
     * @author Olivier Brouckaert
136
     */
137
    public static function read($id, $course_info = [], $getExerciseList = true)
138
    {
139
        $id = (int) $id;
140
        if (empty($course_info)) {
141
            $course_info = api_get_course_info();
142
        }
143
        $course_id = $course_info['real_id'];
144
145
        if (empty($course_id) || $course_id == -1) {
146
            return false;
147
        }
148
149
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
150
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
151
152
        $sql = "SELECT *
153
                FROM $TBL_QUESTIONS
154
                WHERE c_id = $course_id AND id = $id ";
155
        $result = Database::query($sql);
156
157
        // if the question has been found
158
        if ($object = Database::fetch_object($result)) {
159
            $objQuestion = self::getInstance($object->type);
160
            if (!empty($objQuestion)) {
161
                $objQuestion->id = (int) $id;
162
                $objQuestion->iid = (int) $object->iid;
163
                $objQuestion->question = $object->question;
164
                $objQuestion->description = $object->description;
165
                $objQuestion->weighting = $object->ponderation;
166
                $objQuestion->position = $object->position;
167
                $objQuestion->type = (int) $object->type;
168
                $objQuestion->picture = $object->picture;
169
                $objQuestion->level = (int) $object->level;
170
                $objQuestion->extra = $object->extra;
171
                $objQuestion->course = $course_info;
172
                $objQuestion->feedback = isset($object->feedback) ? $object->feedback : '';
173
                $objQuestion->category = TestCategory::getCategoryForQuestion($id, $course_id);
174
                $objQuestion->code = isset($object->code) ? $object->code : '';
175
176
                if ($getExerciseList) {
177
                    $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
178
                    $sql = "SELECT DISTINCT q.exercice_id
179
                            FROM $TBL_EXERCISE_QUESTION q
180
                            INNER JOIN $tblQuiz e
181
                            ON e.c_id = q.c_id AND e.id = q.exercice_id
182
                            WHERE
183
                                q.c_id = $course_id AND
184
                                q.question_id = $id AND
185
                                e.active >= 0";
186
187
                    $result = Database::query($sql);
188
189
                    // fills the array with the exercises which this question is in
190
                    if ($result) {
0 ignored issues
show
introduced by
$result is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
191
                        while ($obj = Database::fetch_object($result)) {
192
                            $objQuestion->exerciseList[] = $obj->exercice_id;
193
                        }
194
                    }
195
                }
196
197
                return $objQuestion;
198
            }
199
        }
200
201
        // question not found
202
        return false;
203
    }
204
205
    /**
206
     * returns the question ID.
207
     *
208
     * @author Olivier Brouckaert
209
     *
210
     * @return int - question ID
211
     */
212
    public function selectId()
213
    {
214
        return $this->id;
215
    }
216
217
    /**
218
     * returns the question title.
219
     *
220
     * @author Olivier Brouckaert
221
     *
222
     * @return string - question title
223
     */
224
    public function selectTitle()
225
    {
226
        if (!api_get_configuration_value('save_titles_as_html')) {
227
            return $this->question;
228
        }
229
230
        return Display::div($this->question, ['style' => 'display: inline-block;']);
231
    }
232
233
    /**
234
     * @param int $itemNumber
235
     *
236
     * @return string
237
     */
238
    public function getTitleToDisplay($itemNumber)
239
    {
240
        $showQuestionTitleHtml = api_get_configuration_value('save_titles_as_html');
241
        $title = '';
242
        if (api_get_configuration_value('show_question_id')) {
243
            $title .= '<h4>#'.$this->course['code'].'-'.$this->iid.'</h4>';
244
        }
245
246
        $title .= $showQuestionTitleHtml ? '' : '<strong>';
247
        $title .= $itemNumber.'. '.$this->selectTitle();
248
        $title .= $showQuestionTitleHtml ? '' : '</strong>';
249
250
        return Display::div(
251
            $title,
252
            ['class' => 'question_title']
253
        );
254
    }
255
256
    /**
257
     * returns the question description.
258
     *
259
     * @author Olivier Brouckaert
260
     *
261
     * @return string - question description
262
     */
263
    public function selectDescription()
264
    {
265
        return $this->description;
266
    }
267
268
    /**
269
     * returns the question weighting.
270
     *
271
     * @author Olivier Brouckaert
272
     *
273
     * @return int - question weighting
274
     */
275
    public function selectWeighting()
276
    {
277
        return $this->weighting;
278
    }
279
280
    /**
281
     * returns the question position.
282
     *
283
     * @author Olivier Brouckaert
284
     *
285
     * @return int - question position
286
     */
287
    public function selectPosition()
288
    {
289
        return $this->position;
290
    }
291
292
    /**
293
     * returns the answer type.
294
     *
295
     * @author Olivier Brouckaert
296
     *
297
     * @return int - answer type
298
     */
299
    public function selectType()
300
    {
301
        return $this->type;
302
    }
303
304
    /**
305
     * returns the level of the question.
306
     *
307
     * @author Nicolas Raynaud
308
     *
309
     * @return int - level of the question, 0 by default
310
     */
311
    public function getLevel()
312
    {
313
        return $this->level;
314
    }
315
316
    /**
317
     * returns the picture name.
318
     *
319
     * @author Olivier Brouckaert
320
     *
321
     * @return string - picture name
322
     */
323
    public function selectPicture()
324
    {
325
        return $this->picture;
326
    }
327
328
    /**
329
     * @return int|string
330
     */
331
    public function getPictureId()
332
    {
333
        // for backward compatibility
334
        // when in field picture we had the filename not the document id
335
        if (preg_match("/quiz-.*/", $this->picture)) {
336
            return DocumentManager::get_document_id(
337
                $this->course,
338
                $this->selectPicturePath(),
339
                api_get_session_id()
340
            );
341
        }
342
343
        return $this->picture;
344
    }
345
346
    /**
347
     * @param int $courseId
348
     * @param int $sessionId
349
     *
350
     * @return false|CDocument
351
     */
352
    public function getPicture($courseId = 0, $sessionId = 0)
353
    {
354
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
355
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
356
357
        if (empty($courseId)) {
358
            return false;
359
        }
360
361
        $pictureId = $this->getPictureId();
362
        $courseInfo = $this->course;
363
        $documentInfo = DocumentManager::get_document_data_by_id(
364
            $pictureId,
365
            $courseInfo['code'],
366
            false,
367
            $sessionId
368
        );
369
370
        if ($documentInfo) {
371
            $em = Database::getManager();
372
373
            /** @var CDocument $document */
374
            $document = $em->getRepository('ChamiloCourseBundle:CDocument')->find($documentInfo['iid']);
375
376
            return $document;
377
        }
378
379
        return false;
380
    }
381
382
    /**
383
     * returns the array with the exercise ID list.
384
     *
385
     * @author Olivier Brouckaert
386
     *
387
     * @return array - list of exercise ID which the question is in
388
     */
389
    public function selectExerciseList()
390
    {
391
        return $this->exerciseList;
392
    }
393
394
    /**
395
     * returns the number of exercises which this question is in.
396
     *
397
     * @author Olivier Brouckaert
398
     *
399
     * @return int - number of exercises
400
     */
401
    public function selectNbrExercises()
402
    {
403
        return count($this->exerciseList);
404
    }
405
406
    /**
407
     * changes the question title.
408
     *
409
     * @param string $title - question title
410
     *
411
     * @author Olivier Brouckaert
412
     */
413
    public function updateTitle($title)
414
    {
415
        $this->question = $title;
416
    }
417
418
    /**
419
     * @param int $id
420
     */
421
    public function updateParentId($id)
422
    {
423
        $this->parent_id = (int) $id;
424
    }
425
426
    /**
427
     * changes the question description.
428
     *
429
     * @param string $description - question description
430
     *
431
     * @author Olivier Brouckaert
432
     */
433
    public function updateDescription($description)
434
    {
435
        $this->description = $description;
436
    }
437
438
    /**
439
     * changes the question weighting.
440
     *
441
     * @param int $weighting - question weighting
442
     *
443
     * @author Olivier Brouckaert
444
     */
445
    public function updateWeighting($weighting)
446
    {
447
        $this->weighting = $weighting;
448
    }
449
450
    /**
451
     * @param array $category
452
     *
453
     * @author Hubert Borderiou 12-10-2011
454
     */
455
    public function updateCategory($category)
456
    {
457
        $this->category = $category;
458
    }
459
460
    /**
461
     * @param int $value
462
     *
463
     * @author Hubert Borderiou 12-10-2011
464
     */
465
    public function updateScoreAlwaysPositive($value)
466
    {
467
        $this->scoreAlwaysPositive = $value;
468
    }
469
470
    /**
471
     * @param int $value
472
     *
473
     * @author Hubert Borderiou 12-10-2011
474
     */
475
    public function updateUncheckedMayScore($value)
476
    {
477
        $this->uncheckedMayScore = $value;
478
    }
479
480
    /**
481
     * in this version, a question can only have 1 category
482
     * if category is 0, then question has no category then delete the category entry.
483
     *
484
     * @param int $categoryId
485
     * @param int $courseId
486
     *
487
     * @return bool
488
     *
489
     * @author Hubert Borderiou 12-10-2011
490
     */
491
    public function saveCategory($categoryId, $courseId = 0)
492
    {
493
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
494
495
        if (empty($courseId)) {
496
            return false;
497
        }
498
499
        if ($categoryId <= 0) {
500
            $this->deleteCategory($courseId);
501
        } else {
502
            // update or add category for a question
503
            $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
504
            $categoryId = (int) $categoryId;
505
            $question_id = (int) $this->id;
506
            $sql = "SELECT count(*) AS nb FROM $table
507
                    WHERE
508
                        question_id = $question_id AND
509
                        c_id = ".$courseId;
510
            $res = Database::query($sql);
511
            $row = Database::fetch_array($res);
512
            if ($row['nb'] > 0) {
513
                $sql = "UPDATE $table
514
                        SET category_id = $categoryId
515
                        WHERE
516
                            question_id = $question_id AND
517
                            c_id = ".$courseId;
518
                Database::query($sql);
519
            } else {
520
                $sql = "INSERT INTO $table (c_id, question_id, category_id)
521
                        VALUES (".$courseId.", $question_id, $categoryId)";
522
                Database::query($sql);
523
            }
524
525
            return true;
526
        }
527
    }
528
529
    /**
530
     * @author hubert borderiou 12-10-2011
531
     *
532
     * @param int $courseId
533
     *                      delete any category entry for question id
534
     *                      delete the category for question
535
     *
536
     * @return bool
537
     */
538
    public function deleteCategory($courseId = 0)
539
    {
540
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
541
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
542
        $questionId = (int) $this->id;
543
        if (empty($courseId) || empty($questionId)) {
544
            return false;
545
        }
546
        $sql = "DELETE FROM $table
547
                WHERE
548
                    question_id = $questionId AND
549
                    c_id = ".$courseId;
550
        Database::query($sql);
551
552
        return true;
553
    }
554
555
    /**
556
     * changes the question position.
557
     *
558
     * @param int $position - question position
559
     *
560
     * @author Olivier Brouckaert
561
     */
562
    public function updatePosition($position)
563
    {
564
        $this->position = $position;
565
    }
566
567
    /**
568
     * changes the question level.
569
     *
570
     * @param int $level - question level
571
     *
572
     * @author Nicolas Raynaud
573
     */
574
    public function updateLevel($level)
575
    {
576
        $this->level = $level;
577
    }
578
579
    /**
580
     * changes the answer type. If the user changes the type from "unique answer" to "multiple answers"
581
     * (or conversely) answers are not deleted, otherwise yes.
582
     *
583
     * @param int $type - answer type
584
     *
585
     * @author Olivier Brouckaert
586
     */
587
    public function updateType($type)
588
    {
589
        $table = Database::get_course_table(TABLE_QUIZ_ANSWER);
590
        $course_id = $this->course['real_id'];
591
592
        if (empty($course_id)) {
593
            $course_id = api_get_course_int_id();
594
        }
595
        // if we really change the type
596
        if ($type != $this->type) {
597
            // if we don't change from "unique answer" to "multiple answers" (or conversely)
598
            if (!in_array($this->type, [UNIQUE_ANSWER, MULTIPLE_ANSWER]) ||
599
                !in_array($type, [UNIQUE_ANSWER, MULTIPLE_ANSWER])
600
            ) {
601
                // removes old answers
602
                $sql = "DELETE FROM $table
603
                        WHERE c_id = $course_id AND question_id = ".intval($this->id);
604
                Database::query($sql);
605
            }
606
607
            $this->type = $type;
608
        }
609
    }
610
611
    /**
612
     * Get default hot spot folder in documents.
613
     *
614
     * @param array $courseInfo
615
     *
616
     * @return CDocument
617
     */
618
    public function getHotSpotFolderInCourse($courseInfo = [])
619
    {
620
        return null;
621
        $courseInfo = empty($courseInfo) ? $this->course : $courseInfo;
0 ignored issues
show
Unused Code introduced by
$courseInfo = empty($cou...s->course : $courseInfo is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
622
623
        if (empty($courseInfo) || empty($courseInfo['directory'])) {
624
            // Stop everything if course is not set.
625
            api_not_allowed();
626
        }
627
628
        $pictureAbsolutePath = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'].'/document/images/';
629
        $picturePath = basename($pictureAbsolutePath);
630
631
        $folder = create_unexisting_directory(
632
            $courseInfo,
633
            api_get_user_id(),
634
            0,
635
            0,
636
            0,
637
            dirname($pictureAbsolutePath),
638
            '/'.$picturePath,
639
            $picturePath,
640
            '',
641
            false,
642
            false
643
        );
644
645
        return $folder;
646
    }
647
648
    /**
649
     * return the name for image use in hotspot question
650
     * to be unique, name is quiz-[utc unix timestamp].jpg.
651
     *
652
     * @param string $prefix
653
     * @param string $extension
654
     *
655
     * @return string
656
     */
657
    public function generatePictureName($prefix = 'quiz-', $extension = 'jpg')
658
    {
659
        // image name is quiz-xxx.jpg in folder images/
660
        $utcTime = time();
661
662
        return $prefix.$utcTime.'.'.$extension;
663
    }
664
665
    /**
666
     *  Deletes a hot spot picture.
667
     *
668
     * @return bool - true
669
     */
670
    public function removePicture()
671
    {
672
        $picture = $this->getPicture();
673
674
        if ($picture) {
675
            $manager = Database::getManager();
676
            $manager->remove($picture);
677
            $manager->flush();
678
679
            return true;
680
        }
681
682
        return false;
683
    }
684
685
    /**
686
     * Exports a picture to another question.
687
     *
688
     * @author Olivier Brouckaert
689
     *
690
     * @param int   $questionId - ID of the target question
691
     * @param array $courseInfo destination course info
692
     *
693
     * @return bool - true if copied, otherwise false
694
     */
695
    public function exportPicture($questionId, $courseInfo)
696
    {
697
        // @todo Create a resource node duplication function.
698
        throw new Exception('exportPicture not available yet');
699
    }
700
701
    /**
702
     * Set title.
703
     *
704
     * @param string $title
705
     */
706
    public function setTitle($title)
707
    {
708
        $this->question = $title;
709
    }
710
711
    /**
712
     * Sets extra info.
713
     *
714
     * @param string $extra
715
     */
716
    public function setExtra($extra)
717
    {
718
        $this->extra = $extra;
719
    }
720
721
    /**
722
     * updates the question in the data base
723
     * if an exercise ID is provided, we add that exercise ID into the exercise list.
724
     *
725
     * @author Olivier Brouckaert
726
     *
727
     * @param Exercise $exercise
728
     */
729
    public function save($exercise)
730
    {
731
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
732
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
733
        $em = Database::getManager();
734
        $exerciseId = $exercise->id;
735
736
        $id = $this->id;
737
        $type = $this->type;
738
        $c_id = $this->course['real_id'];
739
        $categoryId = $this->category;
740
741
        $questionRepo = Container::getQuestionRepository();
742
        $exerciseRepo = Container::getExerciseRepository();
743
744
        // question already exists
745
        if (!empty($id)) {
746
            /** @var CQuizQuestion $question */
747
            $question = $questionRepo->find($id);
748
            $question
749
                ->setQuestion($this->question)
750
                ->setDescription($this->description)
751
                ->setPonderation($this->weighting)
752
                ->setPosition($this->position)
753
                ->setType($this->type)
754
                ->setExtra($this->extra)
755
                ->setLevel($this->level)
756
                ->setFeedback($this->feedback)
757
            ;
758
759
            $em->persist($question);
760
            $em->flush();
761
762
            Event::addEvent(
763
                LOG_QUESTION_UPDATED,
764
                LOG_QUESTION_ID,
765
                $this->iid
766
            );
767
            $this->saveCategory($categoryId);
768
            if (api_get_setting('search_enabled') === 'true') {
769
                $this->search_engine_edit($exerciseId);
770
            }
771
        } else {
772
            // Creates a new question
773
            $sql = "SELECT max(position)
774
                    FROM $TBL_QUESTIONS as question,
775
                    $TBL_EXERCISE_QUESTION as test_question
776
                    WHERE
777
                        question.id = test_question.question_id AND
778
                        test_question.exercice_id = ".$exerciseId." AND
779
                        question.c_id = $c_id AND
780
                        test_question.c_id = $c_id ";
781
            $result = Database::query($sql);
782
            $current_position = Database::result($result, 0, 0);
783
            $this->updatePosition($current_position + 1);
784
            $position = $this->position;
785
786
            $question = new CQuizQuestion();
787
            $question
788
                ->setCId($c_id)
789
                ->setQuestion($this->question)
790
                ->setDescription($this->description)
791
                ->setPonderation($this->weighting)
792
                ->setPosition($position)
793
                ->setType($this->type)
794
                ->setExtra($this->extra)
795
                ->setLevel($this->level)
796
                ->setFeedback($this->feedback)
797
            ;
798
799
            $em->persist($question);
800
            $em->flush();
801
802
            $this->id = $question->getIid();
803
804
            if ($this->id) {
805
                $sql = "UPDATE $TBL_QUESTIONS SET id = iid WHERE iid = {$this->id}";
806
                Database::query($sql);
807
808
                Event::addEvent(
809
                    LOG_QUESTION_CREATED,
810
                    LOG_QUESTION_ID,
811
                    $this->id
812
                );
813
                $exerciseEntity = $exerciseRepo->find($exerciseId);
814
                $node = $questionRepo->addResourceNode($question, api_get_user_entity(api_get_user_id()), $exerciseEntity);
815
                $questionRepo->addResourceNodeToCourse(
816
                    $node,
817
                    ResourceLink::VISIBILITY_PUBLISHED,
818
                    api_get_course_entity(),
819
                    api_get_session_entity(),
820
                    api_get_group_entity()
821
                );
822
823
                $request = Container::getRequest();
824
                if ($request->files->has('imageUpload')) {
825
                    $file = $request->files->get('imageUpload');
826
                    $questionRepo->addFile($question, $file);
827
828
                    $em->flush();
829
                }
830
831
                // If hotspot, create first answer
832
                if ($type == HOT_SPOT || $type == HOT_SPOT_ORDER) {
833
                    $quizAnswer = new CQuizAnswer();
834
                    $quizAnswer
835
                        ->setCId($c_id)
836
                        ->setQuestionId($this->id)
837
                        ->setAnswer('')
838
                        ->setPonderation(10)
839
                        ->setPosition(1)
840
                        ->setHotspotCoordinates('0;0|0|0')
841
                        ->setHotspotType('square');
842
843
                    $em->persist($quizAnswer);
844
                    $em->flush();
845
846
                    $id = $quizAnswer->getIid();
847
848
                    if ($id) {
849
                        $quizAnswer
850
                            ->setId($id)
851
                            ->setIdAuto($id);
852
853
                        $em->persist($quizAnswer);
854
                        $em->flush();
855
                    }
856
                }
857
858
                if ($type == HOT_SPOT_DELINEATION) {
859
                    $quizAnswer = new CQuizAnswer();
860
                    $quizAnswer
861
                        ->setCId($c_id)
862
                        ->setQuestionId($this->id)
863
                        ->setAnswer('')
864
                        ->setPonderation(10)
865
                        ->setPosition(1)
866
                        ->setHotspotCoordinates('0;0|0|0')
867
                        ->setHotspotType('delineation');
868
869
                    $em->persist($quizAnswer);
870
                    $em->flush();
871
872
                    $id = $quizAnswer->getIid();
873
874
                    if ($id) {
875
                        $quizAnswer
876
                            ->setId($id)
877
                            ->setIdAuto($id);
878
879
                        $em->persist($quizAnswer);
880
                        $em->flush();
881
                    }
882
                }
883
884
                if (api_get_setting('search_enabled') === 'true') {
885
                    $this->search_engine_edit($exerciseId, true);
886
                }
887
            }
888
        }
889
890
        // if the question is created in an exercise
891
        if (!empty($exerciseId)) {
892
            // adds the exercise into the exercise list of this question
893
            $this->addToList($exerciseId, true);
894
        }
895
    }
896
897
    /**
898
     * @param int  $exerciseId
899
     * @param bool $addQs
900
     * @param bool $rmQs
901
     */
902
    public function search_engine_edit(
903
        $exerciseId,
904
        $addQs = false,
905
        $rmQs = false
906
    ) {
907
        // update search engine and its values table if enabled
908
        if (!empty($exerciseId) && api_get_setting('search_enabled') == 'true' &&
909
            extension_loaded('xapian')
910
        ) {
911
            $course_id = api_get_course_id();
912
            // get search_did
913
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
914
            if ($addQs || $rmQs) {
915
                //there's only one row per question on normal db and one document per question on search engine db
916
                $sql = 'SELECT * FROM %s
917
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=%s LIMIT 1';
918
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
919
            } else {
920
                $sql = 'SELECT * FROM %s
921
                    WHERE course_code=\'%s\' AND tool_id=\'%s\'
922
                    AND ref_id_high_level=%s AND ref_id_second_level=%s LIMIT 1';
923
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
924
            }
925
            $res = Database::query($sql);
926
927
            if (Database::num_rows($res) > 0 || $addQs) {
928
                $di = new ChamiloIndexer();
929
                if ($addQs) {
930
                    $question_exercises = [(int) $exerciseId];
931
                } else {
932
                    $question_exercises = [];
933
                }
934
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
935
                $di->connectDb(null, null, $lang);
936
937
                // retrieve others exercise ids
938
                $se_ref = Database::fetch_array($res);
939
                $se_doc = $di->get_document((int) $se_ref['search_did']);
940
                if ($se_doc !== false) {
941
                    if (($se_doc_data = $di->get_document_data($se_doc)) !== false) {
942
                        $se_doc_data = UnserializeApi::unserialize(
943
                            'not_allowed_classes',
944
                            $se_doc_data
945
                        );
946
                        if (isset($se_doc_data[SE_DATA]['type']) &&
947
                            $se_doc_data[SE_DATA]['type'] == SE_DOCTYPE_EXERCISE_QUESTION
948
                        ) {
949
                            if (isset($se_doc_data[SE_DATA]['exercise_ids']) &&
950
                                is_array($se_doc_data[SE_DATA]['exercise_ids'])
951
                            ) {
952
                                foreach ($se_doc_data[SE_DATA]['exercise_ids'] as $old_value) {
953
                                    if (!in_array($old_value, $question_exercises)) {
954
                                        $question_exercises[] = $old_value;
955
                                    }
956
                                }
957
                            }
958
                        }
959
                    }
960
                }
961
                if ($rmQs) {
962
                    while (($key = array_search($exerciseId, $question_exercises)) !== false) {
963
                        unset($question_exercises[$key]);
964
                    }
965
                }
966
967
                // build the chunk to index
968
                $ic_slide = new IndexableChunk();
969
                $ic_slide->addValue("title", $this->question);
970
                $ic_slide->addCourseId($course_id);
971
                $ic_slide->addToolId(TOOL_QUIZ);
972
                $xapian_data = [
973
                    SE_COURSE_ID => $course_id,
974
                    SE_TOOL_ID => TOOL_QUIZ,
975
                    SE_DATA => [
976
                        'type' => SE_DOCTYPE_EXERCISE_QUESTION,
977
                        'exercise_ids' => $question_exercises,
978
                        'question_id' => (int) $this->id,
979
                    ],
980
                    SE_USER => (int) api_get_user_id(),
981
                ];
982
                $ic_slide->xapian_data = serialize($xapian_data);
983
                $ic_slide->addValue("content", $this->description);
984
985
                //TODO: index answers, see also form validation on question_admin.inc.php
986
987
                $di->remove_document($se_ref['search_did']);
988
                $di->addChunk($ic_slide);
989
990
                //index and return search engine document id
991
                if (!empty($question_exercises)) { // if empty there is nothing to index
992
                    $did = $di->index();
993
                    unset($di);
994
                }
995
                if ($did || $rmQs) {
996
                    // save it to db
997
                    if ($addQs || $rmQs) {
998
                        $sql = "DELETE FROM %s
999
                            WHERE course_code = '%s' AND tool_id = '%s' AND ref_id_second_level = '%s'";
1000
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
1001
                    } else {
1002
                        $sql = "DELETE FROM %S
1003
                            WHERE
1004
                                course_code = '%s'
1005
                                AND tool_id = '%s'
1006
                                AND tool_id = '%s'
1007
                                AND ref_id_high_level = '%s'
1008
                                AND ref_id_second_level = '%s'";
1009
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
1010
                    }
1011
                    Database::query($sql);
1012
                    if ($rmQs) {
1013
                        if (!empty($question_exercises)) {
1014
                            $sql = "INSERT INTO %s (
1015
                                    id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
1016
                                )
1017
                                VALUES (
1018
                                    NULL, '%s', '%s', %s, %s, %s
1019
                                )";
1020
                            $sql = sprintf(
1021
                                $sql,
1022
                                $tbl_se_ref,
1023
                                $course_id,
1024
                                TOOL_QUIZ,
1025
                                array_shift($question_exercises),
1026
                                $this->id,
1027
                                $did
1028
                            );
1029
                            Database::query($sql);
1030
                        }
1031
                    } else {
1032
                        $sql = "INSERT INTO %s (
1033
                                id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
1034
                            )
1035
                            VALUES (
1036
                                NULL , '%s', '%s', %s, %s, %s
1037
                            )";
1038
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id, $did);
1039
                        Database::query($sql);
1040
                    }
1041
                }
1042
            }
1043
        }
1044
    }
1045
1046
    /**
1047
     * adds an exercise into the exercise list.
1048
     *
1049
     * @author Olivier Brouckaert
1050
     *
1051
     * @param int  $exerciseId - exercise ID
1052
     * @param bool $fromSave   - from $this->save() or not
1053
     */
1054
    public function addToList($exerciseId, $fromSave = false)
1055
    {
1056
        $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1057
        $id = (int) $this->id;
1058
        $exerciseId = (int) $exerciseId;
1059
1060
        // checks if the exercise ID is not in the list
1061
        if (!empty($exerciseId) && !in_array($exerciseId, $this->exerciseList)) {
1062
            $this->exerciseList[] = $exerciseId;
1063
            $courseId = isset($this->course['real_id']) ? $this->course['real_id'] : 0;
1064
            $newExercise = new Exercise($courseId);
1065
            $newExercise->read($exerciseId, false);
1066
            $count = $newExercise->getQuestionCount();
1067
            $count++;
1068
            $sql = "INSERT INTO $exerciseRelQuestionTable (c_id, question_id, exercice_id, question_order)
1069
                    VALUES ({$this->course['real_id']}, ".$id.", ".$exerciseId.", '$count')";
1070
            Database::query($sql);
1071
1072
            // we do not want to reindex if we had just saved adnd indexed the question
1073
            if (!$fromSave) {
1074
                $this->search_engine_edit($exerciseId, true);
1075
            }
1076
        }
1077
    }
1078
1079
    /**
1080
     * removes an exercise from the exercise list.
1081
     *
1082
     * @author Olivier Brouckaert
1083
     *
1084
     * @param int $exerciseId - exercise ID
1085
     * @param int $courseId
1086
     *
1087
     * @return bool - true if removed, otherwise false
1088
     */
1089
    public function removeFromList($exerciseId, $courseId = 0)
1090
    {
1091
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1092
        $id = (int) $this->id;
1093
        $exerciseId = (int) $exerciseId;
1094
1095
        // searches the position of the exercise ID in the list
1096
        $pos = array_search($exerciseId, $this->exerciseList);
1097
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
1098
1099
        // exercise not found
1100
        if ($pos === false) {
1101
            return false;
1102
        } else {
1103
            // deletes the position in the array containing the wanted exercise ID
1104
            unset($this->exerciseList[$pos]);
1105
            //update order of other elements
1106
            $sql = "SELECT question_order
1107
                    FROM $table
1108
                    WHERE
1109
                        c_id = $courseId AND 
1110
                        question_id = $id AND 
1111
                        exercice_id = $exerciseId";
1112
            $res = Database::query($sql);
1113
            if (Database::num_rows($res) > 0) {
1114
                $row = Database::fetch_array($res);
1115
                if (!empty($row['question_order'])) {
1116
                    $sql = "UPDATE $table
1117
                            SET question_order = question_order-1
1118
                            WHERE
1119
                                c_id = $courseId AND 
1120
                                exercice_id = $exerciseId AND 
1121
                                question_order > ".$row['question_order'];
1122
                    Database::query($sql);
1123
                }
1124
            }
1125
1126
            $sql = "DELETE FROM $table
1127
                    WHERE
1128
                        c_id = $courseId AND 
1129
                        question_id = $id AND 
1130
                        exercice_id = $exerciseId";
1131
            Database::query($sql);
1132
1133
            return true;
1134
        }
1135
    }
1136
1137
    /**
1138
     * Deletes a question from the database
1139
     * the parameter tells if the question is removed from all exercises (value = 0),
1140
     * or just from one exercise (value = exercise ID).
1141
     *
1142
     * @author Olivier Brouckaert
1143
     *
1144
     * @param int $deleteFromEx - exercise ID if the question is only removed from one exercise
1145
     *
1146
     * @return bool
1147
     */
1148
    public function delete($deleteFromEx = 0)
1149
    {
1150
        if (empty($this->course)) {
1151
            return false;
1152
        }
1153
1154
        $courseId = $this->course['real_id'];
1155
1156
        if (empty($courseId)) {
1157
            return false;
1158
        }
1159
1160
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1161
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1162
        $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
1163
        $TBL_QUIZ_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
1164
1165
        $id = (int) $this->id;
1166
1167
        // if the question must be removed from all exercises
1168
        if (!$deleteFromEx) {
1169
            //update the question_order of each question to avoid inconsistencies
1170
            $sql = "SELECT exercice_id, question_order 
1171
                    FROM $TBL_EXERCISE_QUESTION
1172
                    WHERE c_id = $courseId AND question_id = ".$id;
1173
1174
            $res = Database::query($sql);
1175
            if (Database::num_rows($res) > 0) {
1176
                while ($row = Database::fetch_array($res)) {
1177
                    if (!empty($row['question_order'])) {
1178
                        $sql = "UPDATE $TBL_EXERCISE_QUESTION
1179
                                SET question_order = question_order-1
1180
                                WHERE
1181
                                    c_id = $courseId AND 
1182
                                    exercice_id = ".intval($row['exercice_id'])." AND 
1183
                                    question_order > ".$row['question_order'];
1184
                        Database::query($sql);
1185
                    }
1186
                }
1187
            }
1188
1189
            $sql = "DELETE FROM $TBL_EXERCISE_QUESTION
1190
                    WHERE c_id = $courseId AND question_id = ".$id;
1191
            Database::query($sql);
1192
1193
            $sql = "DELETE FROM $TBL_QUESTIONS
1194
                    WHERE c_id = $courseId AND id = ".$id;
1195
            Database::query($sql);
1196
1197
            $sql = "DELETE FROM $TBL_REPONSES
1198
                    WHERE c_id = $courseId AND question_id = ".$id;
1199
            Database::query($sql);
1200
1201
            // remove the category of this question in the question_rel_category table
1202
            $sql = "DELETE FROM $TBL_QUIZ_QUESTION_REL_CATEGORY
1203
                    WHERE 
1204
                        c_id = $courseId AND 
1205
                        question_id = ".$id;
1206
            Database::query($sql);
1207
1208
            api_item_property_update(
1209
                $this->course,
1210
                TOOL_QUIZ,
1211
                $id,
1212
                'QuizQuestionDeleted',
1213
                api_get_user_id()
1214
            );
1215
            $this->removePicture();
1216
        } else {
1217
            // just removes the exercise from the list
1218
            $this->removeFromList($deleteFromEx, $courseId);
1219
            if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1220
                // disassociate question with this exercise
1221
                $this->search_engine_edit($deleteFromEx, false, true);
1222
            }
1223
1224
            api_item_property_update(
1225
                $this->course,
1226
                TOOL_QUIZ,
1227
                $id,
1228
                'QuizQuestionDeleted',
1229
                api_get_user_id()
1230
            );
1231
        }
1232
1233
        return true;
1234
    }
1235
1236
    /**
1237
     * Duplicates the question.
1238
     *
1239
     * @author Olivier Brouckaert
1240
     *
1241
     * @param array $courseInfo Course info of the destination course
1242
     *
1243
     * @return false|string ID of the new question
1244
     */
1245
    public function duplicate($courseInfo = [])
1246
    {
1247
        $courseInfo = empty($courseInfo) ? $this->course : $courseInfo;
1248
1249
        if (empty($courseInfo)) {
1250
            return false;
1251
        }
1252
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
1253
        $TBL_QUESTION_OPTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1254
1255
        $question = $this->question;
1256
        $description = $this->description;
1257
        $weighting = $this->weighting;
1258
        $position = $this->position;
1259
        $type = $this->type;
1260
        $level = (int) $this->level;
1261
        $extra = $this->extra;
1262
1263
        // Using the same method used in the course copy to transform URLs
1264
        if ($this->course['id'] != $courseInfo['id']) {
1265
            $description = DocumentManager::replaceUrlWithNewCourseCode(
1266
                $description,
1267
                $this->course['code'],
1268
                $courseInfo['id']
1269
            );
1270
            $question = DocumentManager::replaceUrlWithNewCourseCode(
1271
                $question,
1272
                $this->course['code'],
1273
                $courseInfo['id']
1274
            );
1275
        }
1276
1277
        $course_id = $courseInfo['real_id'];
1278
1279
        // Read the source options
1280
        $options = self::readQuestionOption($this->id, $this->course['real_id']);
1281
1282
        // Inserting in the new course db / or the same course db
1283
        $params = [
1284
            'c_id' => $course_id,
1285
            'question' => $question,
1286
            'description' => $description,
1287
            'ponderation' => $weighting,
1288
            'position' => $position,
1289
            'type' => $type,
1290
            'level' => $level,
1291
            'extra' => $extra,
1292
        ];
1293
        $newQuestionId = Database::insert($questionTable, $params);
1294
1295
        if ($newQuestionId) {
1296
            $sql = "UPDATE $questionTable 
1297
                    SET id = iid
1298
                    WHERE iid = $newQuestionId";
1299
            Database::query($sql);
1300
1301
            if (!empty($options)) {
1302
                // Saving the quiz_options
1303
                foreach ($options as $item) {
1304
                    $item['question_id'] = $newQuestionId;
1305
                    $item['c_id'] = $course_id;
1306
                    unset($item['id']);
1307
                    unset($item['iid']);
1308
                    $id = Database::insert($TBL_QUESTION_OPTIONS, $item);
1309
                    if ($id) {
1310
                        $sql = "UPDATE $TBL_QUESTION_OPTIONS 
1311
                                SET id = iid
1312
                                WHERE iid = $id";
1313
                        Database::query($sql);
1314
                    }
1315
                }
1316
            }
1317
1318
            // Duplicates the picture of the hotspot
1319
            $this->exportPicture($newQuestionId, $courseInfo);
1320
        }
1321
1322
        return $newQuestionId;
1323
    }
1324
1325
    /**
1326
     * @return string
1327
     */
1328
    public function get_question_type_name()
1329
    {
1330
        $key = self::$questionTypes[$this->type];
1331
1332
        return get_lang($key[1]);
1333
    }
1334
1335
    /**
1336
     * @param string $type
1337
     */
1338
    public static function get_question_type($type)
1339
    {
1340
        if ($type == ORAL_EXPRESSION && api_get_setting('enable_record_audio') !== 'true') {
1341
            return null;
1342
        }
1343
1344
        return self::$questionTypes[$type];
1345
    }
1346
1347
    /**
1348
     * @return array
1349
     */
1350
    public static function getQuestionTypeList()
1351
    {
1352
        if (api_get_setting('enable_record_audio') !== 'true') {
1353
            self::$questionTypes[ORAL_EXPRESSION] = null;
1354
            unset(self::$questionTypes[ORAL_EXPRESSION]);
1355
        }
1356
        if (api_get_setting('enable_quiz_scenario') !== 'true') {
1357
            self::$questionTypes[HOT_SPOT_DELINEATION] = null;
1358
            unset(self::$questionTypes[HOT_SPOT_DELINEATION]);
1359
        }
1360
1361
        return self::$questionTypes;
1362
    }
1363
1364
    /**
1365
     * Returns an instance of the class corresponding to the type.
1366
     *
1367
     * @param int $type the type of the question
1368
     *
1369
     * @return $this instance of a Question subclass (or of Questionc class by default)
1370
     */
1371
    public static function getInstance($type)
1372
    {
1373
        if (!is_null($type)) {
1374
            list($fileName, $className) = self::get_question_type($type);
1375
            if (!empty($fileName)) {
1376
                if (class_exists($className)) {
1377
                    return new $className();
1378
                } else {
1379
                    echo 'Can\'t instanciate class '.$className.' of type '.$type;
1380
                }
1381
            }
1382
        }
1383
1384
        return null;
1385
    }
1386
1387
    /**
1388
     * Creates the form to create / edit a question
1389
     * A subclass can redefine this function to add fields...
1390
     *
1391
     * @param FormValidator $form
1392
     * @param Exercise      $exercise
1393
     */
1394
    public function createForm(&$form, $exercise)
1395
    {
1396
        echo '<style>
1397
                .media { display:none;}
1398
            </style>';
1399
1400
        // question name
1401
        if (api_get_configuration_value('save_titles_as_html')) {
1402
            $editorConfig = ['ToolbarSet' => 'TitleAsHtml'];
1403
            $form->addHtmlEditor(
1404
                'questionName',
1405
                get_lang('Question'),
1406
                false,
1407
                false,
1408
                $editorConfig,
1409
                true
1410
            );
1411
        } else {
1412
            $form->addElement('text', 'questionName', get_lang('Question'));
1413
        }
1414
1415
        $form->addRule('questionName', get_lang('Please type the question'), 'required');
1416
1417
        // default content
1418
        $isContent = isset($_REQUEST['isContent']) ? (int) $_REQUEST['isContent'] : null;
1419
1420
        // Question type
1421
        $answerType = isset($_REQUEST['answerType']) ? (int) $_REQUEST['answerType'] : null;
1422
        $form->addElement('hidden', 'answerType', $answerType);
1423
1424
        // html editor
1425
        $editorConfig = [
1426
            'ToolbarSet' => 'TestQuestionDescription',
1427
            'Height' => '150',
1428
        ];
1429
1430
        if (!api_is_allowed_to_edit(null, true)) {
1431
            $editorConfig['UserStatus'] = 'student';
1432
        }
1433
1434
        $form->addButtonAdvancedSettings('advanced_params');
1435
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1436
        $form->addHtmlEditor(
1437
            'questionDescription',
1438
            get_lang('Enrich question'),
1439
            false,
1440
            false,
1441
            $editorConfig
1442
        );
1443
1444
        if ($this->type != MEDIA_QUESTION) {
1445
            // Advanced parameters
1446
            $select_level = self::get_default_levels();
1447
            $form->addElement(
1448
                'select',
1449
                'questionLevel',
1450
                get_lang('Difficulty'),
1451
                $select_level
1452
            );
1453
1454
            // Categories
1455
            $tabCat = TestCategory::getCategoriesForSelect();
1456
1457
            $form->addElement(
1458
                'select',
1459
                'questionCategory',
1460
                get_lang('Category'),
1461
                $tabCat
1462
            );
1463
1464
            global $text;
1465
1466
            switch ($this->type) {
1467
                case UNIQUE_ANSWER:
1468
                    $buttonGroup = [];
1469
                    $buttonGroup[] = $form->addButtonSave(
1470
                        $text,
1471
                        'submitQuestion',
1472
                        true
1473
                    );
1474
                    $buttonGroup[] = $form->addButton(
1475
                        'convertAnswer',
1476
                        get_lang('Convert to multiple answer'),
1477
                        'dot-circle-o',
1478
                        'default',
1479
                        null,
1480
                        null,
1481
                        null,
1482
                        true
1483
                    );
1484
                    $form->addGroup($buttonGroup);
1485
                    break;
1486
                case MULTIPLE_ANSWER:
1487
                    $buttonGroup = [];
1488
                    $buttonGroup[] = $form->addButtonSave(
1489
                        $text,
1490
                        'submitQuestion',
1491
                        true
1492
                    );
1493
                    $buttonGroup[] = $form->addButton(
1494
                        'convertAnswer',
1495
                        get_lang('Convert to unique answer'),
1496
                        'check-square-o',
1497
                        'default',
1498
                        null,
1499
                        null,
1500
                        null,
1501
                        true
1502
                    );
1503
                    $form->addGroup($buttonGroup);
1504
                    break;
1505
            }
1506
            //Medias
1507
            //$course_medias = self::prepare_course_media_select(api_get_course_int_id());
1508
            //$form->addElement('select', 'parent_id', get_lang('Attach to media'), $course_medias);
1509
        }
1510
1511
        $form->addElement('html', '</div>');
1512
1513
        if (!isset($_GET['fromExercise'])) {
1514
            switch ($answerType) {
1515
                case 1:
1516
                    $this->question = get_lang('Select the good reasoning');
1517
                    break;
1518
                case 2:
1519
                    $this->question = get_lang('The marasmus is a consequence of');
1520
                    break;
1521
                case 3:
1522
                    $this->question = get_lang('Calculate the Body Mass Index');
1523
                    break;
1524
                case 4:
1525
                    $this->question = get_lang('Order the operations');
1526
                    break;
1527
                case 5:
1528
                    $this->question = get_lang('List what you consider the 10 top qualities of a good project manager?');
1529
                    break;
1530
                case 9:
1531
                    $this->question = get_lang('The marasmus is a consequence of');
1532
                    break;
1533
            }
1534
        }
1535
1536
        if (!is_null($exercise)) {
1537
            if ($exercise->questionFeedbackEnabled && $this->showFeedback($exercise)) {
1538
                $form->addTextarea('feedback', get_lang('Feedback if not correct'));
1539
            }
1540
        }
1541
1542
        // default values
1543
        $defaults = [];
1544
        $defaults['questionName'] = $this->question;
1545
        $defaults['questionDescription'] = $this->description;
1546
        $defaults['questionLevel'] = $this->level;
1547
        $defaults['questionCategory'] = $this->category;
1548
        $defaults['feedback'] = $this->feedback;
1549
1550
        // Came from he question pool
1551
        if (isset($_GET['fromExercise'])) {
1552
            $form->setDefaults($defaults);
1553
        }
1554
1555
        if (!isset($_GET['newQuestion']) || $isContent) {
1556
            $form->setDefaults($defaults);
1557
        }
1558
1559
        /*if (!empty($_REQUEST['myid'])) {
1560
            $form->setDefaults($defaults);
1561
        } else {
1562
            if ($isContent == 1) {
1563
                $form->setDefaults($defaults);
1564
            }
1565
        }*/
1566
    }
1567
1568
    /**
1569
     * Function which process the creation of questions.
1570
     */
1571
    public function processCreation(FormValidator $form, Exercise $exercise)
1572
    {
1573
        $this->updateTitle($form->getSubmitValue('questionName'));
1574
        $this->updateDescription($form->getSubmitValue('questionDescription'));
1575
        $this->updateLevel($form->getSubmitValue('questionLevel'));
1576
        $this->updateCategory($form->getSubmitValue('questionCategory'));
1577
        $this->setFeedback($form->getSubmitValue('feedback'));
1578
1579
        //Save normal question if NOT media
1580
        if ($this->type != MEDIA_QUESTION) {
1581
            $this->save($exercise);
1582
            // modify the exercise
1583
            $exercise->addToList($this->id);
1584
            $exercise->update_question_positions();
1585
        }
1586
    }
1587
1588
    /**
1589
     * abstract function which creates the form to create / edit the answers of the question.
1590
     *
1591
     * @param FormValidator $form
1592
     */
1593
    abstract public function createAnswersForm(FormValidator $form);
1594
1595
    /**
1596
     * abstract function which process the creation of answers.
1597
     *
1598
     * @param FormValidator $form
1599
     * @param Exercise      $exercise
1600
     */
1601
    abstract public function processAnswersCreation($form, $exercise);
1602
1603
    /**
1604
     * Displays the menu of question types.
1605
     *
1606
     * @param Exercise $objExercise
1607
     */
1608
    public static function displayTypeMenu($objExercise)
1609
    {
1610
        $feedbackType = $objExercise->getFeedbackType();
1611
        $exerciseId = $objExercise->id;
1612
1613
        // 1. by default we show all the question types
1614
        $questionTypeList = self::getQuestionTypeList();
1615
1616
        if (!isset($feedbackType)) {
1617
            $feedbackType = 0;
1618
        }
1619
1620
        switch ($feedbackType) {
1621
            case EXERCISE_FEEDBACK_TYPE_DIRECT:
1622
                $questionTypeList = [
1623
                    UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
1624
                    HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION],
1625
                ];
1626
                break;
1627
            case EXERCISE_FEEDBACK_TYPE_POPUP:
1628
                $questionTypeList = [
1629
                    UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
1630
                    MULTIPLE_ANSWER => self::$questionTypes[MULTIPLE_ANSWER],
1631
                    DRAGGABLE => self::$questionTypes[DRAGGABLE],
1632
                    HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION],
1633
                    CALCULATED_ANSWER => self::$questionTypes[CALCULATED_ANSWER],
1634
                ];
1635
                break;
1636
            default:
1637
                unset($questionTypeList[HOT_SPOT_DELINEATION]);
1638
                break;
1639
        }
1640
1641
        echo '<div class="card">';
1642
        echo '<div class="card-body">';
1643
        echo '<ul class="question_menu">';
1644
        foreach ($questionTypeList as $i => $type) {
1645
            /** @var Question $type */
1646
            $type = new $type[1]();
1647
            $img = $type->getTypePicture();
1648
            $explanation = get_lang($type->getExplanation());
1649
            echo '<li>';
1650
            echo '<div class="icon-image">';
1651
            $icon = '<a href="admin.php?'.api_get_cidreq().'&newQuestion=yes&answerType='.$i.'">'.
1652
                Display::return_icon($img, $explanation, null, ICON_SIZE_BIG).'</a>';
1653
1654
            if ($objExercise->force_edit_exercise_in_lp === false) {
1655
                if ($objExercise->exercise_was_added_in_lp == true) {
1656
                    $img = pathinfo($img);
1657
                    $img = $img['filename'].'_na.'.$img['extension'];
1658
                    $icon = Display::return_icon($img, $explanation, null, ICON_SIZE_BIG);
1659
                }
1660
            }
1661
            echo $icon;
1662
            echo '</div>';
1663
            echo '</li>';
1664
        }
1665
1666
        echo '<li>';
1667
        echo '<div class="icon_image_content">';
1668
        if ($objExercise->exercise_was_added_in_lp == true) {
1669
            echo Display::return_icon(
1670
                'database_na.png',
1671
                get_lang('Recycle existing questions'),
1672
                null,
1673
                ICON_SIZE_BIG
1674
            );
1675
        } else {
1676
            if (in_array($feedbackType, [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
1677
                echo $url = "<a href=\"question_pool.php?".api_get_cidreq()."&type=1&fromExercise=$exerciseId\">";
1678
            } else {
1679
                echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&fromExercise='.$exerciseId.'">';
1680
            }
1681
            echo Display::return_icon(
1682
                'database.png',
1683
                get_lang('Recycle existing questions'),
1684
                null,
1685
                ICON_SIZE_BIG
1686
            );
1687
        }
1688
        echo '</a>';
1689
        echo '</div></li>';
1690
        echo '</ul>';
1691
        echo '</div>';
1692
        echo '</div>';
1693
    }
1694
1695
    /**
1696
     * @param int    $question_id
1697
     * @param string $name
1698
     * @param int    $course_id
1699
     * @param int    $position
1700
     *
1701
     * @return false|string
1702
     */
1703
    public static function saveQuestionOption($question_id, $name, $course_id, $position = 0)
1704
    {
1705
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1706
        $params['question_id'] = (int) $question_id;
1707
        $params['name'] = $name;
1708
        $params['position'] = $position;
1709
        $params['c_id'] = $course_id;
1710
        $result = self::readQuestionOption($question_id, $course_id);
1711
        $last_id = Database::insert($table, $params);
1712
        if ($last_id) {
1713
            $sql = "UPDATE $table SET id = iid WHERE iid = $last_id";
1714
            Database::query($sql);
1715
        }
1716
1717
        return $last_id;
1718
    }
1719
1720
    /**
1721
     * @param int $question_id
1722
     * @param int $course_id
1723
     */
1724
    public static function deleteAllQuestionOptions($question_id, $course_id)
1725
    {
1726
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1727
        Database::delete(
1728
            $table,
1729
            [
1730
                'c_id = ? AND question_id = ?' => [
1731
                    $course_id,
1732
                    $question_id,
1733
                ],
1734
            ]
1735
        );
1736
    }
1737
1738
    /**
1739
     * @param int   $id
1740
     * @param array $params
1741
     * @param int   $course_id
1742
     *
1743
     * @return bool|int
1744
     */
1745
    public static function updateQuestionOption($id, $params, $course_id)
1746
    {
1747
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1748
        $result = Database::update(
1749
            $table,
1750
            $params,
1751
            ['c_id = ? AND id = ?' => [$course_id, $id]]
1752
        );
1753
1754
        return $result;
1755
    }
1756
1757
    /**
1758
     * @param int $question_id
1759
     * @param int $course_id
1760
     *
1761
     * @return array
1762
     */
1763
    public static function readQuestionOption($question_id, $course_id)
1764
    {
1765
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1766
        $result = Database::select(
1767
            '*',
1768
            $table,
1769
            [
1770
                'where' => [
1771
                    'c_id = ? AND question_id = ?' => [
1772
                        $course_id,
1773
                        $question_id,
1774
                    ],
1775
                ],
1776
                'order' => 'id ASC',
1777
            ]
1778
        );
1779
1780
        return $result;
1781
    }
1782
1783
    /**
1784
     * Shows question title an description.
1785
     *
1786
     * @param int   $counter
1787
     * @param array $score
1788
     *
1789
     * @return string HTML string with the header of the question (before the answers table)
1790
     */
1791
    public function return_header(Exercise $exercise, $counter = null, $score = [])
1792
    {
1793
        $counterLabel = '';
1794
        if (!empty($counter)) {
1795
            $counterLabel = (int) $counter;
1796
        }
1797
1798
        $scoreLabel = get_lang('Wrong');
1799
1800
        if (in_array($exercise->results_disabled, [
1801
            RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
1802
            RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
1803
        ])
1804
        ) {
1805
            $scoreLabel = get_lang('Wrong answer. The correct one was:');
1806
        }
1807
1808
        $class = 'error';
1809
        if (isset($score['pass']) && $score['pass'] == true) {
1810
            $scoreLabel = get_lang('Correct');
1811
1812
            if (in_array($exercise->results_disabled, [
1813
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
1814
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
1815
            ])
1816
            ) {
1817
                $scoreLabel = get_lang('Correct answer');
1818
            }
1819
            $class = 'success';
1820
        }
1821
1822
        switch ($this->type) {
1823
            case FREE_ANSWER:
1824
            case ORAL_EXPRESSION:
1825
            case ANNOTATION:
1826
                $score['revised'] = isset($score['revised']) ? $score['revised'] : false;
1827
                if ($score['revised'] == true) {
1828
                    $scoreLabel = get_lang('Revised');
1829
                    $class = '';
1830
                } else {
1831
                    $scoreLabel = get_lang('Not reviewed');
1832
                    $class = 'warning';
1833
                    if (isset($score['weight'])) {
1834
                        $weight = float_format($score['weight'], 1);
1835
                        $score['result'] = ' ? / '.$weight;
1836
                    }
1837
                    $model = ExerciseLib::getCourseScoreModel();
1838
                    if (!empty($model)) {
1839
                        $score['result'] = ' ? ';
1840
                    }
1841
1842
                    $hide = api_get_configuration_value('hide_free_question_score');
1843
                    if ($hide === true) {
1844
                        $score['result'] = '-';
1845
                    }
1846
                }
1847
                break;
1848
            case UNIQUE_ANSWER:
1849
                if (in_array($exercise->results_disabled, [
1850
                    RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
1851
                    RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
1852
                ])
1853
                ) {
1854
                    if (isset($score['user_answered'])) {
1855
                        if ($score['user_answered'] === false) {
1856
                            $scoreLabel = get_lang('Unanswered');
1857
                            $class = 'info';
1858
                        }
1859
                    }
1860
                }
1861
                break;
1862
        }
1863
1864
        // display question category, if any
1865
        $header = '';
1866
        if ($exercise->display_category_name) {
1867
            $header = TestCategory::returnCategoryAndTitle($this->id);
1868
        }
1869
        $show_media = '';
1870
        if ($show_media) {
1871
            $header .= $this->show_media_content();
1872
        }
1873
1874
        $scoreCurrent = [
1875
            'used' => isset($score['score']) ? $score['score'] : '',
1876
            'missing' => isset($score['weight']) ? $score['weight'] : '',
1877
        ];
1878
        $header .= Display::page_subheader2($counterLabel.'. '.$this->question);
1879
1880
        // dont display score for certainty degree questions
1881
        if ($this->type != MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) {
1882
            if (isset($score['result'])) {
1883
                if (in_array($exercise->results_disabled, [
1884
                    RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
1885
                    RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
1886
                ])
1887
                ) {
1888
                    $score['result'] = null;
1889
                }
1890
                $header .= $exercise->getQuestionRibbon($class, $scoreLabel, $score['result'], $scoreCurrent);
1891
            }
1892
        }
1893
1894
        if ($this->type != READING_COMPREHENSION) {
1895
            // Do not show the description (the text to read) if the question is of type READING_COMPREHENSION
1896
            $header .= Display::div(
1897
                $this->description,
1898
                ['class' => 'question_description']
1899
            );
1900
        } else {
1901
            if ($score['pass'] == true) {
1902
                $message = Display::div(
1903
                    sprintf(
1904
                        get_lang('Congratulations, you have reached and correctly understood, at a speed of %s words per minute, a text of a total %s words.'),
1905
                        ReadingComprehension::$speeds[$this->level],
1906
                        $this->getWordsCount()
1907
                    )
1908
                );
1909
            } else {
1910
                $message = Display::div(
1911
                    sprintf(
1912
                        get_lang('Sorry, it seems like a speed of %s words/minute was too fast for this text of %s words.'),
1913
                        ReadingComprehension::$speeds[$this->level],
1914
                        $this->getWordsCount()
1915
                    )
1916
                );
1917
            }
1918
            $header .= $message.'<br />';
1919
        }
1920
1921
        if (isset($score['pass']) && $score['pass'] === false) {
1922
            if ($this->showFeedback($exercise)) {
1923
                $header .= $this->returnFormatFeedback();
1924
            }
1925
        }
1926
1927
        return $header;
1928
    }
1929
1930
    /**
1931
     * @deprecated
1932
     * Create a question from a set of parameters.
1933
     *
1934
     * @param   int     Quiz ID
1935
     * @param   string  Question name
1936
     * @param   int     Maximum result for the question
1937
     * @param   int     Type of question (see constants at beginning of question.class.php)
1938
     * @param   int     Question level/category
1939
     * @param string $quiz_id
1940
     */
1941
    public function create_question(
1942
        $quiz_id,
1943
        $question_name,
1944
        $question_description = '',
1945
        $max_score = 0,
1946
        $type = 1,
1947
        $level = 1
1948
    ) {
1949
        $course_id = api_get_course_int_id();
1950
        $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1951
        $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1952
1953
        $quiz_id = intval($quiz_id);
1954
        $max_score = (float) $max_score;
1955
        $type = intval($type);
1956
        $level = intval($level);
1957
1958
        // Get the max position
1959
        $sql = "SELECT max(position) as max_position
1960
                FROM $tbl_quiz_question q 
1961
                INNER JOIN $tbl_quiz_rel_question r
1962
                ON
1963
                    q.id = r.question_id AND
1964
                    exercice_id = $quiz_id AND
1965
                    q.c_id = $course_id AND
1966
                    r.c_id = $course_id";
1967
        $rs_max = Database::query($sql);
1968
        $row_max = Database::fetch_object($rs_max);
1969
        $max_position = $row_max->max_position + 1;
1970
1971
        $params = [
1972
            'c_id' => $course_id,
1973
            'question' => $question_name,
1974
            'description' => $question_description,
1975
            'ponderation' => $max_score,
1976
            'position' => $max_position,
1977
            'type' => $type,
1978
            'level' => $level,
1979
        ];
1980
        $question_id = Database::insert($tbl_quiz_question, $params);
1981
1982
        if ($question_id) {
1983
            $sql = "UPDATE $tbl_quiz_question  
1984
                    SET id = iid WHERE iid = $question_id";
1985
            Database::query($sql);
1986
1987
            // Get the max question_order
1988
            $sql = "SELECT max(question_order) as max_order
1989
                    FROM $tbl_quiz_rel_question
1990
                    WHERE c_id = $course_id AND exercice_id = $quiz_id ";
1991
            $rs_max_order = Database::query($sql);
1992
            $row_max_order = Database::fetch_object($rs_max_order);
1993
            $max_order = $row_max_order->max_order + 1;
1994
            // Attach questions to quiz
1995
            $sql = "INSERT INTO $tbl_quiz_rel_question (c_id, question_id, exercice_id, question_order)
1996
                    VALUES($course_id, $question_id, $quiz_id, $max_order)";
1997
            Database::query($sql);
1998
        }
1999
2000
        return $question_id;
2001
    }
2002
2003
    /**
2004
     * @return string
2005
     */
2006
    public function getTypePicture()
2007
    {
2008
        return $this->typePicture;
2009
    }
2010
2011
    /**
2012
     * @return string
2013
     */
2014
    public function getExplanation()
2015
    {
2016
        return $this->explanationLangVar;
2017
    }
2018
2019
    /**
2020
     * Get course medias.
2021
     *
2022
     * @param int $course_id
2023
     *
2024
     * @return array
2025
     */
2026
    public static function get_course_medias(
2027
        $course_id,
2028
        $start = 0,
2029
        $limit = 100,
2030
        $sidx = "question",
2031
        $sord = "ASC",
2032
        $where_condition = []
2033
    ) {
2034
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2035
        $default_where = [
2036
            'c_id = ? AND parent_id = 0 AND type = ?' => [
2037
                $course_id,
2038
                MEDIA_QUESTION,
2039
            ],
2040
        ];
2041
        $result = Database::select(
2042
            '*',
2043
            $table_question,
2044
            [
2045
                'limit' => " $start, $limit",
2046
                'where' => $default_where,
2047
                'order' => "$sidx $sord",
2048
            ]
2049
        );
2050
2051
        return $result;
2052
    }
2053
2054
    /**
2055
     * Get count course medias.
2056
     *
2057
     * @param int course id
2058
     *
2059
     * @return int
2060
     */
2061
    public static function get_count_course_medias($course_id)
2062
    {
2063
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2064
        $result = Database::select(
2065
            'count(*) as count',
2066
            $table_question,
2067
            [
2068
                'where' => [
2069
                    'c_id = ? AND parent_id = 0 AND type = ?' => [
2070
                        $course_id,
2071
                        MEDIA_QUESTION,
2072
                    ],
2073
                ],
2074
            ],
2075
            'first'
2076
        );
2077
2078
        if ($result && isset($result['count'])) {
2079
            return $result['count'];
2080
        }
2081
2082
        return 0;
2083
    }
2084
2085
    /**
2086
     * @param int $course_id
2087
     *
2088
     * @return array
2089
     */
2090
    public static function prepare_course_media_select($course_id)
2091
    {
2092
        $medias = self::get_course_medias($course_id);
2093
        $media_list = [];
2094
        $media_list[0] = get_lang('Not linked to media');
2095
2096
        if (!empty($medias)) {
2097
            foreach ($medias as $media) {
2098
                $media_list[$media['id']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
2099
            }
2100
        }
2101
2102
        return $media_list;
2103
    }
2104
2105
    /**
2106
     * @return array
2107
     */
2108
    public static function get_default_levels()
2109
    {
2110
        $levels = [
2111
            1 => 1,
2112
            2 => 2,
2113
            3 => 3,
2114
            4 => 4,
2115
            5 => 5,
2116
        ];
2117
2118
        return $levels;
2119
    }
2120
2121
    /**
2122
     * @return string
2123
     */
2124
    public function show_media_content()
2125
    {
2126
        $html = '';
2127
        if ($this->parent_id != 0) {
2128
            $parent_question = self::read($this->parent_id);
2129
            $html = $parent_question->show_media_content();
2130
        } else {
2131
            $html .= Display::page_subheader($this->selectTitle());
2132
            $html .= $this->selectDescription();
2133
        }
2134
2135
        return $html;
2136
    }
2137
2138
    /**
2139
     * Swap between unique and multiple type answers.
2140
     *
2141
     * @return UniqueAnswer|MultipleAnswer
2142
     */
2143
    public function swapSimpleAnswerTypes()
2144
    {
2145
        $oppositeAnswers = [
2146
            UNIQUE_ANSWER => MULTIPLE_ANSWER,
2147
            MULTIPLE_ANSWER => UNIQUE_ANSWER,
2148
        ];
2149
        $this->type = $oppositeAnswers[$this->type];
2150
        Database::update(
2151
            Database::get_course_table(TABLE_QUIZ_QUESTION),
2152
            ['type' => $this->type],
2153
            ['c_id = ? AND id = ?' => [$this->course['real_id'], $this->id]]
2154
        );
2155
        $answerClasses = [
2156
            UNIQUE_ANSWER => 'UniqueAnswer',
2157
            MULTIPLE_ANSWER => 'MultipleAnswer',
2158
        ];
2159
        $swappedAnswer = new $answerClasses[$this->type]();
2160
        foreach ($this as $key => $value) {
2161
            $swappedAnswer->$key = $value;
2162
        }
2163
2164
        return $swappedAnswer;
2165
    }
2166
2167
    /**
2168
     * @param array $score
2169
     *
2170
     * @return bool
2171
     */
2172
    public function isQuestionWaitingReview($score)
2173
    {
2174
        $isReview = false;
2175
        if (!empty($score)) {
2176
            if (!empty($score['comments']) || $score['score'] > 0) {
2177
                $isReview = true;
2178
            }
2179
        }
2180
2181
        return $isReview;
2182
    }
2183
2184
    /**
2185
     * @param string $value
2186
     */
2187
    public function setFeedback($value)
2188
    {
2189
        $this->feedback = $value;
2190
    }
2191
2192
    /**
2193
     * @param Exercise $exercise
2194
     *
2195
     * @return bool
2196
     */
2197
    public function showFeedback($exercise)
2198
    {
2199
        return
2200
            in_array($this->type, $this->questionTypeWithFeedback) &&
2201
            $exercise->getFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM;
2202
    }
2203
2204
    /**
2205
     * @return string
2206
     */
2207
    public function returnFormatFeedback()
2208
    {
2209
        return '<br />'.Display::return_message($this->feedback, 'normal', false);
2210
    }
2211
2212
    /**
2213
     * Check if this question exists in another exercise.
2214
     *
2215
     * @throws \Doctrine\ORM\Query\QueryException
2216
     *
2217
     * @return bool
2218
     */
2219
    public function existsInAnotherExercise()
2220
    {
2221
        $count = $this->getCountExercise();
2222
2223
        return $count > 1;
2224
    }
2225
2226
    /**
2227
     * @throws \Doctrine\ORM\Query\QueryException
2228
     *
2229
     * @return int
2230
     */
2231
    public function getCountExercise()
2232
    {
2233
        $em = Database::getManager();
2234
2235
        $count = $em
2236
            ->createQuery('
2237
                SELECT COUNT(qq.iid) FROM ChamiloCourseBundle:CQuizRelQuestion qq
2238
                WHERE qq.questionId = :id
2239
            ')
2240
            ->setParameters(['id' => (int) $this->id])
2241
            ->getSingleScalarResult();
2242
2243
        return (int) $count;
2244
    }
2245
2246
    /**
2247
     * Check if this question exists in another exercise.
2248
     *
2249
     * @throws \Doctrine\ORM\Query\QueryException
2250
     *
2251
     * @return mixed
2252
     */
2253
    public function getExerciseListWhereQuestionExists()
2254
    {
2255
        $em = Database::getManager();
2256
2257
        $result = $em
2258
            ->createQuery('
2259
                SELECT e 
2260
                FROM ChamiloCourseBundle:CQuizRelQuestion qq
2261
                JOIN ChamiloCourseBundle:CQuiz e                
2262
                WHERE e.iid = qq.exerciceId AND qq.questionId = :id 
2263
            ')
2264
            ->setParameters(['id' => (int) $this->id])
2265
            ->getResult();
2266
2267
        return $result;
2268
    }
2269
2270
    public function getHotSpotData()
2271
    {
2272
    }
2273
}
2274