Question::search_engine_edit()   F
last analyzed

Complexity

Conditions 27
Paths 1123

Size

Total Lines 138
Code Lines 79

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 79
c 0
b 0
f 0
nc 1123
nop 3
dl 0
loc 138
rs 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
use Chamilo\CourseBundle\Entity\CQuizQuestionOption;
9
10
/**
11
 * Class Question.
12
 *
13
 * This class allows to instantiate an object of type Question
14
 *
15
 * @author Olivier Brouckaert, original author
16
 * @author Patrick Cool, LaTeX support
17
 * @author Julio Montoya <[email protected]> lot of bug fixes
18
 * @author [email protected] - add question categories
19
 */
20
abstract class Question
21
{
22
    public $id;
23
    public $iid;
24
    public $question;
25
    public $description;
26
    public $weighting;
27
    public $position;
28
    public $type;
29
    public $level;
30
    public $picture;
31
    public $exerciseList; // array with the list of exercises which this question is in
32
    public $category_list;
33
    public $parent_id;
34
    public $category;
35
    public $mandatory;
36
    public $isContent;
37
    public $course;
38
    public $feedback;
39
    public $typePicture = 'new_question.png';
40
    public $explanationLangVar = '';
41
    public $questionTableClass = 'table table-striped question-answer-result__detail';
42
    public $questionTypeWithFeedback;
43
    public $extra;
44
    public $export = false;
45
    public $code;
46
    public static $questionTypes = [
47
        UNIQUE_ANSWER => ['unique_answer.class.php', 'UniqueAnswer'],
48
        MULTIPLE_ANSWER => ['multiple_answer.class.php', 'MultipleAnswer'],
49
        FILL_IN_BLANKS => ['fill_blanks.class.php', 'FillBlanks'],
50
        MATCHING => ['matching.class.php', 'Matching'],
51
        FREE_ANSWER => ['freeanswer.class.php', 'FreeAnswer'],
52
        ORAL_EXPRESSION => ['oral_expression.class.php', 'OralExpression'],
53
        HOT_SPOT => ['hotspot.class.php', 'HotSpot'],
54
        HOT_SPOT_DELINEATION => ['HotSpotDelineation.php', 'HotSpotDelineation'],
55
        MULTIPLE_ANSWER_COMBINATION => ['multiple_answer_combination.class.php', 'MultipleAnswerCombination'],
56
        UNIQUE_ANSWER_NO_OPTION => ['unique_answer_no_option.class.php', 'UniqueAnswerNoOption'],
57
        MULTIPLE_ANSWER_TRUE_FALSE => ['multiple_answer_true_false.class.php', 'MultipleAnswerTrueFalse'],
58
        MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY => [
59
            'MultipleAnswerTrueFalseDegreeCertainty.php',
60
            'MultipleAnswerTrueFalseDegreeCertainty',
61
        ],
62
        MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE => [
63
            'multiple_answer_combination_true_false.class.php',
64
            'MultipleAnswerCombinationTrueFalse',
65
        ],
66
        GLOBAL_MULTIPLE_ANSWER => ['global_multiple_answer.class.php', 'GlobalMultipleAnswer'],
67
        CALCULATED_ANSWER => ['calculated_answer.class.php', 'CalculatedAnswer'],
68
        UNIQUE_ANSWER_IMAGE => ['UniqueAnswerImage.php', 'UniqueAnswerImage'],
69
        DRAGGABLE => ['Draggable.php', 'Draggable'],
70
        MATCHING_DRAGGABLE => ['MatchingDraggable.php', 'MatchingDraggable'],
71
        //MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion')
72
        ANNOTATION => ['Annotation.php', 'Annotation'],
73
        READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'],
74
    ];
75
76
    /**
77
     * constructor of the class.
78
     *
79
     * @author Olivier Brouckaert
80
     */
81
    public function __construct()
82
    {
83
        $this->id = 0;
84
        $this->iid = 0;
85
        $this->question = '';
86
        $this->description = '';
87
        $this->weighting = 0;
88
        $this->position = 1;
89
        $this->picture = '';
90
        $this->level = 1;
91
        $this->category = 0;
92
        // This variable is used when loading an exercise like an scenario with
93
        // an special hotspot: final_overlap, final_missing, final_excess
94
        $this->extra = '';
95
        $this->exerciseList = [];
96
        $this->course = api_get_course_info();
97
        $this->category_list = [];
98
        $this->parent_id = 0;
99
        $this->mandatory = 0;
100
        // See BT#12611
101
        $this->questionTypeWithFeedback = [
102
            MATCHING,
103
            MATCHING_DRAGGABLE,
104
            DRAGGABLE,
105
            FILL_IN_BLANKS,
106
            FREE_ANSWER,
107
            ORAL_EXPRESSION,
108
            CALCULATED_ANSWER,
109
            ANNOTATION,
110
        ];
111
    }
112
113
    public function getId()
114
    {
115
        return $this->iid;
116
    }
117
118
    /**
119
     * @return int|null
120
     */
121
    public function getIsContent()
122
    {
123
        $isContent = null;
124
        if (isset($_REQUEST['isContent'])) {
125
            $isContent = (int) $_REQUEST['isContent'];
126
        }
127
128
        return $this->isContent = $isContent;
129
    }
130
131
    /**
132
     * Reads question information from the data base.
133
     *
134
     * @param int   $id              - question ID
135
     * @param array $course_info
136
     * @param bool  $getExerciseList
137
     *
138
     * @return Question
139
     *
140
     * @author Olivier Brouckaert
141
     */
142
    public static function read($id, $course_info = [], $getExerciseList = true)
143
    {
144
        $id = (int) $id;
145
        if (empty($course_info)) {
146
            $course_info = api_get_course_info();
147
        }
148
        $course_id = $course_info['real_id'];
149
150
        if (empty($course_id) || -1 == $course_id) {
151
            return false;
152
        }
153
154
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
155
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
156
157
        $sql = "SELECT *
158
                FROM $TBL_QUESTIONS
159
                WHERE iid = $id ";
160
        $result = Database::query($sql);
161
162
        // if the question has been found
163
        if ($object = Database::fetch_object($result)) {
164
            $objQuestion = self::getInstance($object->type);
165
            if (!empty($objQuestion)) {
166
                $objQuestion->id = $id;
167
                $objQuestion->iid = (int) $object->iid;
168
                $objQuestion->question = $object->question;
169
                $objQuestion->description = $object->description;
170
                $objQuestion->weighting = $object->ponderation;
171
                $objQuestion->position = $object->position;
172
                $objQuestion->type = (int) $object->type;
173
                $objQuestion->picture = $object->picture;
174
                $objQuestion->level = (int) $object->level;
175
                $objQuestion->extra = $object->extra;
176
                $objQuestion->course = $course_info;
177
                $objQuestion->feedback = isset($object->feedback) ? $object->feedback : '';
178
                $objQuestion->category = TestCategory::getCategoryForQuestion($id, $course_id);
179
                $objQuestion->code = isset($object->code) ? $object->code : '';
180
                $categoryInfo = TestCategory::getCategoryInfoForQuestion($id, $course_id);
181
182
                if (!empty($categoryInfo)) {
183
                    if (isset($categoryInfo['category_id'])) {
184
                        $objQuestion->category = (int) $categoryInfo['category_id'];
185
                    }
186
187
                    if (('true' === api_get_setting('exercise.allow_mandatory_question_in_category')) &&
188
                        isset($categoryInfo['mandatory'])
189
                    ) {
190
                        $objQuestion->mandatory = (int) $categoryInfo['mandatory'];
191
                    }
192
                }
193
194
                if ($getExerciseList) {
195
                    $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
196
                    $sql = "SELECT DISTINCT q.quiz_id
197
                            FROM $TBL_EXERCISE_QUESTION q
198
                            INNER JOIN $tblQuiz e
199
                            ON e.iid = q.quiz_id
200
                            WHERE
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) {
208
                        while ($obj = Database::fetch_object($result)) {
209
                            $objQuestion->exerciseList[] = $obj->quiz_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 ('true' !== api_get_setting('editor.save_titles_as_html')) {
232
            return $this->question;
233
        }
234
235
        return Display::div($this->question, ['style' => 'display: inline-block;']);
236
    }
237
238
    public function getTitleToDisplay(Exercise $exercise, int $itemNumber): string
239
    {
240
        $showQuestionTitleHtml = ('true' === api_get_setting('editor.save_titles_as_html'));
241
        $title = '';
242
        if ('true' === api_get_setting('exercise.show_question_id')) {
243
            $title .= '<h4>#'.$this->course['code'].'-'.$this->iid.'</h4>';
244
        }
245
246
        $title .= $showQuestionTitleHtml ? '' : '<strong>';
247
        if (1 !== $exercise->getHideQuestionNumber()) {
248
            $title .= $itemNumber.'. ';
249
        }
250
        $title .= $this->selectTitle();
251
        $title .= $showQuestionTitleHtml ? '' : '</strong>';
252
253
        return Display::div(
254
            $title,
255
            ['class' => 'question_title']
256
        );
257
    }
258
259
    /**
260
     * returns the question description.
261
     *
262
     * @author Olivier Brouckaert
263
     *
264
     * @return string - question description
265
     */
266
    public function selectDescription()
267
    {
268
        return $this->description;
269
    }
270
271
    /**
272
     * returns the question weighting.
273
     *
274
     * @author Olivier Brouckaert
275
     *
276
     * @return int - question weighting
277
     */
278
    public function selectWeighting()
279
    {
280
        return $this->weighting;
281
    }
282
283
    /**
284
     * returns the answer type.
285
     *
286
     * @author Olivier Brouckaert
287
     *
288
     * @return int - answer type
289
     */
290
    public function selectType()
291
    {
292
        return $this->type;
293
    }
294
295
    /**
296
     * returns the level of the question.
297
     *
298
     * @author Nicolas Raynaud
299
     *
300
     * @return int - level of the question, 0 by default
301
     */
302
    public function getLevel()
303
    {
304
        return $this->level;
305
    }
306
307
    /**
308
     * changes the question title.
309
     *
310
     * @param string $title - question title
311
     *
312
     * @author Olivier Brouckaert
313
     */
314
    public function updateTitle($title)
315
    {
316
        $this->question = $title;
317
    }
318
319
    /**
320
     * changes the question description.
321
     *
322
     * @param string $description - question description
323
     *
324
     * @author Olivier Brouckaert
325
     */
326
    public function updateDescription($description)
327
    {
328
        $this->description = $description;
329
    }
330
331
    /**
332
     * changes the question weighting.
333
     *
334
     * @param int $weighting - question weighting
335
     *
336
     * @author Olivier Brouckaert
337
     */
338
    public function updateWeighting($weighting)
339
    {
340
        $this->weighting = $weighting;
341
    }
342
343
    /**
344
     * @param array $category
345
     *
346
     * @author Hubert Borderiou 12-10-2011
347
     */
348
    public function updateCategory($category)
349
    {
350
        $this->category = $category;
351
    }
352
353
    public function setMandatory($value)
354
    {
355
        $this->mandatory = (int) $value;
356
    }
357
358
    /**
359
     * in this version, a question can only have 1 category
360
     * if category is 0, then question has no category then delete the category entry.
361
     *
362
     * @author Hubert Borderiou 12-10-2011
363
     */
364
    public function saveCategory(int $categoryId): bool
365
    {
366
        if ($categoryId <= 0) {
367
            $this->deleteCategory();
368
        } else {
369
            // update or add category for a question
370
            $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
371
            $categoryId = (int) $categoryId;
372
            $questionId = (int) $this->id;
373
            $sql = "SELECT count(*) AS nb FROM $table
374
                    WHERE
375
                        question_id = $questionId
376
                    ";
377
            $res = Database::query($sql);
378
            $row = Database::fetch_array($res);
379
            $allowMandatory = ('true' === api_get_setting('exercise.allow_mandatory_question_in_category'));
380
            if ($row['nb'] > 0) {
381
                $extraMandatoryCondition = '';
382
                if ($allowMandatory) {
383
                    $extraMandatoryCondition = ", mandatory = {$this->mandatory}";
384
                }
385
                $sql = "UPDATE $table
386
                        SET category_id = $categoryId
387
                        $extraMandatoryCondition
388
                        WHERE
389
                            question_id = $questionId
390
                        ";
391
                Database::query($sql);
392
            } else {
393
                $sql = "INSERT INTO $table (question_id, category_id)
394
                        VALUES ($questionId, $categoryId)
395
                        ";
396
                Database::query($sql);
397
                if ($allowMandatory) {
398
                    $id = Database::insert_id();
399
                    if ($id) {
400
                        $sql = "UPDATE $table SET mandatory = {$this->mandatory}
401
                                WHERE iid = $id";
402
                        Database::query($sql);
403
                    }
404
                }
405
            }
406
        }
407
408
        return true;
409
    }
410
411
    /**
412
     * @author hubert borderiou 12-10-2011
413
     *
414
     *                      delete any category entry for question id
415
     *                      delete the category for question
416
     */
417
    public function deleteCategory(): bool
418
    {
419
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
420
        $questionId = (int) $this->id;
421
        if (empty($questionId)) {
422
            return false;
423
        }
424
        $sql = "DELETE FROM $table
425
                WHERE
426
                    question_id = $questionId
427
                ";
428
        Database::query($sql);
429
430
        return true;
431
    }
432
433
    /**
434
     * changes the question position.
435
     *
436
     * @param int $position - question position
437
     *
438
     * @author Olivier Brouckaert
439
     */
440
    public function updatePosition($position)
441
    {
442
        $this->position = $position;
443
    }
444
445
    /**
446
     * changes the question level.
447
     *
448
     * @param int $level - question level
449
     *
450
     * @author Nicolas Raynaud
451
     */
452
    public function updateLevel($level)
453
    {
454
        $this->level = $level;
455
    }
456
457
    /**
458
     * changes the answer type. If the user changes the type from "unique answer" to "multiple answers"
459
     * (or conversely) answers are not deleted, otherwise yes.
460
     *
461
     * @param int $type - answer type
462
     *
463
     * @author Olivier Brouckaert
464
     */
465
    public function updateType($type)
466
    {
467
        $table = Database::get_course_table(TABLE_QUIZ_ANSWER);
468
        $course_id = $this->course['real_id'];
469
470
        if (empty($course_id)) {
471
            $course_id = api_get_course_int_id();
472
        }
473
        // if we really change the type
474
        if ($type != $this->type) {
475
            // if we don't change from "unique answer" to "multiple answers" (or conversely)
476
            if (!in_array($this->type, [UNIQUE_ANSWER, MULTIPLE_ANSWER]) ||
477
                !in_array($type, [UNIQUE_ANSWER, MULTIPLE_ANSWER])
478
            ) {
479
                // removes old answers
480
                $sql = "DELETE FROM $table
481
                        WHERE c_id = $course_id AND question_id = ".(int) ($this->id);
482
                Database::query($sql);
483
            }
484
485
            $this->type = $type;
486
        }
487
    }
488
489
    /**
490
     * Set title.
491
     *
492
     * @param string $title
493
     */
494
    public function setTitle($title)
495
    {
496
        $this->question = $title;
497
    }
498
499
    /**
500
     * Sets extra info.
501
     *
502
     * @param string $extra
503
     */
504
    public function setExtra($extra)
505
    {
506
        $this->extra = $extra;
507
    }
508
509
    /**
510
     * updates the question in the data base
511
     * if an exercise ID is provided, we add that exercise ID into the exercise list.
512
     *
513
     * @author Olivier Brouckaert
514
     *
515
     * @param Exercise $exercise
516
     */
517
    public function save($exercise)
518
    {
519
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
520
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
521
        $em = Database::getManager();
522
        $exerciseId = $exercise->iId;
523
524
        $id = $this->id;
525
        $type = $this->type;
526
        $c_id = $this->course['real_id'];
527
528
        $courseEntity = api_get_course_entity($c_id);
529
        $categoryId = $this->category;
530
531
        $questionCategoryRepo = Container::getQuestionCategoryRepository();
532
        $questionRepo = Container::getQuestionRepository();
533
534
        // question already exists
535
        if (!empty($id)) {
536
            /** @var CQuizQuestion $question */
537
            $question = $questionRepo->find($id);
538
            if ($question) {
0 ignored issues
show
introduced by
$question is of type Chamilo\CourseBundle\Entity\CQuizQuestion, thus it always evaluated to true.
Loading history...
539
                $question
540
                    ->setQuestion($this->question)
541
                    ->setDescription($this->description)
542
                    ->setPonderation($this->weighting)
543
                    ->setPosition($this->position)
544
                    ->setType($this->type)
545
                    ->setExtra($this->extra)
546
                    ->setLevel((int) $this->level)
547
                    ->setFeedback($this->feedback);
548
549
                if (!empty($categoryId)) {
550
                    $category = $questionCategoryRepo->find($categoryId);
551
                    $question->updateCategory($category);
552
                }
553
554
                $em->persist($question);
555
                $em->flush();
556
557
                Event::addEvent(
0 ignored issues
show
Bug introduced by
The method addEvent() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

557
                Event::/** @scrutinizer ignore-call */ 
558
                       addEvent(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
558
                    LOG_QUESTION_UPDATED,
559
                    LOG_QUESTION_ID,
560
                    $this->iid
561
                );
562
                if ('true' === api_get_setting('search_enabled')) {
563
                    $this->search_engine_edit($exerciseId);
564
                }
565
            }
566
        } else {
567
            // Creates a new question
568
            $sql = "SELECT max(position)
569
                    FROM $TBL_QUESTIONS as question,
570
                    $TBL_EXERCISE_QUESTION as test_question
571
                    WHERE
572
                        question.iid = test_question.question_id AND
573
                        test_question.quiz_id = ".$exerciseId;
574
            $result = Database::query($sql);
575
            $current_position = Database::result($result, 0, 0);
576
            $this->updatePosition($current_position + 1);
577
            $position = $this->position;
578
            //$exerciseEntity = $exerciseRepo->find($exerciseId);
579
580
            $question = (new CQuizQuestion())
581
                ->setQuestion($this->question)
582
                ->setDescription($this->description)
583
                ->setPonderation($this->weighting)
584
                ->setPosition($position)
585
                ->setType($this->type)
586
                ->setExtra($this->extra)
587
                ->setLevel((int) $this->level)
588
                ->setFeedback($this->feedback)
589
                ->setParent($courseEntity)
590
                ->addCourseLink(
591
                    $courseEntity,
592
                    api_get_session_entity(),
593
                    api_get_group_entity()
594
                )
595
            ;
596
597
            $em->persist($question);
598
            $em->flush();
599
600
            $this->id = $question->getIid();
601
602
            if ($this->id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
603
                Event::addEvent(
604
                    LOG_QUESTION_CREATED,
605
                    LOG_QUESTION_ID,
606
                    $this->id
607
                );
608
609
                $questionRepo->addFileFromFileRequest($question, 'imageUpload');
610
611
                // If hotspot, create first answer
612
                if (HOT_SPOT == $type || HOT_SPOT_ORDER == $type) {
613
                    $quizAnswer = new CQuizAnswer();
614
                    $quizAnswer
615
                        ->setQuestion($question)
616
                        ->setPonderation(10)
617
                        ->setPosition(1)
618
                        ->setHotspotCoordinates('0;0|0|0')
619
                        ->setHotspotType('square');
620
621
                    $em->persist($quizAnswer);
622
                    $em->flush();
623
                }
624
625
                if (HOT_SPOT_DELINEATION == $type) {
626
                    $quizAnswer = new CQuizAnswer();
627
                    $quizAnswer
628
                        ->setQuestion($question)
629
                        ->setPonderation(10)
630
                        ->setPosition(1)
631
                        ->setHotspotCoordinates('0;0|0|0')
632
                        ->setHotspotType('delineation');
633
634
                    $em->persist($quizAnswer);
635
                    $em->flush();
636
                }
637
638
                if ('true' === api_get_setting('search_enabled')) {
639
                    $this->search_engine_edit($exerciseId, true);
640
                }
641
            }
642
        }
643
644
        // if the question is created in an exercise
645
        if (!empty($exerciseId)) {
646
            // adds the exercise into the exercise list of this question
647
            $this->addToList($exerciseId, true);
648
        }
649
    }
650
651
    /**
652
     * @param int  $exerciseId
653
     * @param bool $addQs
654
     * @param bool $rmQs
655
     */
656
    public function search_engine_edit(
657
        $exerciseId,
658
        $addQs = false,
659
        $rmQs = false
660
    ) {
661
        // update search engine and its values table if enabled
662
        if (!empty($exerciseId) && 'true' == api_get_setting('search_enabled') &&
663
            extension_loaded('xapian')
664
        ) {
665
            $course_id = api_get_course_id();
666
            // get search_did
667
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
668
            if ($addQs || $rmQs) {
669
                //there's only one row per question on normal db and one document per question on search engine db
670
                $sql = 'SELECT * FROM %s
671
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=%s LIMIT 1';
672
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
673
            } else {
674
                $sql = 'SELECT * FROM %s
675
                    WHERE course_code=\'%s\' AND tool_id=\'%s\'
676
                    AND ref_id_high_level=%s AND ref_id_second_level=%s LIMIT 1';
677
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
678
            }
679
            $res = Database::query($sql);
680
681
            if (Database::num_rows($res) > 0 || $addQs) {
682
                $di = new ChamiloIndexer();
683
                if ($addQs) {
684
                    $question_exercises = [(int) $exerciseId];
685
                } else {
686
                    $question_exercises = [];
687
                }
688
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
689
                $di->connectDb(null, null, $lang);
690
691
                // retrieve others exercise ids
692
                $se_ref = Database::fetch_array($res);
693
                $se_doc = $di->get_document((int) $se_ref['search_did']);
694
                if (false !== $se_doc) {
695
                    if (false !== ($se_doc_data = $di->get_document_data($se_doc))) {
696
                        $se_doc_data = UnserializeApi::unserialize(
697
                            'not_allowed_classes',
698
                            $se_doc_data
699
                        );
700
                        if (isset($se_doc_data[SE_DATA]['type']) &&
701
                            SE_DOCTYPE_EXERCISE_QUESTION == $se_doc_data[SE_DATA]['type']
702
                        ) {
703
                            if (isset($se_doc_data[SE_DATA]['exercise_ids']) &&
704
                                is_array($se_doc_data[SE_DATA]['exercise_ids'])
705
                            ) {
706
                                foreach ($se_doc_data[SE_DATA]['exercise_ids'] as $old_value) {
707
                                    if (!in_array($old_value, $question_exercises)) {
708
                                        $question_exercises[] = $old_value;
709
                                    }
710
                                }
711
                            }
712
                        }
713
                    }
714
                }
715
                if ($rmQs) {
716
                    while (false !== ($key = array_search($exerciseId, $question_exercises))) {
717
                        unset($question_exercises[$key]);
718
                    }
719
                }
720
721
                // build the chunk to index
722
                $ic_slide = new IndexableChunk();
723
                $ic_slide->addValue('title', $this->question);
724
                $ic_slide->addCourseId($course_id);
725
                $ic_slide->addToolId(TOOL_QUIZ);
726
                $xapian_data = [
727
                    SE_COURSE_ID => $course_id,
728
                    SE_TOOL_ID => TOOL_QUIZ,
729
                    SE_DATA => [
730
                        'type' => SE_DOCTYPE_EXERCISE_QUESTION,
731
                        'exercise_ids' => $question_exercises,
732
                        'question_id' => (int) $this->id,
733
                    ],
734
                    SE_USER => (int) api_get_user_id(),
735
                ];
736
                $ic_slide->xapian_data = serialize($xapian_data);
737
                $ic_slide->addValue('content', $this->description);
738
739
                //TODO: index answers, see also form validation on question_admin.inc.php
740
741
                $di->remove_document($se_ref['search_did']);
742
                $di->addChunk($ic_slide);
743
744
                //index and return search engine document id
745
                if (!empty($question_exercises)) { // if empty there is nothing to index
746
                    $did = $di->index();
747
                    unset($di);
748
                }
749
                if ($did || $rmQs) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $did does not seem to be defined for all execution paths leading up to this point.
Loading history...
750
                    // save it to db
751
                    if ($addQs || $rmQs) {
752
                        $sql = "DELETE FROM %s
753
                            WHERE course_code = '%s' AND tool_id = '%s' AND ref_id_second_level = '%s'";
754
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
755
                    } else {
756
                        $sql = "DELETE FROM %S
757
                            WHERE
758
                                course_code = '%s'
759
                                AND tool_id = '%s'
760
                                AND tool_id = '%s'
761
                                AND ref_id_high_level = '%s'
762
                                AND ref_id_second_level = '%s'";
763
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
764
                    }
765
                    Database::query($sql);
766
                    if ($rmQs) {
767
                        if (!empty($question_exercises)) {
768
                            $sql = "INSERT INTO %s (
769
                                    id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
770
                                )
771
                                VALUES (
772
                                    NULL, '%s', '%s', %s, %s, %s
773
                                )";
774
                            $sql = sprintf(
775
                                $sql,
776
                                $tbl_se_ref,
777
                                $course_id,
778
                                TOOL_QUIZ,
779
                                array_shift($question_exercises),
780
                                $this->id,
781
                                $did
782
                            );
783
                            Database::query($sql);
784
                        }
785
                    } else {
786
                        $sql = "INSERT INTO %s (
787
                                id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
788
                            )
789
                            VALUES (
790
                                NULL , '%s', '%s', %s, %s, %s
791
                            )";
792
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id, $did);
793
                        Database::query($sql);
794
                    }
795
                }
796
            }
797
        }
798
    }
799
800
    /**
801
     * adds an exercise into the exercise list.
802
     *
803
     * @author Olivier Brouckaert
804
     *
805
     * @param int  $exerciseId - exercise ID
806
     * @param bool $fromSave   - from $this->save() or not
807
     */
808
    public function addToList($exerciseId, $fromSave = false)
809
    {
810
        $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
811
        $id = (int) $this->id;
812
        $exerciseId = (int) $exerciseId;
813
814
        // checks if the exercise ID is not in the list
815
        if (!empty($exerciseId) && !in_array($exerciseId, $this->exerciseList)) {
816
            $this->exerciseList[] = $exerciseId;
817
            $courseId = isset($this->course['real_id']) ? $this->course['real_id'] : 0;
818
            $newExercise = new Exercise($courseId);
819
            $newExercise->read($exerciseId, false);
820
            $count = $newExercise->getQuestionCount();
821
            $count++;
822
            $sql = "INSERT INTO $exerciseRelQuestionTable (question_id, quiz_id, question_order)
823
                    VALUES (".$id.', '.$exerciseId.", '$count')";
824
            Database::query($sql);
825
826
            // we do not want to reindex if we had just saved adnd indexed the question
827
            if (!$fromSave) {
828
                $this->search_engine_edit($exerciseId, true);
829
            }
830
        }
831
    }
832
833
    /**
834
     * removes an exercise from the exercise list.
835
     *
836
     * @author Olivier Brouckaert
837
     *
838
     * @param int $exerciseId - exercise ID
839
     * @param int $courseId
840
     *
841
     * @return bool - true if removed, otherwise false
842
     */
843
    public function removeFromList($exerciseId, $courseId = 0)
844
    {
845
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
846
        $id = (int) $this->id;
847
        $exerciseId = (int) $exerciseId;
848
849
        // searches the position of the exercise ID in the list
850
        $pos = array_search($exerciseId, $this->exerciseList);
851
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
852
853
        // exercise not found
854
        if (false === $pos) {
855
            return false;
856
        } else {
857
            // deletes the position in the array containing the wanted exercise ID
858
            unset($this->exerciseList[$pos]);
859
            //update order of other elements
860
            $sql = "SELECT question_order
861
                    FROM $table
862
                    WHERE
863
                        question_id = $id AND
864
                        quiz_id = $exerciseId";
865
            $res = Database::query($sql);
866
            if (Database::num_rows($res) > 0) {
867
                $row = Database::fetch_array($res);
868
                if (!empty($row['question_order'])) {
869
                    $sql = "UPDATE $table
870
                            SET question_order = question_order-1
871
                            WHERE
872
                                quiz_id = $exerciseId AND
873
                                question_order > ".$row['question_order'];
874
                    Database::query($sql);
875
                }
876
            }
877
878
            $sql = "DELETE FROM $table
879
                    WHERE
880
                        question_id = $id AND
881
                        quiz_id = $exerciseId";
882
            Database::query($sql);
883
884
            return true;
885
        }
886
    }
887
888
    /**
889
     * Deletes a question from the database
890
     * the parameter tells if the question is removed from all exercises (value = 0),
891
     * or just from one exercise (value = exercise ID).
892
     *
893
     * @author Olivier Brouckaert
894
     *
895
     * @param int $deleteFromEx - exercise ID if the question is only removed from one exercise
896
     *
897
     * @return bool
898
     */
899
    public function delete($deleteFromEx = 0)
900
    {
901
        if (empty($this->course)) {
902
            return false;
903
        }
904
905
        $courseId = $this->course['real_id'];
906
907
        if (empty($courseId)) {
908
            return false;
909
        }
910
911
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
912
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
913
        $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
914
        $TBL_QUIZ_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
915
916
        $id = (int) $this->id;
917
918
        // if the question must be removed from all exercises
919
        if (!$deleteFromEx) {
920
            //update the question_order of each question to avoid inconsistencies
921
            $sql = "SELECT quiz_id, question_order
922
                    FROM $TBL_EXERCISE_QUESTION
923
                    WHERE question_id = ".$id;
924
925
            $res = Database::query($sql);
926
            if (Database::num_rows($res) > 0) {
927
                while ($row = Database::fetch_array($res)) {
928
                    if (!empty($row['question_order'])) {
929
                        $sql = "UPDATE $TBL_EXERCISE_QUESTION
930
                                SET question_order = question_order-1
931
                                WHERE
932
                                    quiz_id = ".(int) ($row['quiz_id']).' AND
933
                                    question_order > '.$row['question_order'];
934
                        Database::query($sql);
935
                    }
936
                }
937
            }
938
939
            $sql = "DELETE FROM $TBL_EXERCISE_QUESTION
940
                    WHERE question_id = ".$id;
941
            Database::query($sql);
942
943
            $sql = "DELETE FROM $TBL_QUESTIONS
944
                    WHERE iid = ".$id;
945
            Database::query($sql);
946
947
            $sql = "DELETE FROM $TBL_REPONSES
948
                    WHERE question_id = ".$id;
949
            Database::query($sql);
950
951
            // remove the category of this question in the question_rel_category table
952
            $sql = "DELETE FROM $TBL_QUIZ_QUESTION_REL_CATEGORY
953
                    WHERE
954
                        question_id = ".$id;
955
            Database::query($sql);
956
957
            // Add extra fields.
958
            $extraField = new ExtraFieldValue('question');
959
            $extraField->deleteValuesByItem($this->iid);
960
961
            /*api_item_property_update(
962
                $this->course,
963
                TOOL_QUIZ,
964
                $id,
965
                'QuizQuestionDeleted',
966
                api_get_user_id()
967
            );*/
968
            Event::addEvent(
969
                LOG_QUESTION_DELETED,
970
                LOG_QUESTION_ID,
971
                $this->iid
972
            );
973
        //$this->removePicture();
974
        } else {
975
            // just removes the exercise from the list
976
            $this->removeFromList($deleteFromEx, $courseId);
977
            if ('true' == api_get_setting('search_enabled') && extension_loaded('xapian')) {
978
                // disassociate question with this exercise
979
                $this->search_engine_edit($deleteFromEx, false, true);
980
            }
981
            /*
982
            api_item_property_update(
983
                $this->course,
984
                TOOL_QUIZ,
985
                $id,
986
                'QuizQuestionDeleted',
987
                api_get_user_id()
988
            );*/
989
            Event::addEvent(
990
                LOG_QUESTION_REMOVED_FROM_QUIZ,
991
                LOG_QUESTION_ID,
992
                $this->iid
993
            );
994
        }
995
996
        return true;
997
    }
998
999
    /**
1000
     * Duplicates the question.
1001
     *
1002
     * @author Olivier Brouckaert
1003
     *
1004
     * @param array $courseInfo Course info of the destination course
1005
     *
1006
     * @return false|string ID of the new question
1007
     */
1008
    public function duplicate($courseInfo = [])
1009
    {
1010
        $courseInfo = empty($courseInfo) ? $this->course : $courseInfo;
1011
1012
        if (empty($courseInfo)) {
1013
            return false;
1014
        }
1015
        $TBL_QUESTION_OPTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1016
1017
        $questionText = $this->question;
1018
        $description = $this->description;
1019
1020
        // Using the same method used in the course copy to transform URLs
1021
        if ($this->course['id'] != $courseInfo['id']) {
1022
            $description = DocumentManager::replaceUrlWithNewCourseCode(
1023
                $description,
1024
                $this->course['code'],
1025
                $courseInfo['id']
1026
            );
1027
            $questionText = DocumentManager::replaceUrlWithNewCourseCode(
1028
                $questionText,
1029
                $this->course['code'],
1030
                $courseInfo['id']
1031
            );
1032
        }
1033
1034
        $course_id = $courseInfo['real_id'];
1035
1036
        // Read the source options
1037
        $options = self::readQuestionOption($this->id, $this->course['real_id']);
1038
1039
        $em = Database::getManager();
1040
        $courseEntity = api_get_course_entity($course_id);
1041
1042
        $question = (new CQuizQuestion())
1043
            ->setQuestion($questionText)
1044
            ->setDescription($description)
1045
            ->setPonderation($this->weighting)
1046
            ->setPosition($this->position)
1047
            ->setType($this->type)
1048
            ->setExtra($this->extra)
1049
            ->setLevel($this->level)
1050
            ->setFeedback($this->feedback)
1051
            ->setParent($courseEntity)
1052
            ->addCourseLink($courseEntity)
1053
        ;
1054
1055
        $em->persist($question);
1056
        $em->flush();
1057
        $newQuestionId = $question->getIid();
1058
1059
        if ($newQuestionId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $newQuestionId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1060
            // Add extra fields.
1061
            $extraField = new ExtraFieldValue('question');
1062
            $extraField->copy($this->iid, $newQuestionId);
1063
1064
            if (!empty($options)) {
1065
                // Saving the quiz_options
1066
                foreach ($options as $item) {
1067
                    $item['question_id'] = $newQuestionId;
1068
                    $item['c_id'] = $course_id;
1069
                    unset($item['iid']);
1070
                    unset($item['iid']);
1071
                    Database::insert($TBL_QUESTION_OPTIONS, $item);
1072
                }
1073
            }
1074
1075
            // Duplicates the picture of the hotspot
1076
            // @todo implement copy of hotspot question
1077
            if (HOT_SPOT == $this->type) {
1078
                throw new Exception('implement copy of hotspot question');
1079
            }
1080
        }
1081
1082
        return $newQuestionId;
1083
    }
1084
1085
    /**
1086
     * @return string
1087
     */
1088
    public function get_question_type_name()
1089
    {
1090
        $key = self::$questionTypes[$this->type];
1091
1092
        return get_lang($key[1]);
1093
    }
1094
1095
    /**
1096
     * @param string $type
1097
     */
1098
    public static function get_question_type($type)
1099
    {
1100
        return self::$questionTypes[$type];
1101
    }
1102
1103
    /**
1104
     * @return array
1105
     */
1106
    public static function getQuestionTypeList()
1107
    {
1108
        if ('true' !== api_get_setting('enable_quiz_scenario')) {
1109
            self::$questionTypes[HOT_SPOT_DELINEATION] = null;
1110
            unset(self::$questionTypes[HOT_SPOT_DELINEATION]);
1111
        }
1112
1113
        return self::$questionTypes;
1114
    }
1115
1116
    /**
1117
     * Returns an instance of the class corresponding to the type.
1118
     *
1119
     * @param int $type the type of the question
1120
     *
1121
     * @return $this instance of a Question subclass (or of Questionc class by default)
1122
     */
1123
    public static function getInstance($type)
1124
    {
1125
        if (null !== $type) {
1126
            [$fileName, $className] = self::get_question_type($type);
1127
            if (!empty($fileName)) {
1128
                if (class_exists($className)) {
1129
                    return new $className();
1130
                } else {
1131
                    echo 'Can\'t instanciate class '.$className.' of type '.$type;
1132
                }
1133
            }
1134
        }
1135
1136
        return null;
1137
    }
1138
1139
    /**
1140
     * Creates the form to create / edit a question
1141
     * A subclass can redefine this function to add fields...
1142
     *
1143
     * @param FormValidator $form
1144
     * @param Exercise      $exercise
1145
     */
1146
    public function createForm(&$form, $exercise)
1147
    {
1148
        $zoomOptions = api_get_setting('exercise.quiz_image_zoom', true);
1149
        if (isset($zoomOptions['options'])) {
1150
            $finderFolder = api_get_path(WEB_PATH).'vendor/studio-42/elfinder/';
1151
            echo '<!-- elFinder CSS (REQUIRED) -->';
1152
            echo '<link rel="stylesheet" type="text/css" media="screen" href="'.$finderFolder.'css/elfinder.full.css">';
1153
            echo '<link rel="stylesheet" type="text/css" media="screen" href="'.$finderFolder.'css/theme.css">';
1154
1155
            echo '<!-- elFinder JS (REQUIRED) -->';
1156
            echo '<script src="'.$finderFolder.'js/elfinder.full.js"></script>';
1157
1158
            echo '<!-- elFinder translation (OPTIONAL) -->';
1159
            $language = 'en';
1160
            $platformLanguage = api_get_language_isocode();
1161
            $iso = api_get_language_isocode($platformLanguage);
1162
            $filePart = "vendor/studio-42/elfinder/js/i18n/elfinder.$iso.js";
1163
            $file = api_get_path(SYS_PATH).$filePart;
1164
            $includeFile = '';
1165
            if (file_exists($file)) {
1166
                $includeFile = '<script src="'.api_get_path(WEB_PATH).$filePart.'"></script>';
1167
                $language = $iso;
1168
            }
1169
            echo $includeFile;
1170
            echo '<script>
1171
            $(function() {
1172
                $(".create_img_link").click(function(e){
1173
                    e.preventDefault();
1174
                    e.stopPropagation();
1175
                    var imageZoom = $("input[name=\'imageZoom\']").val();
1176
                    var imageWidth = $("input[name=\'imageWidth\']").val();
1177
                    CKEDITOR.instances.questionDescription.insertHtml(\'<img id="zoom_picture" class="zoom_picture" src="\'+imageZoom+\'" data-zoom-image="\'+imageZoom+\'" width="\'+imageWidth+\'px" />\');
1178
                });
1179
1180
                $("input[name=\'imageZoom\']").on("click", function(){
1181
                    var elf = $("#elfinder").elfinder({
1182
                        url : "'.api_get_path(WEB_LIBRARY_PATH).'elfinder/connectorAction.php?'.api_get_cidreq().'",
1183
                        getFileCallback: function(file) {
1184
                            var filePath = file; //file contains the relative url.
1185
                            var imgPath = "<img src = \'"+filePath+"\'/>";
1186
                            $("input[name=\'imageZoom\']").val(filePath.url);
1187
                            $("#elfinder").remove(); //close the window after image is selected
1188
                        },
1189
                        startPathHash: "l2_Lw", // Sets the course driver as default
1190
                        resizable: false,
1191
                        lang: "'.$language.'"
1192
                    }).elfinder("instance");
1193
                });
1194
            });
1195
            </script>';
1196
            echo '<div id="elfinder"></div>';
1197
        }
1198
1199
        // question name
1200
        if ('true' === api_get_setting('editor.save_titles_as_html')) {
1201
            $editorConfig = ['ToolbarSet' => 'TitleAsHtml'];
1202
            $form->addHtmlEditor(
1203
                'questionName',
1204
                get_lang('Question'),
1205
                false,
1206
                false,
1207
                $editorConfig
1208
            );
1209
        } else {
1210
            $form->addText('questionName', get_lang('Question'));
1211
        }
1212
1213
        $form->addRule('questionName', get_lang('Please type the question'), 'required');
1214
1215
        // default content
1216
        $isContent = isset($_REQUEST['isContent']) ? (int) $_REQUEST['isContent'] : null;
1217
1218
        // Question type
1219
        $answerType = isset($_REQUEST['answerType']) ? (int) $_REQUEST['answerType'] : null;
1220
        $form->addHidden('answerType', $answerType);
1221
1222
        // html editor
1223
        $editorConfig = [
1224
            'ToolbarSet' => 'TestQuestionDescription',
1225
            'Height' => '150',
1226
        ];
1227
1228
        if (!api_is_allowed_to_edit(null, true)) {
1229
            $editorConfig['UserStatus'] = 'student';
1230
        }
1231
1232
        $form->addButtonAdvancedSettings('advanced_params');
1233
        $form->addHtml('<div id="advanced_params_options" style="display:none">');
1234
1235
        if (isset($zoomOptions['options'])) {
1236
            $form->addElement('text', 'imageZoom', get_lang('ImageURL'));
1237
            $form->addElement('text', 'imageWidth', get_lang('PixelWidth'));
1238
            $form->addButton('btn_create_img', get_lang('AddToEditor'), 'plus', 'info', 'small', 'create_img_link');
1239
        }
1240
1241
        $form->addHtmlEditor(
1242
            'questionDescription',
1243
            get_lang('Enrich question'),
1244
            false,
1245
            false,
1246
            $editorConfig
1247
        );
1248
1249
        if (MEDIA_QUESTION != $this->type) {
1250
            // Advanced parameters.
1251
            $form->addSelect(
1252
                'questionLevel',
1253
                get_lang('Difficulty'),
1254
                self::get_default_levels()
1255
            );
1256
1257
            // Categories.
1258
            $form->addSelect(
1259
                'questionCategory',
1260
                get_lang('Category'),
1261
                TestCategory::getCategoriesIdAndName()
1262
            );
1263
            if (EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM == $exercise->getQuestionSelectionType() &&
1264
                ('true' === api_get_setting('exercise.allow_mandatory_question_in_category'))
1265
            ) {
1266
                $form->addCheckBox('mandatory', get_lang('IsMandatory'));
1267
            }
1268
1269
            //global $text;
1270
            $text = get_lang('Save the question');
1271
            switch ($this->type) {
1272
                case UNIQUE_ANSWER:
1273
                    $buttonGroup = [];
1274
                    $buttonGroup[] = $form->addButtonSave(
1275
                        $text,
1276
                        'submitQuestion',
1277
                        true
1278
                    );
1279
                    $buttonGroup[] = $form->addButton(
1280
                        'convertAnswer',
1281
                        get_lang('Convert to multiple answer'),
1282
                        'dot-circle-o',
1283
                        'default',
1284
                        null,
1285
                        null,
1286
                        null,
1287
                        true
1288
                    );
1289
                    $form->addGroup($buttonGroup);
1290
1291
                    break;
1292
                case MULTIPLE_ANSWER:
1293
                    $buttonGroup = [];
1294
                    $buttonGroup[] = $form->addButtonSave(
1295
                        $text,
1296
                        'submitQuestion',
1297
                        true
1298
                    );
1299
                    $buttonGroup[] = $form->addButton(
1300
                        'convertAnswer',
1301
                        get_lang('Convert to unique answer'),
1302
                        'check-square-o',
1303
                        'default',
1304
                        null,
1305
                        null,
1306
                        null,
1307
                        true
1308
                    );
1309
                    $form->addGroup($buttonGroup);
1310
1311
                    break;
1312
            }
1313
            //Medias
1314
            //$course_medias = self::prepare_course_media_select(api_get_course_int_id());
1315
            //$form->addSelect('parent_id', get_lang('Attach to media'), $course_medias);
1316
        }
1317
1318
        $form->addElement('html', '</div>');
1319
1320
        if (!isset($_GET['fromExercise'])) {
1321
            switch ($answerType) {
1322
                case 1:
1323
                    $this->question = get_lang('Select the good reasoning');
1324
1325
                    break;
1326
                case 2:
1327
                    $this->question = get_lang('The marasmus is a consequence of');
1328
1329
                    break;
1330
                case 3:
1331
                    $this->question = get_lang('Calculate the Body Mass Index');
1332
1333
                    break;
1334
                case 4:
1335
                    $this->question = get_lang('Order the operations');
1336
1337
                    break;
1338
                case 5:
1339
                    $this->question = get_lang('List what you consider the 10 top qualities of a good project manager?');
1340
1341
                    break;
1342
                case 9:
1343
                    $this->question = get_lang('The marasmus is a consequence of');
1344
1345
                    break;
1346
            }
1347
        }
1348
1349
        if (null !== $exercise) {
1350
            if ($exercise->questionFeedbackEnabled && $this->showFeedback($exercise)) {
1351
                $form->addTextarea('feedback', get_lang('Feedback if not correct'));
1352
            }
1353
        }
1354
1355
        $extraField = new ExtraField('question');
1356
        $extraField->addElements($form, $this->iid);
1357
1358
        // default values
1359
        $defaults = [];
1360
        $defaults['questionName'] = $this->question;
1361
        $defaults['questionDescription'] = $this->description;
1362
        $defaults['questionLevel'] = $this->level;
1363
        $defaults['questionCategory'] = $this->category;
1364
        $defaults['feedback'] = $this->feedback;
1365
        $defaults['mandatory'] = $this->mandatory;
1366
1367
        // Came from he question pool
1368
        if (isset($_GET['fromExercise'])) {
1369
            $form->setDefaults($defaults);
1370
        }
1371
1372
        if (!isset($_GET['newQuestion']) || $isContent) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $isContent of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1373
            $form->setDefaults($defaults);
1374
        }
1375
1376
        /*if (!empty($_REQUEST['myid'])) {
1377
            $form->setDefaults($defaults);
1378
        } else {
1379
            if ($isContent == 1) {
1380
                $form->setDefaults($defaults);
1381
            }
1382
        }*/
1383
    }
1384
1385
    /**
1386
     * Function which process the creation of questions.
1387
     */
1388
    public function processCreation(FormValidator $form, Exercise $exercise)
1389
    {
1390
        $this->updateTitle($form->getSubmitValue('questionName'));
1391
        $this->updateDescription($form->getSubmitValue('questionDescription'));
1392
        $this->updateLevel($form->getSubmitValue('questionLevel'));
1393
        $this->updateCategory($form->getSubmitValue('questionCategory'));
1394
        $this->setMandatory($form->getSubmitValue('mandatory'));
1395
        $this->setFeedback($form->getSubmitValue('feedback'));
1396
1397
        //Save normal question if NOT media
1398
        if (MEDIA_QUESTION != $this->type) {
1399
            $this->save($exercise);
1400
            // modify the exercise
1401
            $exercise->addToList($this->id);
1402
            $exercise->update_question_positions();
1403
1404
            $params = $form->exportValues();
1405
            $params['item_id'] = $this->id;
1406
1407
            $extraFieldValues = new ExtraFieldValue('question');
1408
            $extraFieldValues->saveFieldValues($params);
1409
        }
1410
    }
1411
1412
    /**
1413
     * Creates the form to create / edit the answers of the question.
1414
     */
1415
    abstract public function createAnswersForm(FormValidator $form);
1416
1417
    /**
1418
     * Process the creation of answers.
1419
     *
1420
     * @param FormValidator $form
1421
     * @param Exercise      $exercise
1422
     */
1423
    abstract public function processAnswersCreation($form, $exercise);
1424
1425
    /**
1426
     * Displays the menu of question types.
1427
     *
1428
     * @param Exercise $objExercise
1429
     */
1430
    public static function displayTypeMenu($objExercise)
1431
    {
1432
        if (empty($objExercise)) {
1433
            return '';
1434
        }
1435
1436
        $feedbackType = $objExercise->getFeedbackType();
1437
        $exerciseId = $objExercise->id;
1438
1439
        // 1. by default we show all the question types
1440
        $questionTypeList = self::getQuestionTypeList();
1441
1442
        if (!isset($feedbackType)) {
1443
            $feedbackType = 0;
1444
        }
1445
1446
        switch ($feedbackType) {
1447
            case EXERCISE_FEEDBACK_TYPE_DIRECT:
1448
                $questionTypeList = [
1449
                    UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
1450
                    HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION],
1451
                ];
1452
1453
                break;
1454
            case EXERCISE_FEEDBACK_TYPE_POPUP:
1455
                $questionTypeList = [
1456
                    UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
1457
                    MULTIPLE_ANSWER => self::$questionTypes[MULTIPLE_ANSWER],
1458
                    DRAGGABLE => self::$questionTypes[DRAGGABLE],
1459
                    HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION],
1460
                    CALCULATED_ANSWER => self::$questionTypes[CALCULATED_ANSWER],
1461
                ];
1462
1463
                break;
1464
            default:
1465
                unset($questionTypeList[HOT_SPOT_DELINEATION]);
1466
1467
                break;
1468
        }
1469
1470
        echo '<div class="card">';
1471
        echo '<div class="card-body">';
1472
        echo '<ul class="question_menu">';
1473
        foreach ($questionTypeList as $i => $type) {
1474
            /** @var Question $type */
1475
            $type = new $type[1]();
1476
            $img = $type->getTypePicture();
1477
            $explanation = $type->getExplanation();
1478
            echo '<li>';
1479
            echo '<div class="icon-image">';
1480
            $icon = Display::url(
1481
                Display::return_icon($img, $explanation, null, ICON_SIZE_BIG),
1482
                'admin.php?'.api_get_cidreq().'&'
1483
                    .http_build_query(['newQuestion' => 'yes', 'answerType' => $i, 'exerciseId' => $exerciseId]),
1484
                ['title' => $explanation]
1485
            );
1486
1487
            if (false === $objExercise->force_edit_exercise_in_lp) {
1488
                if (true == $objExercise->exercise_was_added_in_lp) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1489
                    $img = pathinfo($img);
1490
                    $img = $img['filename'].'_na.'.$img['extension'];
1491
                    $icon = Display::return_icon($img, $explanation, null, ICON_SIZE_BIG);
1492
                }
1493
            }
1494
            echo $icon;
1495
            echo '</div>';
1496
            echo '</li>';
1497
        }
