Completed
Push — master ( d0e06e...1fcdba )
by Julito
09:05
created

Question::countAnswers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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