Question::getIsContent()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CourseBundle\Entity\CQuizAnswer;
6
7
/**
8
 * Class Question.
9
 *
10
 * This class allows to instantiate an object of type Question
11
 *
12
 * @author Olivier Brouckaert, original author
13
 * @author Patrick Cool, LaTeX support
14
 * @author Julio Montoya <[email protected]> lot of bug fixes
15
 * @author [email protected] - add question categories
16
 */
17
abstract class Question
18
{
19
    public $id;
20
    public $iid;
21
    public $question;
22
    public $description;
23
    public $weighting;
24
    public $position;
25
    public $type;
26
    public $level;
27
    public $picture;
28
    public $exerciseList; // array with the list of exercises which this question is in
29
    public $category_list;
30
    public $parent_id;
31
    public $category;
32
    public $mandatory;
33
    public $isContent;
34
    public $course;
35
    public $feedback;
36
    public $typePicture = 'new_question.png';
37
    public $explanationLangVar = '';
38
    public $question_table_class = 'table table-striped';
39
    public $questionTypeWithFeedback;
40
    public $extra;
41
    public $export = false;
42
    public $code;
43
    public static $questionTypes = [
44
        UNIQUE_ANSWER => ['unique_answer.class.php', 'UniqueAnswer'],
45
        MULTIPLE_ANSWER => ['multiple_answer.class.php', 'MultipleAnswer'],
46
        FILL_IN_BLANKS => ['fill_blanks.class.php', 'FillBlanks'],
47
        FILL_IN_BLANKS_COMBINATION => ['FillBlanksCombination.php', 'FillBlanksCombination'],
48
        MATCHING => ['matching.class.php', 'Matching'],
49
        MATCHING_COMBINATION => ['MatchingCombination.php', 'MatchingCombination'],
50
        FREE_ANSWER => ['freeanswer.class.php', 'FreeAnswer'],
51
        ORAL_EXPRESSION => ['oral_expression.class.php', 'OralExpression'],
52
        HOT_SPOT => ['hotspot.class.php', 'HotSpot'],
53
        HOT_SPOT_COMBINATION => ['HotSpotCombination.php', 'HotSpotCombination'],
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
        MATCHING_DRAGGABLE_COMBINATION => ['MatchingDraggableCombination.php', 'MatchingDraggableCombination'],
72
        //MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion')
73
        ANNOTATION => ['Annotation.php', 'Annotation'],
74
        READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'],
75
        UPLOAD_ANSWER => ['UploadAnswer.php', 'UploadAnswer'],
76
        MULTIPLE_ANSWER_DROPDOWN => ['MultipleAnswerDropdown.php', 'MultipleAnswerDropdown'],
77
        MULTIPLE_ANSWER_DROPDOWN_COMBINATION => ['MultipleAnswerDropdownCombination.php', 'MultipleAnswerDropdownCombination'],
78
        ANSWER_IN_OFFICE_DOC => ['AnswerInOfficeDoc.php', 'AnswerInOfficeDoc'],
79
    ];
80
81
    /**
82
     * constructor of the class.
83
     *
84
     * @author Olivier Brouckaert
85
     */
86
    public function __construct()
87
    {
88
        $this->iid = 0;
89
        $this->question = '';
90
        $this->description = '';
91
        $this->weighting = 0;
92
        $this->position = 1;
93
        $this->picture = '';
94
        $this->level = 1;
95
        $this->category = 0;
96
        // This variable is used when loading an exercise like an scenario with
97
        // an special hotspot: final_overlap, final_missing, final_excess
98
        $this->extra = '';
99
        $this->exerciseList = [];
100
        $this->course = api_get_course_info();
101
        $this->category_list = [];
102
        $this->parent_id = 0;
103
        $this->mandatory = 0;
104
        // See BT#12611
105
        $this->questionTypeWithFeedback = [
106
            MATCHING,
107
            MATCHING_COMBINATION,
108
            MATCHING_DRAGGABLE,
109
            MATCHING_DRAGGABLE_COMBINATION,
110
            DRAGGABLE,
111
            FILL_IN_BLANKS,
112
            FILL_IN_BLANKS_COMBINATION,
113
            FREE_ANSWER,
114
            ANSWER_IN_OFFICE_DOC,
115
            ORAL_EXPRESSION,
116
            CALCULATED_ANSWER,
117
            ANNOTATION,
118
            UPLOAD_ANSWER,
119
        ];
120
    }
121
122
    /**
123
     * @return int|null
124
     */
125
    public function getIsContent()
126
    {
127
        $isContent = null;
128
        if (isset($_REQUEST['isContent'])) {
129
            $isContent = (int) $_REQUEST['isContent'];
130
        }
131
132
        return $this->isContent = $isContent;
133
    }
134
135
    /**
136
     * Reads question information from the data base.
137
     *
138
     * @param int   $id              - question ID
139
     * @param array $course_info
140
     * @param bool  $getExerciseList
141
     *
142
     * @return Question
143
     *
144
     * @author Olivier Brouckaert
145
     */
146
    public static function read($id, $course_info = [], $getExerciseList = true)
147
    {
148
        $id = (int) $id;
149
        if (empty($course_info)) {
150
            $course_info = api_get_course_info();
151
        }
152
        $course_id = $course_info['real_id'];
153
154
        if (empty($course_id) || -1 == $course_id) {
155
            return false;
156
        }
157
158
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
159
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
160
161
        $sql = "SELECT *
162
                FROM $TBL_QUESTIONS
163
                WHERE iid = $id ";
164
        $result = Database::query($sql);
165
166
        // if the question has been found
167
        if ($object = Database::fetch_object($result)) {
168
            $objQuestion = self::getInstance($object->type);
169
            if (!empty($objQuestion)) {
170
                $objQuestion->iid = (int) $object->iid;
171
                $objQuestion->question = $object->question;
172
                $objQuestion->description = $object->description;
173
                $objQuestion->weighting = $object->ponderation;
174
                $objQuestion->position = $object->position;
175
                $objQuestion->type = (int) $object->type;
176
                $objQuestion->picture = $object->picture;
177
                $objQuestion->level = (int) $object->level;
178
                $objQuestion->extra = $object->extra;
179
                $objQuestion->course = $course_info;
180
                $objQuestion->feedback = isset($object->feedback) ? $object->feedback : '';
181
                $objQuestion->code = isset($object->code) ? $object->code : '';
182
                $categoryInfo = TestCategory::getCategoryInfoForQuestion($id, $course_id);
183
184
                if (!empty($categoryInfo)) {
185
                    if (isset($categoryInfo['category_id'])) {
186
                        $objQuestion->category = (int) $categoryInfo['category_id'];
187
                    }
188
189
                    if (api_get_configuration_value('allow_mandatory_question_in_category') &&
190
                        isset($categoryInfo['mandatory'])
191
                    ) {
192
                        $objQuestion->mandatory = (int) $categoryInfo['mandatory'];
193
                    }
194
                }
195
196
                if ($getExerciseList) {
197
                    $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
198
                    $sql = "SELECT DISTINCT q.exercice_id
199
                            FROM $TBL_EXERCISE_QUESTION q
200
                            INNER JOIN $tblQuiz e
201
                            ON e.iid = q.exercice_id
202
                            WHERE
203
                                q.c_id = $course_id AND
204
                                q.question_id = $id AND
205
                                e.active >= 0";
206
207
                    $result = Database::query($sql);
208
209
                    // fills the array with the exercises which this question is in
210
                    if ($result) {
0 ignored issues
show
introduced by
$result is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
211
                        while ($obj = Database::fetch_object($result)) {
212
                            $objQuestion->exerciseList[] = $obj->exercice_id;
213
                        }
214
                    }
215
                }
216
217
                return $objQuestion;
218
            }
219
        }
220
221
        // question not found
222
        return false;
223
    }
224
225
    /**
226
     * returns the question ID.
227
     *
228
     * @author Olivier Brouckaert
229
     *
230
     * @return int - question ID
231
     */
232
    public function selectId()
233
    {
234
        return $this->iid;
235
    }
236
237
    /**
238
     * returns the question title.
239
     *
240
     * @author Olivier Brouckaert
241
     *
242
     * @return string - question title
243
     */
244
    public function selectTitle()
245
    {
246
        if (!api_get_configuration_value('save_titles_as_html')) {
247
            return $this->question;
248
        }
249
250
        return Display::div($this->question, ['style' => 'display: inline-block;']);
251
    }
252
253
    /**
254
     * @param int $itemNumber The numerical counter of the question
255
     * @param int $exerciseId The iid of the corresponding c_quiz, for specific rules applied to the title
256
     */
257
    public function getTitleToDisplay(int $itemNumber, int $exerciseId): string
258
    {
259
        $showQuestionTitleHtml = api_get_configuration_value('save_titles_as_html');
260
        $title = '';
261
        if (api_get_configuration_value('show_question_id')) {
262
            $title .= '<h4>#'.$this->course['code'].'-'.$this->iid.'</h4>';
263
        }
264
265
        $title .= $showQuestionTitleHtml ? '' : '<strong>';
266
        $checkIfShowNumberQuestion = $this->getShowHideConfiguration($exerciseId);
267
        if ($checkIfShowNumberQuestion != 1) {
268
            $title .= $itemNumber.'. ';
269
        }
270
        $title .= $this->selectTitle();
271
272
        $title .= $showQuestionTitleHtml ? '' : '</strong>';
273
274
        return Display::div(
275
            $title,
276
            ['class' => 'question_title']
277
        );
278
    }
279
280
    /**
281
     * Gets the respective value to show or hide the number of a question in the exam.
282
     * If the field does not exist in the database, it will return 0.
283
     *
284
     * @param int $exerciseId The iid of the corresponding c_quiz, to avoid mix-ups when the question is used in more than one exercise
285
     *
286
     * @return int 1 if we should hide the numbering for the current question
287
     */
288
    public function getShowHideConfiguration(int $exerciseId): int
289
    {
290
        $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
291
        $tblQuizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
292
        $showHideConfiguration = api_get_configuration_value('quiz_hide_question_number');
293
        if (!$showHideConfiguration) {
294
            return 0;
295
        }
296
        // Check if the field exist
297
        $checkFieldSql = "SHOW COLUMNS FROM $tblQuiz WHERE Field = 'hide_question_number'";
298
        $res = Database::query($checkFieldSql);
299
        $result = Database::store_result($res);
300
        if (count($result) != 0) {
301
            $sql = "
302
                SELECT
303
                    q.hide_question_number AS hide_num
304
                FROM
305
                    $tblQuiz as q
306
                INNER JOIN  $tblQuizRelQuestion AS qrq ON qrq.exercice_id = q.iid
307
                WHERE qrq.question_id = ".$this->iid."
308
                AND qrq.exercice_id = ".$exerciseId;
309
            $res = Database::query($sql);
310
            $result = Database::store_result($res);
311
            if (is_array($result) &&
312
                isset($result[0]) &&
313
                isset($result[0]['hide_num'])
314
            ) {
315
                return (int) $result[0]['hide_num'];
316
            }
317
        }
318
319
        return 0;
320
    }
321
322
    /**
323
     * returns the question description.
324
     *
325
     * @author Olivier Brouckaert
326
     *
327
     * @return string - question description
328
     */
329
    public function selectDescription()
330
    {
331
        return $this->description;
332
    }
333
334
    /**
335
     * returns the question weighting.
336
     *
337
     * @author Olivier Brouckaert
338
     *
339
     * @return int - question weighting
340
     */
341
    public function selectWeighting()
342
    {
343
        return $this->weighting;
344
    }
345
346
    /**
347
     * returns the question position.
348
     *
349
     * @author Olivier Brouckaert
350
     *
351
     * @return int - question position
352
     */
353
    public function selectPosition()
354
    {
355
        return $this->position;
356
    }
357
358
    /**
359
     * returns the answer type.
360
     *
361
     * @author Olivier Brouckaert
362
     *
363
     * @return int - answer type
364
     */
365
    public function selectType()
366
    {
367
        return $this->type;
368
    }
369
370
    /**
371
     * returns the level of the question.
372
     *
373
     * @author Nicolas Raynaud
374
     *
375
     * @return int - level of the question, 0 by default
376
     */
377
    public function getLevel()
378
    {
379
        return $this->level;
380
    }
381
382
    /**
383
     * returns the picture name.
384
     *
385
     * @author Olivier Brouckaert
386
     *
387
     * @return string - picture name
388
     */
389
    public function selectPicture()
390
    {
391
        return $this->picture;
392
    }
393
394
    /**
395
     * @return string
396
     */
397
    public function selectPicturePath()
398
    {
399
        if (!empty($this->picture)) {
400
            return api_get_path(WEB_COURSE_PATH).$this->course['directory'].'/document/images/'.$this->getPictureFilename();
401
        }
402
403
        return '';
404
    }
405
406
    /**
407
     * @return int|string
408
     */
409
    public function getPictureId()
410
    {
411
        // for backward compatibility
412
        // when in field picture we had the filename not the document id
413
        if (preg_match("/quiz-.*/", $this->picture)) {
414
            return DocumentManager::get_document_id(
415
                $this->course,
416
                $this->selectPicturePath(),
417
                api_get_session_id()
418
            );
419
        }
420
421
        return $this->picture;
422
    }
423
424
    /**
425
     * @param int $courseId
426
     * @param int $sessionId
427
     *
428
     * @return string
429
     */
430
    public function getPictureFilename($courseId = 0, $sessionId = 0)
431
    {
432
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
433
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
434
435
        if (empty($courseId)) {
436
            return '';
437
        }
438
        // for backward compatibility
439
        // when in field picture we had the filename not the document id
440
        if (preg_match("/quiz-.*/", $this->picture)) {
441
            return $this->picture;
442
        }
443
444
        $pictureId = $this->getPictureId();
445
        $courseInfo = $this->course;
446
        $documentInfo = DocumentManager::get_document_data_by_id(
447
            $pictureId,
448
            $courseInfo['code'],
449
            false,
450
            $sessionId
451
        );
452
        $documentFilename = '';
453
        if ($documentInfo) {
454
            // document in document/images folder
455
            $documentFilename = pathinfo(
456
                $documentInfo['path'],
457
                PATHINFO_BASENAME
458
            );
459
        }
460
461
        return $documentFilename;
462
    }
463
464
    /**
465
     * returns the array with the exercise ID list.
466
     *
467
     * @author Olivier Brouckaert
468
     *
469
     * @return array - list of exercise ID which the question is in
470
     */
471
    public function selectExerciseList()
472
    {
473
        return $this->exerciseList;
474
    }
475
476
    /**
477
     * returns the number of exercises which this question is in.
478
     *
479
     * @author Olivier Brouckaert
480
     *
481
     * @return int - number of exercises
482
     */
483
    public function selectNbrExercises()
484
    {
485
        return count($this->exerciseList);
486
    }
487
488
    /**
489
     * changes the question title.
490
     *
491
     * @param string $title - question title
492
     *
493
     * @author Olivier Brouckaert
494
     */
495
    public function updateTitle($title)
496
    {
497
        $this->question = $title;
498
    }
499
500
    /**
501
     * @param int $id
502
     */
503
    public function updateParentId($id)
504
    {
505
        $this->parent_id = (int) $id;
506
    }
507
508
    /**
509
     * changes the question description.
510
     *
511
     * @param string $description - question description
512
     *
513
     * @author Olivier Brouckaert
514
     */
515
    public function updateDescription($description)
516
    {
517
        $this->description = $description;
518
    }
519
520
    /**
521
     * changes the question weighting.
522
     *
523
     * @param int $weighting - question weighting
524
     *
525
     * @author Olivier Brouckaert
526
     */
527
    public function updateWeighting($weighting)
528
    {
529
        $this->weighting = $weighting;
530
    }
531
532
    /**
533
     * @param array $category
534
     *
535
     * @author Hubert Borderiou 12-10-2011
536
     */
537
    public function updateCategory($category)
538
    {
539
        $this->category = $category;
540
    }
541
542
    public function setMandatory($value)
543
    {
544
        $this->mandatory = (int) $value;
545
    }
546
547
    /**
548
     * @param int $value
549
     *
550
     * @author Hubert Borderiou 12-10-2011
551
     */
552
    public function updateScoreAlwaysPositive($value)
553
    {
554
        $this->scoreAlwaysPositive = $value;
555
    }
556
557
    /**
558
     * @param int $value
559
     *
560
     * @author Hubert Borderiou 12-10-2011
561
     */
562
    public function updateUncheckedMayScore($value)
563
    {
564
        $this->uncheckedMayScore = $value;
565
    }
566
567
    /**
568
     * Save category of a question.
569
     *
570
     * A question can have n categories if category is empty,
571
     * then question has no category then delete the category entry
572
     *
573
     * @param array $category_list
574
     *
575
     * @author Julio Montoya - Adding multiple cat support
576
     */
577
    public function saveCategories($category_list)
578
    {
579
        if (!empty($category_list)) {
580
            $this->deleteCategory();
581
            $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
582
583
            // update or add category for a question
584
            foreach ($category_list as $category_id) {
585
                $category_id = (int) $category_id;
586
                $question_id = (int) $this->iid;
587
                $sql = "SELECT count(*) AS nb
588
                        FROM $table
589
                        WHERE
590
                            category_id = $category_id
591
                            AND question_id = $question_id
592
                            AND c_id=".api_get_course_int_id();
593
                $res = Database::query($sql);
594
                $row = Database::fetch_array($res);
595
                if ($row['nb'] > 0) {
596
                    // DO nothing
597
                } else {
598
                    $sql = "INSERT INTO $table (c_id, question_id, category_id)
599
                            VALUES (".api_get_course_int_id().", $question_id, $category_id)";
600
                    Database::query($sql);
601
                }
602
            }
603
        }
604
    }
605
606
    /**
607
     * In this version, a question can only have 1 category.
608
     * If category is 0, then question has no category then delete the category entry.
609
     *
610
     * @param int $categoryId
611
     * @param int $courseId
612
     *
613
     * @return bool
614
     *
615
     * @author Hubert Borderiou 12-10-2011
616
     */
617
    public function saveCategory($categoryId, $courseId = 0)
618
    {
619
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
620
621
        if (empty($courseId)) {
622
            return false;
623
        }
624
625
        if ($categoryId <= 0) {
626
            $this->deleteCategory($courseId);
627
        } else {
628
            // update or add category for a question
629
            $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
630
            $categoryId = (int) $categoryId;
631
            $question_id = (int) $this->iid;
632
            $sql = "SELECT count(*) AS nb FROM $table
633
                    WHERE
634
                        question_id = $question_id AND
635
                        c_id = $courseId";
636
            $res = Database::query($sql);
637
            $row = Database::fetch_array($res);
638
            $allowMandatory = api_get_configuration_value('allow_mandatory_question_in_category');
639
            if ($row['nb'] > 0) {
640
                $extraMandatoryCondition = '';
641
                if ($allowMandatory) {
642
                    $extraMandatoryCondition = ", mandatory = {$this->mandatory}";
643
                }
644
                $sql = "UPDATE $table
645
                        SET category_id = $categoryId
646
                        $extraMandatoryCondition
647
                        WHERE
648
                            question_id = $question_id AND
649
                            c_id = $courseId";
650
                Database::query($sql);
651
            } else {
652
                $sql = "INSERT INTO $table (c_id, question_id, category_id)
653
                        VALUES ($courseId, $question_id, $categoryId)";
654
                Database::query($sql);
655
656
                if ($allowMandatory) {
657
                    $id = Database::insert_id();
658
                    if ($id) {
659
                        $sql = "UPDATE $table SET mandatory = {$this->mandatory}
660
                                WHERE iid = $id";
661
                        Database::query($sql);
662
                    }
663
                }
664
            }
665
666
            return true;
667
        }
668
    }
669
670
    /**
671
     * @author hubert borderiou 12-10-2011
672
     *
673
     * @param int $courseId
674
     *                      delete any category entry for question id
675
     *                      delete the category for question
676
     *
677
     * @return bool
678
     */
679
    public function deleteCategory($courseId = 0)
680
    {
681
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
682
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
683
        $questionId = (int) $this->iid;
684
        if (empty($courseId) || empty($questionId)) {
685
            return false;
686
        }
687
        $sql = "DELETE FROM $table
688
                WHERE
689
                    question_id = $questionId AND
690
                    c_id = $courseId";
691
        Database::query($sql);
692
693
        return true;
694
    }
695
696
    /**
697
     * changes the question position.
698
     *
699
     * @param int $position - question position
700
     *
701
     * @author Olivier Brouckaert
702
     */
703
    public function updatePosition($position)
704
    {
705
        $this->position = $position;
706
    }
707
708
    /**
709
     * changes the question level.
710
     *
711
     * @param int $level - question level
712
     *
713
     * @author Nicolas Raynaud
714
     */
715
    public function updateLevel($level)
716
    {
717
        $this->level = $level;
718
    }
719
720
    /**
721
     * changes the answer type. If the user changes the type from "unique answer" to "multiple answers"
722
     * (or conversely) answers are not deleted, otherwise yes.
723
     *
724
     * @param int $type - answer type
725
     *
726
     * @author Olivier Brouckaert
727
     */
728
    public function updateType($type)
729
    {
730
        $table = Database::get_course_table(TABLE_QUIZ_ANSWER);
731
732
        // if we really change the type
733
        if ($type != $this->type) {
734
            // if we don't change from "unique answer" to "multiple answers" (or conversely)
735
            if (!in_array($this->type, [UNIQUE_ANSWER, MULTIPLE_ANSWER]) ||
736
                !in_array($type, [UNIQUE_ANSWER, MULTIPLE_ANSWER])
737
            ) {
738
                // removes old answers
739
                $sql = "DELETE FROM $table
740
                        WHERE question_id = ".intval($this->iid);
741
                Database::query($sql);
742
            }
743
744
            $this->type = $type;
745
        }
746
    }
747
748
    /**
749
     * Get default hot spot folder in documents.
750
     *
751
     * @param array $courseInfo
752
     *
753
     * @return string
754
     */
755
    public function getHotSpotFolderInCourse($courseInfo = [])
756
    {
757
        $courseInfo = empty($courseInfo) ? $this->course : $courseInfo;
758
759
        if (empty($courseInfo) || empty($courseInfo['directory'])) {
760
            // Stop everything if course is not set.
761
            api_not_allowed();
762
        }
763
764
        $pictureAbsolutePath = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'].'/document/images/';
765
        $picturePath = basename($pictureAbsolutePath);
766
767
        if (!is_dir($picturePath)) {
768
            create_unexisting_directory(
769
                $courseInfo,
770
                api_get_user_id(),
771
                0,
772
                0,
773
                0,
774
                dirname($pictureAbsolutePath),
775
                '/'.$picturePath,
776
                $picturePath,
777
                '',
778
                false,
779
                false
780
            );
781
        }
782
783
        return $pictureAbsolutePath;
784
    }
785
786
    /**
787
     * adds a picture to the question.
788
     *
789
     * @param string $picture - temporary path of the picture to upload
790
     *
791
     * @return bool - true if uploaded, otherwise false
792
     *
793
     * @author Olivier Brouckaert
794
     */
795
    public function uploadPicture($picture)
796
    {
797
        $picturePath = $this->getHotSpotFolderInCourse();
798
799
        // if the question has got an ID
800
        if ($this->iid) {
801
            $pictureFilename = self::generatePictureName();
802
            $img = new Image($picture);
803
            $img->send_image($picturePath.'/'.$pictureFilename, -1, 'jpg');
804
            $document_id = add_document(
805
                $this->course,
806
                '/images/'.$pictureFilename,
807
                'file',
808
                filesize($picturePath.'/'.$pictureFilename),
809
                $pictureFilename
810
            );
811
812
            if ($document_id) {
813
                $this->picture = $document_id;
814
815
                if (!file_exists($picturePath.'/'.$pictureFilename)) {
816
                    return false;
817
                }
818
819
                api_item_property_update(
820
                    $this->course,
821
                    TOOL_DOCUMENT,
822
                    $document_id,
823
                    'DocumentAdded',
824
                    api_get_user_id()
825
                );
826
827
                $this->resizePicture('width', 800);
828
829
                return true;
830
            }
831
        }
832
833
        return false;
834
    }
835
836
    /**
837
     * return the name for image use in hotspot question
838
     * to be unique, name is quiz-[utc unix timestamp].jpg.
839
     *
840
     * @param string $prefix
841
     * @param string $extension
842
     *
843
     * @return string
844
     */
845
    public function generatePictureName($prefix = 'quiz-', $extension = 'jpg')
846
    {
847
        // image name is quiz-xxx.jpg in folder images/
848
        $utcTime = time();
849
850
        return $prefix.$utcTime.'.'.$extension;
851
    }
852
853
    /**
854
     * deletes the picture.
855
     *
856
     * @author Olivier Brouckaert
857
     *
858
     * @return bool - true if removed, otherwise false
859
     */
860
    public function removePicture()
861
    {
862
        $picturePath = $this->getHotSpotFolderInCourse();
863
864
        // if the question has got an ID and if the picture exists
865
        if ($this->iid) {
866
            $picture = $this->picture;
867
            $this->picture = '';
868
869
            return @unlink($picturePath.'/'.$picture) ? true : false;
870
        }
871
872
        return false;
873
    }
874
875
    /**
876
     * Exports a picture to another question.
877
     *
878
     * @author Olivier Brouckaert
879
     *
880
     * @param int   $questionId - ID of the target question
881
     * @param array $courseInfo destination course info
882
     *
883
     * @return bool - true if copied, otherwise false
884
     */
885
    public function exportPicture(int $questionId, array $courseInfo)
886
    {
887
        if (empty($questionId) || empty($courseInfo)) {
888
            return false;
889
        }
890
891
        $course_id = $courseInfo['real_id'];
892
        $destination_path = $this->getHotSpotFolderInCourse($courseInfo);
893
894
        if (empty($destination_path)) {
895
            return false;
896
        }
897
898
        $source_path = $this->getHotSpotFolderInCourse();
899
900
        // if the question has got an ID and if the picture exists
901
        if (!$this->iid || empty($this->picture)) {
902
            return false;
903
        }
904
905
        $sourcePictureName = $this->getPictureFilename($course_id);
906
        $picture = $this->generatePictureName();
907
        $result = false;
908
        if (file_exists($source_path.'/'.$sourcePictureName)) {
909
            // for backward compatibility
910
            $result = copy(
911
                $source_path.'/'.$sourcePictureName,
912
                $destination_path.'/'.$picture
913
            );
914
        } else {
915
            $imageInfo = DocumentManager::get_document_data_by_id(
916
                $this->picture,
917
                $courseInfo['code']
918
            );
919
            if (file_exists($imageInfo['absolute_path'])) {
920
                $result = @copy(
921
                    $imageInfo['absolute_path'],
922
                    $destination_path.'/'.$picture
923
                );
924
            }
925
        }
926
927
        // If copy was correct then add to the database
928
        if (!$result) {
929
            return false;
930
        }
931
932
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION);
933
        $sql = "UPDATE $table SET
934
                picture = '".Database::escape_string($picture)."'
935
                WHERE iid = ".intval($questionId);
936
        Database::query($sql);
937
938
        $documentId = add_document(
939
            $courseInfo,
940
            '/images/'.$picture,
941
            'file',
942
            filesize($destination_path.'/'.$picture),
943
            $picture
944
        );
945
946
        if (!$documentId) {
947
            return false;
948
        }
949
950
        return api_item_property_update(
951
            $courseInfo,
952
            TOOL_DOCUMENT,
953
            $documentId,
954
            'DocumentAdded',
955
            api_get_user_id()
956
        );
957
    }