1498
1499
        echo '<li>';
1500
        echo '<div class="icon_image">';
1501
        if (true == $objExercise->exercise_was_added_in_lp) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1502
            echo Display::getMdiIcon('database', 'ch-tool-icon-disabled mt-4', null, ICON_SIZE_BIG, get_lang('Recycle existing questions'));
1503
        } else {
1504
            if (in_array($feedbackType, [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
1505
                echo $url = '<a href="question_pool.php?'.api_get_cidreq()."&type=1&fromExercise=$exerciseId\">";
1506
            } else {
1507
                echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&fromExercise='.$exerciseId.'">';
1508
            }
1509
            echo Display::getMdiIcon('database', 'ch-tool-icon mt-4', 'display: block; height: auto;', ICON_SIZE_BIG, get_lang('Recycle existing questions'));
1510
        }
1511
        echo '</a>';
1512
        echo '</div></li>';
1513
        echo '</ul>';
1514
        echo '</div>';
1515
        echo '</div>';
1516
    }
1517
1518
    /**
1519
     * @param string $name
1520
     * @param int    $position
1521
     *
1522
     * @return CQuizQuestion|null
1523
     */
1524
    public static function saveQuestionOption(CQuizQuestion $question, $name, $position = 0)
1525
    {
1526
        $option = new CQuizQuestionOption();
1527
        $option
1528
            ->setQuestion($question)
1529
            ->setTitle($name)
1530
            ->setPosition($position)
1531
        ;
1532
        $em = Database::getManager();
1533
        $em->persist($option);
1534
        $em->flush();
1535
    }
1536
1537
    /**
1538
     * @param int $question_id
1539
     * @param int $course_id
1540
     */
1541
    public static function deleteAllQuestionOptions($question_id, $course_id)
1542
    {
1543
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1544
        Database::delete(
1545
            $table,
1546
            [
1547
                'c_id = ? AND question_id = ?' => [
1548
                    $course_id,
1549
                    $question_id,
1550
                ],
1551
            ]
1552
        );
1553
    }
1554
1555
    /**
1556
     * @param int $question_id
1557
     *
1558
     * @return array
1559
     */
1560
    public static function readQuestionOption($question_id)
1561
    {
1562
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1563
1564
        return Database::select(
1565
            '*',
1566
            $table,
1567
            [
1568
                'where' => [
1569
                    'question_id = ?' => [
1570
                        $question_id,
1571
                    ],
1572
                ],
1573
                'order' => 'iid ASC',
1574
            ]
1575
        );
1576
    }
1577
1578
    /**
1579
     * Shows question title an description.
1580
     *
1581
     * @param int   $counter
1582
     * @param array $score
1583
     *
1584
     * @return string HTML string with the header of the question (before the answers table)
1585
     */
1586
    public function return_header(Exercise $exercise, $counter = null, $score = [])
1587
    {
1588
        $counterLabel = '';
1589
        if (!empty($counter)) {
1590
            $counterLabel = (int) $counter;
1591
        }
1592
1593
        $scoreLabel = get_lang('Wrong');
1594
        if (in_array($exercise->results_disabled, [
1595
            RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
1596
            RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
1597
        ])
1598
        ) {
1599
            $scoreLabel = get_lang('Wrong answer. The correct one was:');
1600
        }
1601
1602
        $class = 'error';
1603
        if (isset($score['pass']) && true == $score['pass']) {
1604
            $scoreLabel = get_lang('Correct');
1605
1606
            if (in_array($exercise->results_disabled, [
1607
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
1608
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
1609
            ])
1610
            ) {
1611
                $scoreLabel = get_lang('Correct answer');
1612
            }
1613
            $class = 'success';
1614
        }
1615
1616
        switch ($this->type) {
1617
            case FREE_ANSWER:
1618
            case ORAL_EXPRESSION:
1619
            case ANNOTATION:
1620
                $score['revised'] = isset($score['revised']) ? $score['revised'] : false;
1621
                if (true == $score['revised']) {
1622
                    $scoreLabel = get_lang('Revised');
1623
                    $class = '';
1624
                } else {
1625
                    $scoreLabel = get_lang('Not reviewed');
1626
                    $class = 'warning';
1627
                    if (isset($score['weight'])) {
1628
                        $weight = float_format($score['weight'], 1);
1629
                        $score['result'] = ' ? / '.$weight;
1630
                    }
1631
                    $model = ExerciseLib::getCourseScoreModel();
1632
                    if (!empty($model)) {
1633
                        $score['result'] = ' ? ';
1634
                    }
1635
1636
                    $hide = ('true' === api_get_setting('exercise.hide_free_question_score'));
1637
                    if (true === $hide) {
1638
                        $score['result'] = '-';
1639
                    }
1640
                }
1641
1642
                break;
1643
            case UNIQUE_ANSWER:
1644
                if (in_array($exercise->results_disabled, [
1645
                    RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
1646
                    RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
1647
                ])
1648
                ) {
1649
                    if (isset($score['user_answered'])) {
1650
                        if (false === $score['user_answered']) {
1651
                            $scoreLabel = get_lang('Unanswered');
1652
                            $class = 'info';
1653
                        }
1654
                    }
1655
                }
1656
1657
                break;
1658
        }
1659
1660
        // display question category, if any
1661
        $header = '';
1662
        if ($exercise->display_category_name) {
1663
            $header = TestCategory::returnCategoryAndTitle($this->id);
1664
        }
1665
        $show_media = '';
1666
        if ($show_media) {
1667
            $header .= $this->show_media_content();
1668
        }
1669
1670
        $scoreCurrent = [
1671
            'used' => isset($score['score']) ? $score['score'] : '',
1672
            'missing' => isset($score['weight']) ? $score['weight'] : '',
1673
        ];
1674
        $header .= Display::page_subheader2($counterLabel.'. '.$this->question);
1675
1676
        $showRibbon = true;
1677
        // dont display score for certainty degree questions
1678
        if (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $this->type) {
1679
            $showRibbon = false;
1680
            $ribbonResult = ('true' === api_get_setting('exercise.show_exercise_question_certainty_ribbon_result'));
1681
            if (true === $ribbonResult) {
1682
                $showRibbon = true;
1683
            }
1684
        }
1685
1686
        if ($showRibbon && isset($score['result'])) {
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
                $score['result'] = null;
1693
            }
1694
            $header .= $exercise->getQuestionRibbon($class, $scoreLabel, $score['result'], $scoreCurrent);
1695
        }
1696
1697
        if (READING_COMPREHENSION != $this->type) {
1698
            // Do not show the description (the text to read) if the question is of type READING_COMPREHENSION
1699
            $header .= Display::div(
1700
                $this->description,
1701
                ['class' => 'question-answer-result__header-description']
1702
            );
1703
        } else {
1704
            /** @var ReadingComprehension $this */
1705
            if (true === $score['pass']) {
1706
                $message = Display::div(
1707
                    sprintf(
1708
                        get_lang(
1709
                            'Congratulations, you have reached and correctly understood, at a speed of %s words per minute, a text of a total %s words.'
1710
                        ),
1711
                        ReadingComprehension::$speeds[$this->level],
1712
                        $this->getWordsCount()
1713
                    )
1714
                );
1715
            } else {
1716
                $message = Display::div(
1717
                    sprintf(
1718
                        get_lang(
1719
                            'Sorry, it seems like a speed of %s words/minute was too fast for this text of %s words.'
1720
                        ),
1721
                        ReadingComprehension::$speeds[$this->level],
1722
                        $this->getWordsCount()
1723
                    )
1724
                );
1725
            }
1726
            $header .= $message.'<br />';
1727
        }
1728
1729
        if ($exercise->hideComment && HOT_SPOT == $this->type) {
1730
            $header .= Display::return_message(get_lang('ResultsOnlyAvailableOnline'));
1731
1732
            return $header;
1733
        }
1734
1735
        if (isset($score['pass']) && false === $score['pass']) {
1736
            if ($this->showFeedback($exercise)) {
1737
                $header .= $this->returnFormatFeedback();
1738
            }
1739
        }
1740
1741
        return Display::div(
1742
            $header,
1743
            ['class' => 'question-answer-result__header']
1744
        );
1745
    }
