Passed
Push — master ( 5db41b...b5271b )
by Julito
10:05
created

Question::processCreation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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