958
959
    /**
960
     * Saves the picture coming from POST into a temporary file
961
     * Temporary pictures are used when we don't want to save a picture right after a form submission.
962
     * For example, if we first show a confirmation box.
963
     *
964
     * @author Olivier Brouckaert
965
     *
966
     * @param string $picture     - temporary path of the picture to move
967
     * @param string $pictureName - Name of the picture
968
     */
969
    public function setTmpPicture($picture, $pictureName)
970
    {
971
        $picturePath = $this->getHotSpotFolderInCourse();
972
        $pictureName = explode('.', $pictureName);
973
        $Extension = $pictureName[sizeof($pictureName) - 1];
974
975
        // saves the picture into a temporary file
976
        @move_uploaded_file($picture, $picturePath.'/tmp.'.$Extension);
977
    }
978
979
    /**
980
     * Set title.
981
     *
982
     * @param string $title
983
     */
984
    public function setTitle($title)
985
    {
986
        $this->question = $title;
987
    }
988
989
    /**
990
     * Sets extra info.
991
     *
992
     * @param string $extra
993
     */
994
    public function setExtra($extra)
995
    {
996
        $this->extra = $extra;
997
    }
998
999
    /**
1000
     * Updates the question in the database.
1001
     * if an exercise ID is provided, we add that exercise ID into the exercise list.
1002
     *
1003
     * @author Olivier Brouckaert
1004
     *
1005
     * @param Exercise $exercise
1006
     */