1746
1747
    /**
1748
     * @deprecated
1749
     * Create a question from a set of parameters
1750
     *
1751
     * @param int    $question_name        Quiz ID
1752
     * @param string $question_description Question name
1753
     * @param int    $max_score            Maximum result for the question
1754
     * @param int    $type                 Type of question (see constants at beginning of question.class.php)
1755
     * @param int    $level                Question level/category
1756
     * @param string $quiz_id
1757
     */
1758
    public function create_question(
1759
        $quiz_id,
1760
        $question_name,
1761
        $question_description = '',
1762
        $max_score = 0,
1763
        $type = 1,
1764
        $level = 1
1765
    ) {
1766
        $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1767
        $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1768
1769
        $quiz_id = (int) $quiz_id;
1770
        $max_score = (float) $max_score;
1771
        $type = (int) $type;
1772
        $level = (int) $level;
1773
1774
        // Get the max position
1775
        $sql = "SELECT max(position) as max_position
1776
                FROM $tbl_quiz_question q
1777
                INNER JOIN $tbl_quiz_rel_question r
1778
                ON
1779
                    q.iid = r.question_id AND
1780
                    quiz_id = $quiz_id";
1781
        $rs_max = Database::query($sql);
1782
        $row_max = Database::fetch_object($rs_max);
1783
        $max_position = $row_max->max_position + 1;
1784
1785
        $params = [
1786
            'question' => $question_name,
1787
            'description' => $question_description,
1788
            'ponderation' => $max_score,
1789
            'position' => $max_position,
1790
            'type' => $type,
1791
            'level' => $level,
1792
            'mandatory' => 0,
1793
        ];
1794
        $question_id = Database::insert($tbl_quiz_question, $params);
1795
1796
        if ($question_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $question_id of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1797
            // Get the max question_order
1798
            $sql = "SELECT max(question_order) as max_order
1799
                    FROM $tbl_quiz_rel_question
1800
                    WHERE quiz_id = $quiz_id ";
1801
            $rs_max_order = Database::query($sql);
1802
            $row_max_order = Database::fetch_object($rs_max_order);
1803
            $max_order = $row_max_order->max_order + 1;
1804
            // Attach questions to quiz
1805
            $sql = "INSERT INTO $tbl_quiz_rel_question (question_id, quiz_id, question_order)
1806
                    VALUES($question_id, $quiz_id, $max_order)";
1807
            Database::query($sql);
1808
        }
1809
1810
        return $question_id;
1811
    }
