Passed
Pull Request — master (#6637)
by
unknown
14:37 queued 06:29
created

Question::displayTypeMenu()   B

Complexity

Conditions 10
Paths 55

Size

Total Lines 88
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

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

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