1007
    public function save($exercise)
1008
    {
1009
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1010
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1011
        $em = Database::getManager();
1012
        $exerciseId = $exercise->iid;
1013
1014
        $id = $this->iid;
1015
        $question = $this->question;
1016
        $description = $this->description;
1017
        $weighting = $this->weighting;
1018
        $position = $this->position;
1019
        $type = $this->type;
1020
        $picture = $this->picture;
1021
        $level = $this->level;
1022
        $extra = $this->extra;
1023
        $c_id = $this->course['real_id'];
1024
        $categoryId = $this->category;
1025
1026
        // question already exists
1027
        if (!empty($id)) {
1028
            $params = [
1029
                'question' => $question,
1030
                'description' => $description,
1031
                'ponderation' => $weighting,
1032
                'position' => $position,
1033
                'type' => $type,
1034
                'picture' => $picture,
1035
                'extra' => $extra,
1036
                'level' => $level,
1037
            ];
1038
            if ($exercise->questionFeedbackEnabled) {
1039
                $params['feedback'] = $this->feedback;
1040
            }
1041
1042
            Database::update(
1043
                $TBL_QUESTIONS,
1044
                $params,
1045
                ['iid = ?' => [$id]]
1046
            );
1047
1048
            Event::addEvent(
1049
                LOG_QUESTION_UPDATED,
1050
                LOG_QUESTION_ID,
1051
                $this->iid
1052
            );
1053
            $this->saveCategory($categoryId);
1054
1055
            if (!empty($exerciseId)) {
1056
                api_item_property_update(
1057
                    $this->course,
1058
                    TOOL_QUIZ,
1059
                    $id,
1060
                    'QuizQuestionUpdated',
1061
                    api_get_user_id()
1062
                );
1063
            }
1064
            if (api_get_setting('search_enabled') === 'true') {
1065
                $this->search_engine_edit($exerciseId);
1066
            }
1067
        } else {
1068
            // Creates a new question.
1069
            $sql = "SELECT max(position)
1070
                    FROM $TBL_QUESTIONS as question,
1071
                    $TBL_EXERCISE_QUESTION as test_question
1072
                    WHERE
1073
                        question.iid = test_question.question_id AND
1074
                        test_question.exercice_id = ".$exerciseId." AND
1075
                        test_question.c_id = $c_id ";
1076
            $result = Database::query($sql);
1077
            $current_position = Database::result($result, 0, 0);
1078
            $this->updatePosition($current_position + 1);
1079
            $position = $this->position;
1080
1081
            $params = [
1082
                'c_id' => $c_id,
1083
                'question' => $question,
1084
                'description' => $description,
1085
                'ponderation' => $weighting,
1086
                'position' => $position,
1087
                'type' => $type,
1088
                'picture' => $picture,
1089
                'extra' => $extra,
1090
                'level' => $level,
1091
            ];
1092
1093
            if ($exercise->questionFeedbackEnabled) {
1094
                $params['feedback'] = $this->feedback;
1095
            }
1096
            $this->iid = Database::insert($TBL_QUESTIONS, $params);
1097
1098
            if ($this->iid) {
1099
                Event::addEvent(
1100
                    LOG_QUESTION_CREATED,
1101
                    LOG_QUESTION_ID,
1102
                    $this->iid
1103
                );
1104
1105
                api_item_property_update(
1106
                    $this->course,
1107
                    TOOL_QUIZ,
1108
                    $this->iid,
1109
                    'QuizQuestionAdded',
1110
                    api_get_user_id()
1111
                );
1112
1113
                // If hotspot, create first answer
1114
                if (in_array($type, [HOT_SPOT, HOT_SPOT_COMBINATION, HOT_SPOT_ORDER])) {
1115
                    $quizAnswer = new CQuizAnswer();
1116
                    $quizAnswer
1117
                        ->setCId($c_id)
1118
                        ->setQuestionId($this->iid)
1119
                        ->setAnswer('')
1120
                        ->setPonderation(10)
1121
                        ->setPosition(1)
1122
                        ->setHotspotCoordinates('0;0|0|0')
1123
                        ->setHotspotType('square');
1124
1125
                    $em->persist($quizAnswer);
1126
                    $em->flush();
1127
1128
                    $id = $quizAnswer->getId();
1129
1130
                    if ($id) {
1131
                        $quizAnswer
1132
                            ->setId($id)
1133
                            ->setIdAuto($id);
1134
1135
                        $em->merge($quizAnswer);
1136
                        $em->flush();
1137
                    }
1138
                }
1139
1140
                if ($type == HOT_SPOT_DELINEATION) {
1141
                    $quizAnswer = new CQuizAnswer();
1142
                    $quizAnswer
1143
                        ->setCId($c_id)
1144
                        ->setQuestionId($this->iid)
1145
                        ->setAnswer('')
1146
                        ->setPonderation(10)
1147
                        ->setPosition(1)
1148
                        ->setHotspotCoordinates('0;0|0|0')
1149
                        ->setHotspotType('delineation');
1150
1151
                    $em->persist($quizAnswer);
1152
                    $em->flush();
1153
1154
                    $id = $quizAnswer->getId();
1155
1156
                    if ($id) {
1157
                        $quizAnswer
1158
                            ->setId($id)
1159
                            ->setIdAuto($id);
1160
1161
                        $em->merge($quizAnswer);
1162
                        $em->flush();
1163
                    }
1164
                }
1165
1166
                if (api_get_setting('search_enabled') === 'true') {
1167
                    $this->search_engine_edit($exerciseId, true);
1168
                }
1169
            }
1170
        }
1171
1172
        // if the question is created in an exercise
1173
        if (!empty($exerciseId)) {
1174
            // adds the exercise into the exercise list of this question
1175
            $this->addToList($exerciseId, true);
1176
        }
1177
    }