1812
1813
    /**
1814
     * @return string
1815
     */
1816
    public function getTypePicture()
1817
    {
1818
        return $this->typePicture;
1819
    }
1820
1821
    /**
1822
     * @return string
1823
     */
1824
    public function getExplanation()
1825
    {
1826
        return get_lang($this->explanationLangVar);
1827
    }
1828
1829
    /**
1830
     * Get course medias.
1831
     *
1832
     * @param int $course_id
1833
     *
1834
     * @return array
1835
     */
1836
    public static function get_course_medias(
1837
        $course_id,
1838
        $start = 0,
1839
        $limit = 100,
1840
        $sidx = 'question',
1841
        $sord = 'ASC',
1842
        $where_condition = []
1843
    ) {
1844
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1845
        $default_where = [
1846
            'c_id = ? AND parent_id = 0 AND type = ?' => [
1847
                $course_id,
1848
                MEDIA_QUESTION,
1849
            ],
1850
        ];
1851
1852
        return Database::select(
1853
            '*',
1854
            $table_question,
1855
            [
1856
                'limit' => " $start, $limit",
1857
                'where' => $default_where,
1858
                'order' => "$sidx $sord",
1859
            ]
1860
        );
1861
    }
1862
1863
    /**
1864
     * Get count course medias.
1865
     *
1866
     * @param int $course_id course id
1867
     *
1868
     * @return int
1869
     */
