Passed
Push — master ( 2d9a22...0085e5 )
by Julito
10:53 queued 02:39
created

Question::read()   C

Complexity

Conditions 15
Paths 126

Size

Total Lines 79
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 49
nc 126
nop 3
dl 0
loc 79
rs 5.7
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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