1178
1179
    /**
1180
     * @param int  $exerciseId
1181
     * @param bool $addQs
1182
     * @param bool $rmQs
1183
     */
1184
    public function search_engine_edit(
1185
        $exerciseId,
1186
        $addQs = false,
1187
        $rmQs = false
1188
    ) {
1189
        // update search engine and its values table if enabled
1190
        if (!empty($exerciseId) && api_get_setting('search_enabled') == 'true' &&
1191
            extension_loaded('xapian')
1192
        ) {
1193
            $course_id = api_get_course_id();
1194
            // get search_did
1195
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1196
            if ($addQs || $rmQs) {
1197
                //there's only one row per question on normal db and one document per question on search engine db
1198
                $sql = 'SELECT * FROM %s
1199
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=%s LIMIT 1';
1200
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->iid);
1201
            } else {
1202
                $sql = 'SELECT * FROM %s
1203
                    WHERE course_code=\'%s\' AND tool_id=\'%s\'
1204
                    AND ref_id_high_level=%s AND ref_id_second_level=%s LIMIT 1';
1205
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->iid);
1206
            }
1207
            $res = Database::query($sql);
1208
1209
            if (Database::num_rows($res) > 0 || $addQs) {
1210
                $di = new ChamiloIndexer();
1211
                if ($addQs) {
1212
                    $question_exercises = [(int) $exerciseId];
1213
                } else {
1214
                    $question_exercises = [];
1215
                }
1216
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
1217
                $di->connectDb(null, null, $lang);
1218
1219
                // retrieve others exercise ids
1220
                $se_ref = Database::fetch_array($res);
1221
                $se_doc = $di->get_document((int) $se_ref['search_did']);
1222
                if ($se_doc !== false) {
1223
                    if (($se_doc_data = $di->get_document_data($se_doc)) !== false) {
1224
                        $se_doc_data = UnserializeApi::unserialize(
1225
                            'not_allowed_classes',
1226
                            $se_doc_data
1227
                        );
1228
                        if (isset($se_doc_data[SE_DATA]['type']) &&
1229
                            $se_doc_data[SE_DATA]['type'] == SE_DOCTYPE_EXERCISE_QUESTION
1230
                        ) {
1231
                            if (isset($se_doc_data[SE_DATA]['exercise_ids']) &&
1232
                                is_array($se_doc_data[SE_DATA]['exercise_ids'])
1233
                            ) {
1234
                                foreach ($se_doc_data[SE_DATA]['exercise_ids'] as $old_value) {
1235
                                    if (!in_array($old_value, $question_exercises)) {
1236
                                        $question_exercises[] = $old_value;
1237
                                    }
1238
                                }
1239
                            }
1240
                        }
1241
                    }
1242
                }
1243
                if ($rmQs) {
1244
                    while (($key = array_search($exerciseId, $question_exercises)) !== false) {
1245
                        unset($question_exercises[$key]);
1246
                    }
1247
                }
1248
1249
                // build the chunk to index
1250
                $ic_slide = new IndexableChunk();
1251
                $ic_slide->addValue('title', $this->question);
1252
                $ic_slide->addCourseId($course_id);
1253
                $ic_slide->addToolId(TOOL_QUIZ);
1254
                $xapian_data = [
1255
                    SE_COURSE_ID => $course_id,
1256
                    SE_TOOL_ID => TOOL_QUIZ,
1257
                    SE_DATA => [
1258
                        'type' => SE_DOCTYPE_EXERCISE_QUESTION,
1259
                        'exercise_ids' => $question_exercises,
1260
                        'question_id' => (int) $this->iid,
1261
                    ],
1262
                    SE_USER => (int) api_get_user_id(),
1263
                ];
1264
                $ic_slide->xapian_data = serialize($xapian_data);
1265
                $ic_slide->addValue('content', $this->description);
1266
1267
                //TODO: index answers, see also form validation on question_admin.inc.php
1268
1269
                $di->remove_document($se_ref['search_did']);
1270
                $di->addChunk($ic_slide);
1271
1272
                //index and return search engine document id
1273
                if (!empty($question_exercises)) { // if empty there is nothing to index
1274
                    $did = $di->index();
1275
                    unset($di);
1276
                }
1277
                if ($did || $rmQs) {
1278
                    // save it to db
1279
                    if ($addQs || $rmQs) {
1280
                        $sql = "DELETE FROM %s
1281
                            WHERE course_code = '%s' AND tool_id = '%s' AND ref_id_second_level = '%s'";
1282
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->iid);
1283
                    } else {
1284
                        $sql = "DELETE FROM %S
1285
                            WHERE
1286
                                course_code = '%s'
1287
                                AND tool_id = '%s'
1288
                                AND tool_id = '%s'
1289
                                AND ref_id_high_level = '%s'
1290
                                AND ref_id_second_level = '%s'";
1291
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->iid);
1292
                    }
1293
                    Database::query($sql);
1294
                    if ($rmQs) {
1295
                        if (!empty($question_exercises)) {
1296
                            $sql = "INSERT INTO %s (
1297
                                    id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
1298
                                )
1299
                                VALUES (
1300
                                    NULL, '%s', '%s', %s, %s, %s
1301
                                )";
1302
                            $sql = sprintf(
1303
                                $sql,
1304
                                $tbl_se_ref,
1305
                                $course_id,
1306
                                TOOL_QUIZ,
1307
                                array_shift($question_exercises),
1308
                                $this->iid,
1309
                                $did
1310
                            );
1311
                            Database::query($sql);
1312
                        }
1313
                    } else {
1314
                        $sql = "INSERT INTO %s (
1315
                                id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
1316
                            )
1317
                            VALUES (
1318
                                NULL , '%s', '%s', %s, %s, %s
1319
                            )";
1320
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->iid, $did);
1321
                        Database::query($sql);
1322
                    }
1323
                }
1324
            }
1325
        }
1326
    }
1327
1328
    /**
1329
     * adds an exercise into the exercise list.
1330
     *
1331
     * @author Olivier Brouckaert
1332
     *
1333
     * @param int  $exerciseId - exercise ID
1334
     * @param bool $fromSave   - from $this->save() or not
1335
     */
1336
    public function addToList($exerciseId, $fromSave = false)
1337
    {
1338
        $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1339
        $id = (int) $this->iid;
1340
        $exerciseId = (int) $exerciseId;
1341
1342
        // checks if the exercise ID is not in the list
1343
        if (!empty($exerciseId) && !in_array($exerciseId, $this->exerciseList)) {
1344
            $this->exerciseList[] = $exerciseId;
1345
            $courseId = isset($this->course['real_id']) ? $this->course['real_id'] : 0;
1346
            $newExercise = new Exercise($courseId);
1347
            $newExercise->read($exerciseId, false);
1348
            $count = $newExercise->getQuestionCount();
1349
            $count++;
1350
            $sql = "INSERT INTO $exerciseRelQuestionTable (c_id, question_id, exercice_id, question_order)
1351
                    VALUES ({$this->course['real_id']}, $id, $exerciseId, $count)";
1352
            Database::query($sql);
1353
1354
            // we do not want to reindex if we had just saved adnd indexed the question
1355
            if (!$fromSave) {
1356
                $this->search_engine_edit($exerciseId, true);
1357
            }
1358
        }
1359
    }
1360
1361
    /**
1362
     * removes an exercise from the exercise list.
1363
     *
1364
     * @author Olivier Brouckaert
1365
     *
1366
     * @param int $exerciseId - exercise ID
1367
     * @param int $courseId   The ID of the course, to avoid deleting re-used questions
1368
     *
1369
     * @return bool - true if removed, otherwise false
1370
     */
1371
    public function removeFromList(int $exerciseId, int $courseId = 0): bool
1372
    {
1373
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1374
        $id = (int) $this->iid;
1375
        $exerciseId = (int) $exerciseId;
1376
1377
        // searches the position of the exercise ID in the list
1378
        $pos = array_search($exerciseId, $this->exerciseList);
1379
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
1380
1381
        // exercise not found
1382
        if (false === $pos) {
1383
            return false;
1384
        } else {
1385
            // deletes the position in the array containing the wanted exercise ID
1386
            unset($this->exerciseList[$pos]);
1387
            //update order of other elements
1388
            $sql = "SELECT question_order
1389
                    FROM $table
1390
                    WHERE
1391
                        c_id = $courseId AND
1392
                        question_id = $id AND
1393
                        exercice_id = $exerciseId";
1394
            $res = Database::query($sql);
1395
            if (Database::num_rows($res) > 0) {
1396
                $row = Database::fetch_array($res);
1397
                if (!empty($row['question_order'])) {
1398
                    $sql = "UPDATE $table
1399
                            SET question_order = question_order-1
1400
                            WHERE
1401
                                c_id = $courseId AND
1402
                                exercice_id = $exerciseId AND
1403
                                question_order > ".$row['question_order'];
1404
                    Database::query($sql);
1405
                }
1406
            }
1407
1408
            $sql = "DELETE FROM $table
1409
                    WHERE
1410
                        c_id = $courseId AND
1411
                        question_id = $id AND
1412
                        exercice_id = $exerciseId";
1413
            Database::query($sql);
1414
1415
            return true;
1416
        }
1417
    }
1418
1419
    /**
1420
     * Deletes a question from the database
1421
     * The parameter tells if the question is removed from all exercises (value = 0),
1422
     * or just from one exercise (value = exercise ID).
1423
     *
1424
     * @author Olivier Brouckaert
1425
     *
1426
     * @param int  $deleteFromEx  Exercise ID if the question is only to be removed from one exercise
1427
     * @param bool $deletePicture Allow for special cases where the picture would be better left alone
1428
     */
1429
    public function delete(int $deleteFromEx = 0, bool $deletePicture = true): bool
1430
    {
1431
        if (empty($this->course)) {
1432
            return false;
1433
        }
1434
1435
        $courseId = $this->course['real_id'];
1436
1437
        if (empty($courseId)) {
1438
            return false;
1439
        }
1440
1441
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1442
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1443
        $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
1444
        $TBL_QUIZ_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
1445
1446
        $id = (int) $this->iid;
1447
1448
        // if the question must be removed from all exercises
1449
        if (!$deleteFromEx) {
1450
            $courseFilter = " AND c_id = $courseId";
1451
1452
            if (true === api_get_configuration_value('quiz_question_allow_inter_course_linking')) {
1453
                $courseFilter = '';
1454
            }
1455
1456
            //update the question_order of each question to avoid inconsistencies
1457
            $sql = "SELECT exercice_id, question_order
1458
                    FROM $TBL_EXERCISE_QUESTION
1459
                    WHERE question_id = $id
1460
                        $courseFilter";
1461
1462
            $res = Database::query($sql);
1463
            if (Database::num_rows($res) > 0) {
1464
                while ($row = Database::fetch_array($res)) {
1465
                    if (!empty($row['question_order'])) {
1466
                        $sql = "UPDATE $TBL_EXERCISE_QUESTION
1467
                                SET question_order = question_order-1
1468
                                WHERE
1469
                                    exercice_id = ".intval($row['exercice_id'])." AND
1470
                                    question_order > ".$row['question_order']
1471
                                    .$courseFilter;
1472
                        Database::query($sql);
1473
                    }
1474
                }
1475
            }
1476
1477
            $sql = "DELETE FROM $TBL_EXERCISE_QUESTION
1478
                    WHERE question_id = $id
1479
                        $courseFilter";
1480
            Database::query($sql);
1481
1482
            $sql = "DELETE FROM $TBL_QUESTIONS
1483
                    WHERE iid = ".$id;
1484
            Database::query($sql);
1485
1486
            $sql = "DELETE FROM $TBL_REPONSES
1487
                    WHERE question_id = ".$id;
1488
            Database::query($sql);
1489
1490
            // remove the category of this question in the question_rel_category table
1491
            $sql = "DELETE FROM $TBL_QUIZ_QUESTION_REL_CATEGORY
1492
                    WHERE
1493
                        question_id = $id
1494
                        $courseFilter";
1495
            Database::query($sql);
1496
1497
            // Add extra fields.
1498
            $extraField = new ExtraFieldValue('question');
1499
            $extraField->deleteValuesByItem($this->iid);
1500
1501
            $sql = "DELETE FROM $TBL_QUESTIONS
1502
                    WHERE iid = $id";
1503
            Database::query($sql);
1504
1505
            api_item_property_update(
1506
                $this->course,
1507
                TOOL_QUIZ,
1508
                $id,
1509
                'QuizQuestionDeleted',
1510
                api_get_user_id()
1511
            );
1512
            Event::addEvent(
1513
                LOG_QUESTION_DELETED,
1514
                LOG_QUESTION_ID,
1515
                $this->iid
1516
            );
1517
            if ($deletePicture) {
1518
                $this->removePicture();
1519
            }
1520
        } else {
1521
            // just removes the exercise from the list
1522
            $this->removeFromList($deleteFromEx, $courseId);
1523
            if (api_get_setting('search_enabled') === 'true' && extension_loaded('xapian')) {
1524
                // disassociate question with this exercise
1525
                $this->search_engine_edit($deleteFromEx, false, true);
1526
            }
1527
1528
            api_item_property_update(
1529
                $this->course,
1530
                TOOL_QUIZ,
1531
                $id,
1532
                'QuizQuestionDeleted',
1533
                api_get_user_id()
1534
            );
1535
            Event::addEvent(
1536
                LOG_QUESTION_REMOVED_FROM_QUIZ,
1537
                LOG_QUESTION_ID,
1538
                $this->iid
1539
            );
1540
        }
1541
1542
        return true;
1543
    }