1870
    public static function get_count_course_medias($course_id)
1871
    {
1872
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1873
        $result = Database::select(
1874
            'count(*) as count',
1875
            $table_question,
1876
            [
1877
                'where' => [
1878
                    'c_id = ? AND parent_id = 0 AND type = ?' => [
1879
                        $course_id,
1880
                        MEDIA_QUESTION,
1881
                    ],
1882
                ],
1883
            ],
1884
            'first'
1885
        );
1886
1887
        if ($result && isset($result['count'])) {
1888
            return $result['count'];
1889
        }
1890
1891
        return 0;
1892
    }
1893
1894
    /**
1895
     * @param int $course_id
1896
     *
1897
     * @return array
1898
     */
1899
    public static function prepare_course_media_select($course_id)
1900
    {
1901
        $medias = self::get_course_medias($course_id);
1902
        $media_list = [];
1903
        $media_list[0] = get_lang('Not linked to media');
1904
1905
        if (!empty($medias)) {
1906
            foreach ($medias as $media) {
1907
                $media_list[$media['id']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
1908
            }
1909
        }
1910
1911
        return $media_list;
1912
    }
1913
1914
    /**
1915
     * @return array
1916
     */
1917
    public static function get_default_levels()
1918
    {
1919
        return [
1920
            1 => 1,
1921
            2 => 2,
1922
            3 => 3,
1923
            4 => 4,
1924
            5 => 5,
1925
        ];
1926
    }
1927
1928
    /**
1929
     * @return string
1930
     */
1931
    public function show_media_content()
1932
    {
1933
        $html = '';
1934
        if (0 != $this->parent_id) {
1935
            $parent_question = self::read($this->parent_id);
1936
            $html = $parent_question->show_media_content();
1937
        } else {
1938
            $html .= Display::page_subheader($this->selectTitle());
1939
            $html .= $this->selectDescription();
1940
        }
1941
1942
        return $html;
1943
    }
1944
1945
    /**
1946
     * Swap between unique and multiple type answers.
1947
     *
1948
     * @return UniqueAnswer|MultipleAnswer
1949
     */
1950
    public function swapSimpleAnswerTypes()
1951
    {
1952
        $oppositeAnswers = [
1953
            UNIQUE_ANSWER => MULTIPLE_ANSWER,
1954
            MULTIPLE_ANSWER => UNIQUE_ANSWER,
1955
        ];
1956
        $this->type = $oppositeAnswers[$this->type];
1957
        Database::update(
1958
            Database::get_course_table(TABLE_QUIZ_QUESTION),
1959
            ['type' => $this->type],
1960
            ['c_id = ? AND id = ?' => [$this->course['real_id'], $this->id]]
1961
        );
1962
        $answerClasses = [
1963
            UNIQUE_ANSWER => 'UniqueAnswer',
1964
            MULTIPLE_ANSWER => 'MultipleAnswer',
1965
        ];
1966
        $swappedAnswer = new $answerClasses[$this->type]();
1967
        foreach ($this as $key => $value) {
1968
            $swappedAnswer->$key = $value;
1969
        }
1970
1971
        return $swappedAnswer;
1972
    }
1973
1974
    /**
1975
     * @param array $score
1976
     *
1977
     * @return bool
1978
     */
1979
    public function isQuestionWaitingReview($score)
1980
    {
1981
        $isReview = false;
1982
        if (!empty($score)) {
1983
            if (!empty($score['comments']) || $score['score'] > 0) {
1984
                $isReview = true;
1985
            }
1986
        }
1987
1988
        return $isReview;
1989
    }
1990
1991
    /**
1992
     * @param string $value
1993
     */
1994
    public function setFeedback($value)
1995
    {
1996
        $this->feedback = $value;
1997
    }
1998
1999
    /**
2000
     * @param Exercise $exercise
2001
     *
2002
     * @return bool
2003
     */
2004
    public function showFeedback($exercise)
2005
    {
2006
        if (false === $exercise->hideComment) {
2007
            return false;
2008
        }
2009
2010
        return
2011
            in_array($this->type, $this->questionTypeWithFeedback) &&
2012
            EXERCISE_FEEDBACK_TYPE_EXAM != $exercise->getFeedbackType();
2013
    }
2014
2015
    /**
2016
     * @return string
2017
     */
2018
    public function returnFormatFeedback()
2019
    {
2020
        return '<br />'.Display::return_message($this->feedback, 'normal', false);
2021
    }
2022
2023
    /**
2024
     * Check if this question exists in another exercise.
2025
     *
2026
     * @throws \Doctrine\ORM\Query\QueryException
2027
     *
2028
     * @return bool
2029
     */
2030
    public function existsInAnotherExercise()
2031
    {
2032
        $count = $this->getCountExercise();
2033
2034
        return $count > 1;
2035
    }
2036
2037
    /**
2038
     * @throws \Doctrine\ORM\Query\QueryException
2039
     *
2040
     * @return int
2041
     */
2042
    public function getCountExercise()
2043
    {
2044
        $em = Database::getManager();
2045
2046
        $count = $em
2047
            ->createQuery('
2048
                SELECT COUNT(qq.iid) FROM ChamiloCourseBundle:CQuizRelQuestion qq
2049
                WHERE qq.question = :id
2050
            ')
2051
            ->setParameters(['id' => (int) $this->id])
2052
            ->getSingleScalarResult();
2053
2054
        return (int) $count;
2055
    }
2056
2057
    /**
2058
     * Check if this question exists in another exercise.
2059
     *
2060
     * @throws \Doctrine\ORM\Query\QueryException
2061
     */
2062
    public function getExerciseListWhereQuestionExists()
2063
    {
2064
        $em = Database::getManager();
2065
2066
        return $em
2067
            ->createQuery('
2068
                SELECT e
2069
                FROM ChamiloCourseBundle:CQuizRelQuestion qq
2070
                JOIN ChamiloCourseBundle:CQuiz e
2071
                WHERE e.iid = qq.exerciceId AND qq.questionId = :id
2072
            ')
2073
            ->setParameters(['id' => (int) $this->id])
2074
            ->getResult();
2075
    }
2076
2077
    /**
2078
     * @return int
2079
     */
2080
    public function countAnswers()
2081
    {
2082
        $result = Database::select(
2083
            'COUNT(1) AS c',
2084
            Database::get_course_table(TABLE_QUIZ_ANSWER),
2085
            ['where' => ['question_id = ?' => [$this->id]]],
2086
            'first'
2087
        );
2088
2089
        return (int) $result['c'];
2090
    }
2091
}
2092