1544
1545
    /**
1546
     * Duplicates the question.
1547
     *
1548
     * @author Olivier Brouckaert
1549
     *
1550
     * @param array $courseInfo Course info of the destination course
1551
     *
1552
     * @return false|int ID of the new question
1553
     */
1554
    public function duplicate($courseInfo = [])
1555
    {
1556
        $courseInfo = empty($courseInfo) ? $this->course : $courseInfo;
1557
1558
        if (empty($courseInfo)) {
1559
            return false;
1560
        }
1561
        $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION);
1562
        $TBL_QUESTION_OPTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1563
1564
        $question = $this->question;
1565
        $description = $this->description;
1566
        $weighting = $this->weighting;
1567
        $position = $this->position;
1568
        $type = $this->type;
1569
        $level = (int) $this->level;
1570
        $extra = $this->extra;
1571
1572
        // Using the same method used in the course copy to transform URLs
1573
        if ($this->course['id'] != $courseInfo['id']) {
1574
            $description = DocumentManager::replaceUrlWithNewCourseCode(
1575
                $description,
1576
                $this->course['code'],
1577
                $courseInfo['id']
1578
            );
1579
            $question = DocumentManager::replaceUrlWithNewCourseCode(
1580
                $question,
1581
                $this->course['code'],
1582
                $courseInfo['id']
1583
            );
1584
        }
1585
1586
        $course_id = $courseInfo['real_id'];
1587
1588
        // Read the source options
1589
        $options = self::readQuestionOption($this->iid, $this->course['real_id']);
1590
1591
        // Inserting in the new course db / or the same course db
1592
        $params = [
1593
            'c_id' => $course_id,
1594
            'question' => $question,
1595
            'description' => $description,
1596
            'ponderation' => $weighting,
1597
            'position' => $position,
1598
            'type' => $type,
1599
            'level' => $level,
1600
            'extra' => $extra,
1601
        ];
1602
        $newQuestionId = Database::insert($questionTable, $params);
1603
1604
        if ($newQuestionId) {
1605
            // Add extra fields.
1606
            $extraField = new ExtraFieldValue('question');
1607
            $extraField->copy($this->iid, $newQuestionId);
1608
1609
            if (!empty($options)) {
1610
                // Saving the quiz_options
1611
                foreach ($options as $item) {
1612
                    $item['question_id'] = $newQuestionId;
1613
                    $item['c_id'] = $course_id;
1614
                    unset($item['iid']);
1615
                    unset($item['id']);
1616
                    $id = Database::insert($TBL_QUESTION_OPTIONS, $item);
1617
                }
1618
            }
1619
1620
            // Duplicates the picture of the hotspot
1621
            $this->exportPicture($newQuestionId, $courseInfo);
1622
        }
1623
1624
        Event::addEvent(
1625
            LOG_QUESTION_CREATED,
1626
            LOG_QUESTION_ID,
1627
            $newQuestionId
1628
        );
1629
1630
        return $newQuestionId;
1631
    }
1632
1633
    /**
1634
     * @return string
1635
     */
1636
    public function get_question_type_name()
1637
    {
1638
        $key = self::$questionTypes[$this->type];
1639
1640
        return get_lang($key[1]);
1641
    }
1642
1643
    /**
1644
     * @param string $type
1645
     */
1646
    public static function get_question_type($type)
1647
    {
1648
        if ($type == ORAL_EXPRESSION && api_get_setting('enable_record_audio') !== 'true') {
1649
            return null;
1650
        }
1651
1652
        return self::$questionTypes[$type];
1653
    }
1654
1655
    /**
1656
     * @return array
1657
     */
1658
    public static function getQuestionTypeList()
1659
    {
1660
        if ('true' !== api_get_setting('enable_record_audio')) {
1661
            self::$questionTypes[ORAL_EXPRESSION] = null;
1662
            unset(self::$questionTypes[ORAL_EXPRESSION]);
1663
        }
1664
        if ('true' !== api_get_setting('enable_quiz_scenario')) {
1665
            self::$questionTypes[HOT_SPOT_DELINEATION] = null;
1666
            unset(self::$questionTypes[HOT_SPOT_DELINEATION]);
1667
        }
1668
        if ('true' !== OnlyofficePlugin::create()->get('enable_onlyoffice_plugin')) {
1669
            unset(self::$questionTypes[ANSWER_IN_OFFICE_DOC]);
1670
        }
1671
1672
        return self::$questionTypes;
1673
    }
1674
1675
    /**
1676
     * Returns an instance of the class corresponding to the type.
1677
     *
1678
     * @param int $type the type of the question
1679
     *
1680
     * @return $this instance of a Question subclass (or of Questionc class by default)
1681
     */
1682
    public static function getInstance($type)
1683
    {
1684
        if (!is_null($type)) {
1685
            list($fileName, $className) = self::get_question_type($type);
1686
            if (!empty($fileName)) {
1687
                include_once $fileName;
1688
                if (class_exists($className)) {
1689
                    return new $className();
1690
                } else {
1691
                    echo 'Can\'t instanciate class '.$className.' of type '.$type;
1692
                }
1693
            }
1694
        }
1695
1696
        return null;
1697
    }
1698
1699
    /**
1700
     * Creates the form to create / edit a question
1701
     * A subclass can redefine this function to add fields...
1702
     *
1703
     * @param FormValidator $form
1704
     * @param Exercise      $exercise
1705
     */
1706
    public function createForm(&$form, $exercise)
1707
    {
1708
        echo '<style>
1709
                .media { display:none;}
1710
            </style>';
1711
1712
        $zoomOptions = api_get_configuration_value('quiz_image_zoom');
1713
        if (isset($zoomOptions['options'])) {
1714
            $finderFolder = api_get_path(WEB_PATH).'vendor/studio-42/elfinder/';
1715
            echo '<!-- elFinder CSS (REQUIRED) -->';
1716
            echo '<link rel="stylesheet" type="text/css" media="screen" href="'.$finderFolder.'css/elfinder.full.css">';
1717
            echo '<link rel="stylesheet" type="text/css" media="screen" href="'.$finderFolder.'css/theme.css">';
1718
1719
            echo '<!-- elFinder JS (REQUIRED) -->';
1720
            echo '<script type="text/javascript" src="'.$finderFolder.'js/elfinder.full.js"></script>';
1721
1722
            echo '<!-- elFinder translation (OPTIONAL) -->';
1723
            $language = 'en';
1724
            $platformLanguage = api_get_interface_language();
1725
            $iso = api_get_language_isocode($platformLanguage);
1726
            $filePart = "vendor/studio-42/elfinder/js/i18n/elfinder.$iso.js";
1727
            $file = api_get_path(SYS_PATH).$filePart;
1728
            $includeFile = '';
1729
            if (file_exists($file)) {
1730
                $includeFile = '<script type="text/javascript" src="'.api_get_path(WEB_PATH).$filePart.'"></script>';
1731
                $language = $iso;
1732
            }
1733
            echo $includeFile;
1734
1735
            echo '<script type="text/javascript" charset="utf-8">
1736
            $(function() {
1737
                $(".create_img_link").click(function(e){
1738
                    e.preventDefault();
1739
                    e.stopPropagation();
1740
                    var imageZoom = $("input[name=\'imageZoom\']").val();
1741
                    var imageWidth = $("input[name=\'imageWidth\']").val();
1742
                    CKEDITOR.instances.questionDescription.insertHtml(\'<img id="zoom_picture" class="zoom_picture" src="\'+imageZoom+\'" data-zoom-image="\'+imageZoom+\'" width="\'+imageWidth+\'px" />\');
1743
                });
1744
1745
                $("input[name=\'imageZoom\']").on("click", function(){
1746
                    var elf = $("#elfinder").elfinder({
1747
                        url : "'.api_get_path(WEB_LIBRARY_PATH).'elfinder/connectorAction.php?'.api_get_cidreq().'",
1748
                        getFileCallback: function(file) {
1749
                            var filePath = file; //file contains the relative url.
1750
                            var imgPath = "<img src = \'"+filePath+"\'/>";
1751
                            $("input[name=\'imageZoom\']").val(filePath.url);
1752
                            $("#elfinder").remove(); //close the window after image is selected
1753
                        },
1754
                        startPathHash: "l2_Lw", // Sets the course driver as default
1755
                        resizable: false,
1756
                        lang: "'.$language.'"
1757
                    }).elfinder("instance");
1758
                });
1759
            });
1760
            </script>';
1761
            echo '<div id="elfinder"></div>';
1762
        }
1763
1764
        // question name
1765
        if (api_get_configuration_value('save_titles_as_html')) {
1766
            $editorConfig = ['ToolbarSet' => 'TitleAsHtml'];
1767
            $form->addHtmlEditor(
1768
                'questionName',
1769
                get_lang('Question'),
1770
                false,
1771
                false,
1772
                $editorConfig,
1773
                true
1774
            );
1775
        } else {
1776
            $form->addElement('text', 'questionName', get_lang('Question'));
1777
        }
1778
1779
        $form->addRule('questionName', get_lang('GiveQuestion'), 'required');
1780
1781
        // default content
1782
        $isContent = isset($_REQUEST['isContent']) ? (int) $_REQUEST['isContent'] : null;
1783
1784
        // Question type
1785
        $answerType = isset($_REQUEST['answerType']) ? (int) $_REQUEST['answerType'] : null;
1786
        $form->addElement('hidden', 'answerType', $answerType);
1787
1788
        // html editor
1789
        $editorConfig = [
1790
            'ToolbarSet' => 'TestQuestionDescription',
1791
            'Height' => '150',
1792
        ];
1793
1794
        if (!api_is_allowed_to_edit(null, true)) {
1795
            $editorConfig['UserStatus'] = 'student';
1796
        }
1797
1798
        $form->addButtonAdvancedSettings('advanced_params');
1799
1800
        $displayAdvancedParamsOptions = api_get_configuration_value('quiz_question_edit_open_advanced_params_by_default') ? 'block' : 'none';
1801
        $form->addHtml('<div id="advanced_params_options" style="display:'.$displayAdvancedParamsOptions.'">');
1802
1803
        if (isset($zoomOptions['options'])) {
1804
            $form->addElement('text', 'imageZoom', get_lang('ImageURL'));
1805
            $form->addElement('text', 'imageWidth', get_lang('PixelWidth'));
1806
            $form->addButton('btn_create_img', get_lang('AddToEditor'), 'plus', 'info', 'small', 'create_img_link');
1807
        }
1808
1809
        $form->addHtmlEditor(
1810
            'questionDescription',
1811
            get_lang('QuestionDescription'),
1812
            false,
1813
            false,
1814
            $editorConfig
1815
        );
1816
1817
        if ($this->type != MEDIA_QUESTION) {
1818
            // Advanced parameters.
1819
            $form->addElement(
1820
                'select',
1821
                'questionLevel',
1822
                get_lang('Difficulty'),
1823
                self::get_default_levels()
1824
            );
1825
1826
            // Categories.
1827
            $form->addElement(
1828
                'select',
1829
                'questionCategory',
1830
                get_lang('Category'),
1831
                TestCategory::getCategoriesIdAndName()
1832
            );
1833
1834
            if (EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM == $exercise->getQuestionSelectionType() &&
1835
                api_get_configuration_value('allow_mandatory_question_in_category')
1836
            ) {
1837
                $form->addCheckBox(
1838
                    'mandatory',
1839
                    get_lang('IsMandatory')
1840
                );
1841
            }
1842
1843
            global $text;
1844
            switch ($this->type) {
1845
                case UNIQUE_ANSWER:
1846
                case MULTIPLE_ANSWER_DROPDOWN:
1847
                case MULTIPLE_ANSWER_DROPDOWN_COMBINATION:
1848
                    $buttonGroup = [];
1849
                    $buttonGroup[] = $form->addButtonSave(
1850
                        $text,
1851
                        'submitQuestion',
1852
                        true
1853
                    );
1854
                    $buttonGroup[] = $form->addButton(
1855
                        'convertAnswer',
1856
                        get_lang('ConvertToMultipleAnswer'),
1857
                        'check-square-o',
1858
                        'default',
1859
                        null,
1860
                        null,
1861
                        null,
1862
                        true
1863
                    );
1864
                    $form->addGroup($buttonGroup);
1865
                    break;
1866
                case MULTIPLE_ANSWER:
1867
                    $buttonGroup = [];
1868
                    $buttonGroup[] = $form->addButtonSave(
1869
                        $text,
1870
                        'submitQuestion',
1871
                        true
1872
                    );
1873
                    $buttonGroup[] = $form->addButton(
1874
                        'convertAnswer',
1875
                        get_lang('ConvertToUniqueAnswer'),
1876
                        'dot-circle-o',
1877
                        'default',
1878
                        null,
1879
                        null,
1880
                        null,
1881
                        true
1882
                    );
1883
                    $buttonGroup[] = $form->addButton(
1884
                        'convertAnswerAlt',
1885
                        get_lang('ConvertToMultipleAnswerDropdown'),
1886
                        'check-square-o',
1887
                        'default',
1888
                        null,
1889
                        null,
1890
                        null,
1891
                        true
1892
                    );
1893
                    $form->addGroup($buttonGroup);
1894
                    break;
1895
            }
1896
            //Medias
1897
            //$course_medias = self::prepare_course_media_select(api_get_course_int_id());
1898
            //$form->addElement('select', 'parent_id', get_lang('AttachToMedia'), $course_medias);
1899
        }
1900
1901
        $form->addElement('html', '</div>');
1902
1903
        if (!isset($_GET['fromExercise'])) {
1904
            switch ($answerType) {
1905
                case 1:
1906
                    $this->question = get_lang('DefaultUniqueQuestion');
1907
                    break;
1908
                case 2:
1909
                    $this->question = get_lang('DefaultMultipleQuestion');
1910
                    break;
1911
                case 3:
1912
                    $this->question = get_lang('DefaultFillBlankQuestion');
1913
                    break;
1914
                case 4:
1915
                    $this->question = get_lang('DefaultMathingQuestion');
1916
                    break;
1917
                case 5:
1918
                    $this->question = get_lang('DefaultOpenQuestion');
1919
                    break;
1920
                case 9:
1921
                    $this->question = get_lang('DefaultMultipleQuestion');
1922
                    break;
1923
            }
1924
        }
1925
1926
        if (!is_null($exercise)) {
1927
            if ($exercise->questionFeedbackEnabled && $this->showFeedback($exercise)) {
1928
                $form->addTextarea('feedback', get_lang('FeedbackIfNotCorrect'));
1929
            }
1930
        }
1931
1932
        $extraField = new ExtraField('question');
1933
        $extraField->addElements($form, $this->iid);
1934
1935
        // default values
1936
1937
        // Came from he question pool
1938
        if (isset($_GET['fromExercise'])
1939
            || (!isset($_GET['newQuestion']) || $isContent)
1940
        ) {
1941
            try {
1942
                $form->getElement('questionName')->setValue($this->question);
1943
            } catch (Exception $exception) {
1944
            }
1945
1946
            try {
1947
                $form->getElement('questionDescription')->setValue($this->description);
1948
            } catch (Exception $e) {
1949
            }
1950
1951
            try {
1952
                $form->getElement('questionLevel')->setValue($this->level);
1953
            } catch (Exception $e) {
1954
            }
1955
1956
            try {
1957
                $form->getElement('questionCategory')->setValue($this->category);
1958
            } catch (Exception $e) {
1959
            }
1960
1961
            try {
1962
                $form->getElement('feedback')->setValue($this->feedback);
1963
            } catch (Exception $e) {
1964
            }
1965
1966
            try {
1967
                $form->getElement('mandatory')->setValue($this->mandatory);
1968
            } catch (Exception $e) {
1969
            }
1970
        }
1971
1972
        /*if (!empty($_REQUEST['myid'])) {
1973
            $form->setDefaults($defaults);
1974
        } else {
1975
            if ($isContent == 1) {
1976
                $form->setDefaults($defaults);
1977
            }
1978
        }*/
1979
    }
1980
1981
    /**
1982
     * function which process the creation of questions.
1983
     *
1984
     * @param FormValidator $form
1985
     * @param Exercise      $exercise
1986
     */
1987
    public function processCreation($form, $exercise)
1988
    {
1989
        $this->updateTitle($form->getSubmitValue('questionName'));
1990
        $this->updateDescription($form->getSubmitValue('questionDescription'));
1991
        $this->updateLevel($form->getSubmitValue('questionLevel'));
1992
        $this->updateCategory($form->getSubmitValue('questionCategory'));
1993
        $this->setMandatory($form->getSubmitValue('mandatory'));
1994
        $this->setFeedback($form->getSubmitValue('feedback'));
1995
1996
        //Save normal question if NOT media
1997
        if (MEDIA_QUESTION != $this->type) {
1998
            $creationMode = empty($this->iid);
1999
            $this->save($exercise);
2000
            $exercise->addToList($this->iid);
2001
2002
            // Only update position in creation and when using ordered or random types.
2003
            if ($creationMode &&
2004
                in_array($exercise->questionSelectionType, [EX_Q_SELECTION_ORDERED, EX_Q_SELECTION_RANDOM])
2005
            ) {
2006
                $exercise->update_question_positions();
2007
            }
2008
2009
            $params = $form->exportValues();
2010
            $params['item_id'] = $this->iid;
2011
2012
            $extraFieldValues = new ExtraFieldValue('question');
2013
            $extraFieldValues->saveFieldValues($params);
2014
        }
2015
    }
2016
2017
    /**
2018
     * abstract function which creates the form to create / edit the answers of the question.
2019
     */
2020
    abstract public function createAnswersForm($form);
2021
2022
    /**
2023
     * abstract function which process the creation of answers.
2024
     *
2025
     * @param FormValidator $form
2026
     * @param Exercise      $exercise
2027
     */
2028
    abstract public function processAnswersCreation($form, $exercise);
2029
2030
    /**
2031
     * Displays the menu of question types.
2032
     *
2033
     * @param Exercise $objExercise
2034
     */
2035
    public static function displayTypeMenu($objExercise)
2036
    {
2037
        if (empty($objExercise)) {
2038
            return '';
2039
        }
2040
2041
        $feedbackType = $objExercise->getFeedbackType();
2042
        $exerciseId = $objExercise->iid;
2043
2044
        // 1. by default we show all the question types
2045
        $questionTypeList = self::getQuestionTypeList();
2046
2047
        if (!isset($feedbackType)) {
2048
            $feedbackType = 0;
2049
        }
2050
2051
        switch ($feedbackType) {
2052
            case EXERCISE_FEEDBACK_TYPE_DIRECT:
2053
                $questionTypeList = [
2054
                    UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
2055
                    HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION],
2056
                ];
2057
                break;
2058
            case EXERCISE_FEEDBACK_TYPE_POPUP:
2059
                $questionTypeList = [
2060
                    UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
2061
                    MULTIPLE_ANSWER => self::$questionTypes[MULTIPLE_ANSWER],
2062
                    DRAGGABLE => self::$questionTypes[DRAGGABLE],
2063
                    HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION],
2064
                    CALCULATED_ANSWER => self::$questionTypes[CALCULATED_ANSWER],
2065
                ];
2066
                break;
2067
            default:
2068
                unset($questionTypeList[HOT_SPOT_DELINEATION]);
2069
                break;
2070
        }
2071
2072
        echo '<div class="panel panel-default">';
2073
        echo '<div class="panel-body">';
2074
        echo '<ul class="question_menu">';
2075
        foreach ($questionTypeList as $i => $type) {
2076
            /** @var Question $type */
2077
            $type = new $type[1]();
2078
            $img = $type->getTypePicture();
2079
            $explanation = get_lang($type->getExplanation());
2080
            echo '<li>';
2081
            echo '<div class="icon-image">';
2082
            $icon = '<a href="admin.php?'.api_get_cidreq().'&newQuestion=yes&answerType='.$i.'">'.
2083
                Display::return_icon($img, $explanation, null, ICON_SIZE_BIG).'</a>';
2084
2085
            if ($objExercise->force_edit_exercise_in_lp === false) {
2086
                if ($objExercise->exercise_was_added_in_lp == true) {
2087
                    $img = pathinfo($img);
2088
                    $img = $img['filename'].'_na.'.$img['extension'];
2089
                    $icon = Display::return_icon($img, $explanation, null, ICON_SIZE_BIG);
2090
                }
2091
            }
2092
            echo $icon;
2093
            echo '</div>';
2094
            echo '</li>';
2095
        }
2096
2097
        echo '<li>';
2098
        echo '<div class="icon_image_content">';
2099
        if ($objExercise->exercise_was_added_in_lp == true) {
2100
            echo Display::return_icon(
2101
                'database_na.png',
2102
                get_lang('GetExistingQuestion'),
2103
                null,
2104
                ICON_SIZE_BIG
2105
            );
2106
        } else {
2107
            if (in_array($feedbackType, [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) {
2108
                echo $url = "<a href=\"question_pool.php?".api_get_cidreq()."&type=1&fromExercise=$exerciseId\">";
2109
            } else {
2110
                echo $url = '<a href="question_pool.php?'.api_get_cidreq().'&fromExercise='.$exerciseId.'">';
2111
            }
2112
            echo Display::return_icon(
2113
                'database.png',
2114
                get_lang('GetExistingQuestion'),
2115
                null,
2116
                ICON_SIZE_BIG
2117
            );
2118
        }
2119
        echo '</a>';
2120
        echo '</div></li>';
2121
        echo '</ul>';
2122
        echo '</div>';
2123
        echo '</div>';
2124
    }
2125
2126
    /**
2127
     * @param int    $question_id
2128
     * @param string $name
2129
     * @param int    $course_id
2130
     * @param int    $position
2131
     *
2132
     * @return false|string
2133
     */
2134
    public static function saveQuestionOption($question_id, $name, $course_id, $position = 0)
2135
    {
2136
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
2137
        $params['question_id'] = (int) $question_id;
2138
        $params['name'] = $name;
2139
        $params['position'] = $position;
2140
        $params['c_id'] = $course_id;
2141
        //$result = self::readQuestionOption($question_id, $course_id);
2142
        $last_id = Database::insert($table, $params);
2143
2144
        return $last_id;
2145
    }
2146
2147
    /**
2148
     * @param int $question_id
2149
     * @param int $course_id
2150
     */
2151
    public static function deleteAllQuestionOptions($question_id, $course_id)
2152
    {
2153
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
2154
        Database::delete(
2155
            $table,
2156
            [
2157
                'question_id = ?' => [
2158
                    $question_id,
2159
                ],
2160
            ]
2161
        );
2162
    }
2163
2164
    /**
2165
     * @param int   $id
2166
     * @param array $params
2167
     * @param int   $course_id
2168
     *
2169
     * @return bool|int
2170
     */
2171
    public static function updateQuestionOption($id, $params, $course_id)
2172
    {
2173
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
2174
        if (isset($params['id'])) {
2175
            // 'id' has been replaced by 'iid' but is still defined into
2176
            // $params because of Database::select() which add this index
2177
            // by default, so "undefine" it to avoid errors if the field
2178
            // does not exist
2179
            unset($params['id']);
2180
        }
2181
2182
        return Database::update(
2183
            $table,
2184
            $params,
2185
            ['iid = ?' => [$id]]
2186
        );
2187
    }
2188
2189
    /**
2190
     * @param int $question_id
2191
     * @param int $course_id
2192
     *
2193
     * @return array
2194
     */
2195
    public static function readQuestionOption($question_id, $course_id)
2196
    {
2197
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
2198
2199
        return Database::select(
2200
            '*',
2201
            $table,
2202
            [
2203
                'where' => [
2204
                    'question_id = ?' => [
2205
                        $question_id,
2206
                    ],
2207
                ],
2208
                'order' => 'iid ASC',
2209
            ]
2210
        );
2211
    }
2212
2213
    /**
2214
     * Shows question title an description.
2215
     *
2216
     * @param Exercise $exercise The current exercise object
2217
     * @param int      $counter  A counter for the current question
2218
     * @param array    $score    Array of optional info ['pass', 'revised', 'score', 'weight', 'user_answered']
2219
     *
2220
     * @return string HTML string with the header of the question (before the answers table)
2221
     */
2222
    public function return_header(Exercise $exercise, $counter = null, $score = [])
2223
    {
2224
        $counterLabel = '';
2225
        if (!empty($counter)) {
2226
            $counterLabel = (int) $counter;
2227
        }
2228
2229
        $scoreLabel = get_lang('Wrong');
2230
        if (in_array($exercise->results_disabled, [
2231
            RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
2232
            RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
2233
        ])
2234
        ) {
2235
            $scoreLabel = get_lang('QuizWrongAnswerHereIsTheCorrectOne');
2236
        }
2237
2238
        $class = 'error';
2239
        if (isset($score['pass']) && $score['pass'] == true) {
2240
            $scoreLabel = get_lang('Correct');
2241
2242
            if (in_array($exercise->results_disabled, [
2243
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
2244
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
2245
            ])
2246
            ) {
2247
                $scoreLabel = get_lang('CorrectAnswer');
2248
            }
2249
            $class = 'success';
2250
        }
2251
2252
        switch ($this->type) {
2253
            case FREE_ANSWER:
2254
            case UPLOAD_ANSWER:
2255
            case ORAL_EXPRESSION:
2256
            case ANSWER_IN_OFFICE_DOC:
2257
            case ANNOTATION:
2258
                $score['revised'] = isset($score['revised']) ? $score['revised'] : false;
2259
                if ($score['revised'] == true) {
2260
                    $scoreLabel = get_lang('Revised');
2261
                    $class = '';
2262
                } else {
2263
                    $scoreLabel = get_lang('NotRevised');
2264
                    $class = 'warning';
2265
                    if (isset($score['weight'])) {
2266
                        $weight = float_format($score['weight'], 1);
2267
                        $score['result'] = ' ? / '.$weight;
2268
                    }
2269
                    $model = ExerciseLib::getCourseScoreModel();
2270
                    if (!empty($model)) {
2271
                        $score['result'] = ' ? ';
2272
                    }
2273
2274
                    $hide = api_get_configuration_value('hide_free_question_score');
2275
                    if (true === $hide) {
2276
                        $score['result'] = '-';
2277
                    }
2278
                }
2279
                break;
2280
            case UNIQUE_ANSWER:
2281
                if (in_array($exercise->results_disabled, [
2282
                    RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
2283
                    RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
2284
                ])
2285
                ) {
2286
                    if (isset($score['user_answered'])) {
2287
                        if ($score['user_answered'] === false) {
2288
                            $scoreLabel = get_lang('Unanswered');
2289
                            $class = 'info';
2290
                        }
2291
                    }
2292
                }
2293
                break;
2294
        }
2295
2296
        // display question category, if any
2297
        $header = '';
2298
        if ($exercise->display_category_name) {
2299
            $header = TestCategory::returnCategoryAndTitle($this->iid);
2300
        }
2301
        $show_media = '';
2302
        if ($show_media) {
2303
            $header .= $this->show_media_content();
2304
        }
2305
2306
        $scoreCurrent = [
2307
            'used' => isset($score['score']) ? $score['score'] : '',
2308
            'missing' => isset($score['weight']) ? $score['weight'] : '',
2309
        ];
2310
2311
        // Check whether we need to hide the question ID
2312
        // (quiz_hide_question_number config + quiz field)
2313
        $title = '';
2314
        if ($exercise->getHideQuestionNumber()) {
2315
            $title = Display::page_subheader2($this->question);
2316
        } else {
2317
            $title = Display::page_subheader2($counterLabel.'. '.$this->question);
2318
        }
2319
        $header .= $title;
2320
2321
        $showRibbon = true;
2322
        // dont display score for certainty degree questions
2323
        if (MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $this->type) {
2324
            $showRibbon = false;
2325
            $ribbonResult = api_get_configuration_value('show_exercise_question_certainty_ribbon_result');
2326
            if (true === $ribbonResult) {
2327
                $showRibbon = true;
2328
            }
2329
        }
2330
2331
        if ($showRibbon && isset($score['result'])) {
2332
            if (in_array($exercise->results_disabled, [
2333
                RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER,
2334
                RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING,
2335
            ])
2336
            ) {
2337
                $score['result'] = null;
2338
            }
2339
            $header .= $exercise->getQuestionRibbon($class, $scoreLabel, $score['result'], $scoreCurrent);
2340
        }
2341
2342
        if ($this->type != READING_COMPREHENSION) {
2343
            // Do not show the description (the text to read) if the question is of type READING_COMPREHENSION
2344
            $header .= Display::div(
2345
                $this->description,
2346
                ['class' => 'question_description']
2347
            );
2348
        } else {
2349
            if (isset($score['pass']) && true == $score['pass']) {
2350
                $message = Display::div(
2351
                    sprintf(
2352
                        get_lang('ReadingQuestionCongratsSpeedXReachedForYWords'),
2353
                        ReadingComprehension::$speeds[$this->level],
2354
                        $this->getWordsCount()
2355
                    )
2356
                );
2357
            } else {
2358
                $message = Display::div(
2359
                    sprintf(
2360
                        get_lang('ReadingQuestionCongratsSpeedXNotReachedForYWords'),
2361
                        ReadingComprehension::$speeds[$this->level],
2362
                        $this->getWordsCount()
2363
                    )
2364
                );
2365
            }
2366
            $header .= $message.'<br />';
2367
        }
2368
2369
        if ($exercise->hideComment && in_array($this->type, [HOT_SPOT, HOT_SPOT_COMBINATION])) {
2370
            $header .= Display::return_message(get_lang('ResultsOnlyAvailableOnline'));
2371
2372
            return $header;
2373
        }
2374
2375
        if (isset($score['pass']) && $score['pass'] === false) {
2376
            if ($this->showFeedback($exercise)) {
2377
                $header .= $this->returnFormatFeedback();
2378
            }
2379
        }
2380
2381
        return $header;
2382
    }
2383
2384
    /**
2385
     * @deprecated
2386
     * Create a question from a set of parameters.
2387
     *
2388
     * @param   int     Quiz ID
2389
     * @param   string  Question name
2390
     * @param   int     Maximum result for the question
2391
     * @param   int     Type of question (see constants at beginning of question.class.php)
2392
     * @param   int     Question level/category
2393
     */
2394
    public function create_question(
2395
        $quiz_id,
2396
        $question_name,
2397
        $question_description = '',
2398
        $max_score = 0,
2399
        $type = 1,
2400
        $level = 1
2401
    ) {
2402
        $course_id = api_get_course_int_id();
2403
        $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2404
        $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
2405
2406
        $quiz_id = (int) $quiz_id;
2407
        $max_score = (float) $max_score;
2408
        $type = (int) $type;
2409
        $level = (int) $level;
2410
2411
        // Get the max position
2412
        $sql = "SELECT max(position) as max_position
2413
                FROM $tbl_quiz_question q
2414
                INNER JOIN $tbl_quiz_rel_question r
2415
                ON
2416
                    q.iid = r.question_id AND
2417
                    exercice_id = $quiz_id AND
2418
                    r.c_id = $course_id";
2419
        $rs_max = Database::query($sql);
2420
        $row_max = Database::fetch_object($rs_max);
2421
        $max_position = $row_max->max_position + 1;
2422
2423
        $params = [
2424
            'c_id' => $course_id,
2425
            'question' => $question_name,
2426
            'description' => $question_description,
2427
            'ponderation' => $max_score,
2428
            'position' => $max_position,
2429
            'type' => $type,
2430
            'level' => $level,
2431
        ];
2432
        $question_id = Database::insert($tbl_quiz_question, $params);
2433
2434
        if ($question_id) {
2435
            // Get the max question_order
2436
            $sql = "SELECT max(question_order) as max_order
2437
                    FROM $tbl_quiz_rel_question
2438
                    WHERE c_id = $course_id AND exercice_id = $quiz_id ";
2439
            $rs_max_order = Database::query($sql);
2440
            $row_max_order = Database::fetch_object($rs_max_order);
2441
            $max_order = $row_max_order->max_order + 1;
2442
            // Attach questions to quiz
2443
            $sql = "INSERT INTO $tbl_quiz_rel_question (c_id, question_id, exercice_id, question_order)
2444
                    VALUES($course_id, $question_id, $quiz_id, $max_order)";
2445
            Database::query($sql);
2446
        }
2447
2448
        return $question_id;
2449
    }
2450
2451
    /**
2452
     * @return string
2453
     */
2454
    public function getTypePicture()
2455
    {
2456
        return $this->typePicture;
2457
    }
2458
2459
    /**
2460
     * @return string
2461
     */
2462
    public function getExplanation()
2463
    {
2464
        return $this->explanationLangVar;
2465
    }
2466
2467
    /**
2468
     * Get course medias.
2469
     *
2470
     * @param int $course_id
2471
     *
2472
     * @return array
2473
     */
2474
    public static function get_course_medias(
2475
        $course_id,
2476
        $start = 0,
2477
        $limit = 100,
2478
        $sidx = 'question',
2479
        $sord = 'ASC',
2480
        $where_condition = []
2481
    ) {
2482
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2483
        $default_where = [
2484
            'c_id = ? AND parent_id = 0 AND type = ?' => [
2485
                $course_id,
2486
                MEDIA_QUESTION,
2487
            ],
2488
        ];
2489
2490
        return Database::select(
2491
            '*',
2492
            $table_question,
2493
            [
2494
                'limit' => " $start, $limit",
2495
                'where' => $default_where,
2496
                'order' => "$sidx $sord",
2497
            ]
2498
        );
2499
    }
2500
2501
    /**
2502
     * Get count course medias.
2503
     *
2504
     * @param int $course_id course id
2505
     *
2506
     * @return int
2507
     */
2508
    public static function get_count_course_medias($course_id)
2509
    {
2510
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
2511
        $result = Database::select(
2512
            'count(*) as count',
2513
            $table_question,
2514
            [
2515
                'where' => [
2516
                    'c_id = ? AND parent_id = 0 AND type = ?' => [
2517
                        $course_id,
2518
                        MEDIA_QUESTION,
2519
                    ],
2520
                ],
2521
            ],
2522
            'first'
2523
        );
2524
2525
        if ($result && isset($result['count'])) {
2526
            return $result['count'];
2527
        }
2528
2529
        return 0;
2530
    }
2531
2532
    /**
2533
     * @param int $course_id
2534
     *
2535
     * @return array
2536
     */
2537
    public static function prepare_course_media_select($course_id)
2538
    {
2539
        $medias = self::get_course_medias($course_id);
2540
        $media_list = [];
2541
        $media_list[0] = get_lang('NoMedia');
2542
2543
        if (!empty($medias)) {
2544
            foreach ($medias as $media) {
2545
                $media_list[$media['iid']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
2546
            }
2547
        }
2548
2549
        return $media_list;
2550
    }
2551
2552
    /**
2553
     * @return array
2554
     */
2555
    public static function get_default_levels()
2556
    {
2557
        return [
2558
            1 => 1,
2559
            2 => 2,
2560
            3 => 3,
2561
            4 => 4,
2562
            5 => 5,
2563
        ];
2564
    }
2565
2566
    /**
2567
     * @return string
2568
     */
2569
    public function show_media_content()
2570
    {
2571
        $html = '';
2572
        if (0 != $this->parent_id) {
2573
            $parent_question = self::read($this->parent_id);
2574
            $html = $parent_question->show_media_content();
2575
        } else {
2576
            $html .= Display::page_subheader($this->selectTitle());
2577
            $html .= $this->selectDescription();
2578
        }
2579
2580
        return $html;
2581
    }
2582
2583
    /**
2584
     * Swap between unique and multiple type answers.
2585
     *
2586
     * @return UniqueAnswer|MultipleAnswer
2587
     */
2588
    public function swapSimpleAnswerTypes($index = 0)
2589
    {
2590
        $oppositeAnswers = [
2591
            UNIQUE_ANSWER => [MULTIPLE_ANSWER],
2592
            MULTIPLE_ANSWER => [UNIQUE_ANSWER, MULTIPLE_ANSWER_DROPDOWN, MULTIPLE_ANSWER_DROPDOWN_COMBINATION],
2593
            MULTIPLE_ANSWER_DROPDOWN => [MULTIPLE_ANSWER],
2594
            MULTIPLE_ANSWER_DROPDOWN_COMBINATION => [MULTIPLE_ANSWER],
2595
        ];
2596
        $this->type = $oppositeAnswers[$this->type][$index];
2597
        Database::update(
2598
            Database::get_course_table(TABLE_QUIZ_QUESTION),
2599
            ['type' => $this->type],
2600
            ['iid = ?' => [$this->iid]]
2601
        );
2602
        $answerClasses = [
2603
            UNIQUE_ANSWER => 'UniqueAnswer',
2604
            MULTIPLE_ANSWER => 'MultipleAnswer',
2605
            MULTIPLE_ANSWER_DROPDOWN => 'MultipleAnswerDropdown',
2606
            MULTIPLE_ANSWER_DROPDOWN_COMBINATION => 'MultipleAnswerDropdownCombination',
2607
        ];
2608
        $swappedAnswer = new $answerClasses[$this->type]();
2609
        foreach ($this as $key => $value) {
2610
            $swappedAnswer->$key = $value;
2611
        }
2612
2613
        $objAnswer = new Answer($swappedAnswer->iid);
2614
        $_POST['nb_answers'] = $objAnswer->nbrAnswers;
2615
2616
        return $swappedAnswer;
2617
    }
2618
2619
    /**
2620
     * @param array $score
2621
     *
2622
     * @return bool
2623
     */
2624
    public function isQuestionWaitingReview($score)
2625
    {
2626
        $isReview = false;
2627
        if (!empty($score)) {
2628
            if (!empty($score['comments']) || $score['score'] > 0) {
2629
                $isReview = true;
2630
            }
2631
        }
2632
2633
        return $isReview;
2634
    }
2635
2636
    /**
2637
     * @param string $value
2638
     */
2639
    public function setFeedback($value)
2640
    {
2641
        $this->feedback = $value;
2642
    }
2643
2644
    /**
2645
     * @param Exercise $exercise
2646
     *
2647
     * @return bool
2648
     */
2649
    public function showFeedback($exercise)
2650
    {
2651
        if (false === $exercise->hideComment) {
2652
            return false;
2653
        }
2654
2655
        return
2656
            in_array($this->type, $this->questionTypeWithFeedback) &&
2657
            EXERCISE_FEEDBACK_TYPE_EXAM != $exercise->getFeedbackType();
2658
    }
2659
2660
    /**
2661
     * @return string
2662
     */
2663
    public function returnFormatFeedback()
2664
    {
2665
        return '<br />'.Display::return_message($this->feedback, 'normal', false);
2666
    }
2667
2668
    /**
2669
     * Check if this question exists in another exercise.
2670
     *
2671
     * @throws \Doctrine\ORM\Query\QueryException
2672
     *
2673
     * @return bool
2674
     */
2675
    public function existsInAnotherExercise()
2676
    {
2677
        $count = $this->getCountExercise();
2678
2679
        return $count > 1;
2680
    }
2681
2682
    /**
2683
     * @throws \Doctrine\ORM\Query\QueryException
2684
     *
2685
     * @return int
2686
     */
2687
    public function getCountExercise()
2688
    {
2689
        $em = Database::getManager();
2690
2691
        $count = $em
2692
            ->createQuery('
2693
                SELECT COUNT(qq.iid) FROM ChamiloCourseBundle:CQuizRelQuestion qq
2694
                WHERE qq.questionId = :iid
2695
            ')
2696
            ->setParameters(['iid' => (int) $this->iid])
2697
            ->getSingleScalarResult();
2698
2699
        return (int) $count;
2700
    }
2701
2702
    /**
2703
     * Check if this question exists in another exercise.
2704
     *
2705
     * @throws \Doctrine\ORM\Query\QueryException
2706
     *
2707
     * @return mixed
2708
     */
2709
    public function getExerciseListWhereQuestionExists()
2710
    {
2711
        $em = Database::getManager();
2712
2713
        return $em
2714
            ->createQuery('
2715
                SELECT e
2716
                FROM ChamiloCourseBundle:CQuizRelQuestion qq
2717
                JOIN ChamiloCourseBundle:CQuiz e
2718
                WHERE e.iid = qq.exerciceId AND qq.questionId = :iid
2719
            ')
2720
            ->setParameters(['iid' => (int) $this->iid])
2721
            ->getResult();
2722
    }
2723
2724
    /**
2725
     * @return int
2726
     */
2727
    public function countAnswers()
2728
    {
2729
        $result = Database::select(
2730
            'COUNT(1) AS c',
2731
            Database::get_course_table(TABLE_QUIZ_ANSWER),
2732
            ['where' => ['question_id = ?' => [$this->iid]]],
2733
            'first'
2734
        );
2735
2736
        return (int) $result['c'];
2737
    }
2738
2739
    /**
2740
     * Count the number of quizzes that use a question.
2741
     *
2742
     * @param int $questionId - question ID
2743
     *
2744
     * @return int - The number of quizzes where the question is used
2745
     */
2746
    public static function countQuizzesUsingQuestion(int $questionId)
2747
    {
2748
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
2749
        $result = Database::select(
2750
            'count(*) as count',
2751
            $table,
2752
            [
2753
                'where' => [
2754
                    'question_id = ? ' => [
2755
                        $questionId,
2756
                    ],
2757
                ],
2758
            ],
2759
            'first'
2760
        );
2761
2762
        if ($result && isset($result['count'])) {
2763
            return $result['count'];
2764
        }
2765
2766
        return 0;
2767
    }
2768
2769
    /**
2770
     * Gets the first quiz ID that uses a given question.
2771
     * The c_quiz_rel_question result with lower iid is the master quiz.
2772
     *
2773
     * @param int $questionId - question ID
2774
     *
2775
     * @return int The quiz ID
2776
     */
2777
    public static function getMasterQuizForQuestion($questionId)
2778
    {
2779
        $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
2780
2781
        $row = Database::select(
2782
            '*',
2783
            $table,
2784
            [
2785
                'where' => [
2786
                    'question_id = ?' => [
2787
                        $questionId,
2788
                    ],
2789
                ],
2790
                'order' => 'iid ASC',
2791
            ],
2792
            'first'
2793
        );
2794
2795
        if (is_array($row) && isset($row['exercice_id'])) {
2796
            return $row['exercice_id'];
2797
        } else {
2798
            return false;
2799
        }
2800
    }
2801
2802
    /**
2803
     * Resizes a picture || Warning!: can only be called after uploadPicture,
2804
     * or if picture is already available in object.
2805
     *
2806
     * @param string $Dimension - Resizing happens proportional according to given dimension: height|width|any
2807
     * @param int    $Max       - Maximum size
2808
     *
2809
     * @return bool|null - true if success, false if failed
2810
     *
2811
     * @author Toon Keppens
2812
     */
2813
    private function resizePicture($Dimension, $Max)
2814
    {
2815
        // if the question has an ID
2816
        if (!$this->iid) {
2817
            return false;
2818
        }
2819
2820
        $picturePath = $this->getHotSpotFolderInCourse().'/'.$this->getPictureFilename();
2821
2822
        // Get dimensions from current image.
2823
        $my_image = new Image($picturePath);
2824
2825
        $current_image_size = $my_image->get_image_size();
2826
        $current_width = $current_image_size['width'];
2827
        $current_height = $current_image_size['height'];
2828
2829
        if ($current_width < $Max && $current_height < $Max) {
2830
            return true;
2831
        } elseif ($current_height == '') {
2832
            return false;
2833
        }
2834
2835
        // Resize according to height.
2836
        if ($Dimension == "height") {
2837
            $resize_scale = $current_height / $Max;
2838
            $new_width = ceil($current_width / $resize_scale);
2839
        }
2840
2841
        // Resize according to width
2842
        if ($Dimension == "width") {
2843
            $new_width = $Max;
2844
        }
2845
2846
        // Resize according to height or width, both should not be larger than $Max after resizing.
2847
        if ($Dimension == "any") {
2848
            if ($current_height > $current_width || $current_height == $current_width) {
2849
                $resize_scale = $current_height / $Max;
2850
                $new_width = ceil($current_width / $resize_scale);
2851
            }
2852
            if ($current_height < $current_width) {
2853
                $new_width = $Max;
2854
            }
2855
        }
2856
2857
        $my_image->resize($new_width);
2858
        $result = $my_image->send_image($picturePath);
2859
2860
        if ($result) {
2861
            return true;
2862
        }
2863
2864
        return false;
2865
    }
2866
}
2867