Completed
Push — 1.10.x ( d7355d...4a3e2c )
by
unknown
51:30
created

Question::selectWeighting()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
/**
5
 * Class Question
6
 *
7
 *	This class allows to instantiate an object of type Question
8
 *
9
 * @author Olivier Brouckaert, original author
10
 * @author Patrick Cool, LaTeX support
11
 * @author Julio Montoya <[email protected]> lot of bug fixes
12
 * @author [email protected] - add question categories
13
 * @package chamilo.exercise
14
 */
15
abstract class Question
16
{
17
    public $id;
18
    public $question;
19
    public $description;
20
    public $weighting;
21
    public $position;
22
    public $type;
23
    public $level;
24
    public $picture;
25
    public $exerciseList;  // array with the list of exercises which this question is in
26
    public $category_list;
27
    public $parent_id;
28
    public $category;
29
    public $isContent;
30
    public $course;
31
    public static $typePicture = 'new_question.png';
32
    public static $explanationLangVar = '';
33
    public $question_table_class = 'table table-striped';
34
    public static $questionTypes = array(
35
        UNIQUE_ANSWER => array('unique_answer.class.php', 'UniqueAnswer'),
36
        MULTIPLE_ANSWER => array('multiple_answer.class.php', 'MultipleAnswer'),
37
        FILL_IN_BLANKS => array('fill_blanks.class.php', 'FillBlanks'),
38
        MATCHING => array('matching.class.php', 'Matching'),
39
        FREE_ANSWER => array('freeanswer.class.php', 'FreeAnswer'),
40
        ORAL_EXPRESSION => array('oral_expression.class.php', 'OralExpression'),
41
        HOT_SPOT => array('hotspot.class.php', 'HotSpot'),
42
        HOT_SPOT_DELINEATION => array('hotspot.class.php', 'HotspotDelineation'),
43
        MULTIPLE_ANSWER_COMBINATION => array('multiple_answer_combination.class.php', 'MultipleAnswerCombination'),
44
        UNIQUE_ANSWER_NO_OPTION => array('unique_answer_no_option.class.php', 'UniqueAnswerNoOption'),
45
        MULTIPLE_ANSWER_TRUE_FALSE => array('multiple_answer_true_false.class.php', 'MultipleAnswerTrueFalse'),
46
        MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE => array(
47
            'multiple_answer_combination_true_false.class.php',
48
            'MultipleAnswerCombinationTrueFalse'
49
        ),
50
        GLOBAL_MULTIPLE_ANSWER => array('global_multiple_answer.class.php' , 'GlobalMultipleAnswer'),
51
        CALCULATED_ANSWER => array('calculated_answer.class.php' , 'CalculatedAnswer'),
52
        UNIQUE_ANSWER_IMAGE => ['UniqueAnswerImage.php', 'UniqueAnswerImage'],
53
        DRAGGABLE => ['Draggable.php', 'Draggable'],
54
        MATCHING_DRAGGABLE => ['MatchingDraggable.php', 'MatchingDraggable']
55
        //MEDIA_QUESTION => array('media_question.class.php' , 'MediaQuestion')
56
    );
57
58
    /**
59
     * constructor of the class
60
     *
61
     * @author Olivier Brouckaert
62
     */
63
    public function __construct()
64
    {
65
        $this->id = 0;
66
        $this->question = '';
67
        $this->description = '';
68
        $this->weighting = 0;
69
        $this->position = 1;
70
        $this->picture = '';
71
        $this->level = 1;
72
        $this->category = 0;
73
        // This variable is used when loading an exercise like an scenario with
74
        // an special hotspot: final_overlap, final_missing, final_excess
75
        $this->extra = '';
76
        $this->exerciseList = array();
77
        $this->course = api_get_course_info();
78
        $this->category_list = array();
79
        $this->parent_id = 0;
80
    }
81
82
    /**
83
     * @return int|null
84
     */
85
    public function getIsContent()
86
    {
87
        $isContent = null;
88
        if (isset($_REQUEST['isContent'])) {
89
            $isContent = intval($_REQUEST['isContent']);
90
        }
91
92
        return $this->isContent = $isContent;
93
    }
94
95
    /**
96
     * Reads question information from the data base
97
     *
98
     * @param int $id - question ID
99
     * @param int $course_id
100
     *
101
     * @return Question
102
     *
103
     * @author Olivier Brouckaert
104
     */
105
    public static function read($id, $course_id = null)
106
    {
107
        $id = intval($id);
108
109
        if (!empty($course_id)) {
110
            $course_info =  api_get_course_info_by_id($course_id);
111
        } else {
112
            $course_info = api_get_course_info();
113
        }
114
115
        $course_id = $course_info['real_id'];
116
117
        if (empty($course_id) || $course_id == -1 ) {
118
119
            return false;
120
        }
121
122
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
123
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
124
125
        $sql = "SELECT question, description, ponderation, position, type, picture, level, extra
126
                FROM $TBL_QUESTIONS
127
                WHERE c_id = $course_id AND id = $id ";
128
129
        $result = Database::query($sql);
130
131
        // if the question has been found
132
        if ($object = Database::fetch_object($result)) {
133
134
            $objQuestion = Question::getInstance($object->type);
135
            if (!empty($objQuestion)) {
136
137
                $objQuestion->id = $id;
138
                $objQuestion->question = $object->question;
139
                $objQuestion->description = $object->description;
140
                $objQuestion->weighting = $object->ponderation;
141
                $objQuestion->position = $object->position;
142
                $objQuestion->type = $object->type;
143
                $objQuestion->picture = $object->picture;
144
                $objQuestion->level = (int) $object->level;
145
                $objQuestion->extra = $object->extra;
146
                $objQuestion->course = $course_info;
147
                $objQuestion->category = TestCategory::getCategoryForQuestion($id);
148
149
                $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST);
150
151
                $sql = "SELECT DISTINCT q.exercice_id
152
                        FROM $TBL_EXERCISE_QUESTION q
153
                        INNER JOIN $tblQuiz e
154
                        ON e.c_id = q.c_id AND e.id = q.exercice_id
155
                        WHERE
156
                            q.c_id = $course_id AND
157
                            q.question_id = $id AND
158
                            e.active >= 0";
159
160
                $result = Database::query($sql);
161
162
                // fills the array with the exercises which this question is in
163
                if ($result) {
164
                    while ($obj = Database::fetch_object($result)) {
165
                        $objQuestion->exerciseList[] = $obj->exercice_id;
166
                    }
167
                }
168
169
                return $objQuestion;
170
            }
171
        }
172
173
        // question not found
174
        return false;
175
    }
176
177
    /**
178
     * returns the question ID
179
     *
180
     * @author Olivier Brouckaert
181
     *
182
     * @return integer - question ID
183
     */
184
    public function selectId()
185
    {
186
        return $this->id;
187
    }
188
189
    /**
190
     * returns the question title
191
     *
192
     * @author Olivier Brouckaert
193
     * @return string - question title
194
     */
195
    public function selectTitle()
196
    {
197
        return $this->question;
198
    }
199
200
    /**
201
     * returns the question description
202
     *
203
     * @author Olivier Brouckaert
204
     * @return string - question description
205
     */
206
    public function selectDescription()
207
    {
208
        return $this->description;
209
    }
210
211
    /**
212
     * returns the question weighting
213
     *
214
     * @author Olivier Brouckaert
215
     * @return integer - question weighting
216
     */
217
    public function selectWeighting()
218
    {
219
        return $this->weighting;
220
    }
221
222
    /**
223
     * returns the question position
224
     *
225
     * @author Olivier Brouckaert
226
     * @return integer - question position
227
     */
228
    public function selectPosition()
229
    {
230
        return $this->position;
231
    }
232
233
    /**
234
     * returns the answer type
235
     *
236
     * @author Olivier Brouckaert
237
     * @return integer - answer type
238
     */
239
    public function selectType()
240
    {
241
        return $this->type;
242
    }
243
244
    /**
245
     * returns the level of the question
246
     *
247
     * @author Nicolas Raynaud
248
     * @return integer - level of the question, 0 by default.
249
     */
250
    public function selectLevel()
251
    {
252
        return $this->level;
253
    }
254
255
    /**
256
     * returns the picture name
257
     *
258
     * @author Olivier Brouckaert
259
     * @return string - picture name
260
     */
261
    public function selectPicture()
262
    {
263
        return $this->picture;
264
    }
265
266
    /**
267
     * @return bool|string
268
     */
269
    public function selectPicturePath()
270
    {
271
        if (!empty($this->picture)) {
272
            return api_get_path(WEB_COURSE_PATH) . $this->course['path'] . '/document/images/' . $this->picture;
273
        }
274
275
        return false;
276
    }
277
278
    /**
279
     * returns the array with the exercise ID list
280
     *
281
     * @author Olivier Brouckaert
282
     * @return array - list of exercise ID which the question is in
283
     */
284
    public function selectExerciseList()
285
    {
286
        return $this->exerciseList;
287
    }
288
289
    /**
290
     * returns the number of exercises which this question is in
291
     *
292
     * @author Olivier Brouckaert
293
     * @return integer - number of exercises
294
     */
295
    public function selectNbrExercises()
296
    {
297
        return sizeof($this->exerciseList);
298
    }
299
300
    /**
301
     * changes the question title
302
     *
303
     * @param string $title - question title
304
     *
305
     * @author Olivier Brouckaert
306
     */
307
    public function updateTitle($title)
308
    {
309
        $this->question=$title;
310
    }
311
312
    /**
313
     * @param int $id
314
     */
315
    public function updateParentId($id)
316
    {
317
        $this->parent_id = intval($id);
318
    }
319
320
    /**
321
     * changes the question description
322
     *
323
     * @param string $description - question description
324
     *
325
     * @author Olivier Brouckaert
326
     *
327
     */
328
    public function updateDescription($description)
329
    {
330
        $this->description = $description;
331
    }
332
333
    /**
334
     * changes the question weighting
335
     *
336
     * @param integer $weighting - question weighting
337
     *
338
     * @author Olivier Brouckaert
339
     */
340
    public function updateWeighting($weighting)
341
    {
342
        $this->weighting = $weighting;
343
    }
344
345
    /**
346
     * @param array $category
347
     *
348
     * @author Hubert Borderiou 12-10-2011
349
     *
350
     */
351
    public function updateCategory($category)
352
    {
353
        $this->category = $category;
354
    }
355
356
    /**
357
     * @param int $value
358
     *
359
     * @author Hubert Borderiou 12-10-2011
360
     */
361
    public function updateScoreAlwaysPositive($value)
362
    {
363
        $this->scoreAlwaysPositive = $value;
364
    }
365
366
    /**
367
     * @param int $value
368
     *
369
     * @author Hubert Borderiou 12-10-2011
370
     */
371
    public function updateUncheckedMayScore($value)
372
    {
373
        $this->uncheckedMayScore = $value;
374
    }
375
376
    /**
377
     * Save category of a question
378
     *
379
     * A question can have n categories if category is empty,
380
     * then question has no category then delete the category entry
381
     *
382
     * @param array $category_list
383
     *
384
     * @author Julio Montoya - Adding multiple cat support
385
     */
386
    public function saveCategories($category_list)
387
    {
388
        if (!empty($category_list)) {
389
            $this->deleteCategory();
390
            $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
391
392
            // update or add category for a question
393
            foreach ($category_list as $category_id) {
394
                $category_id = intval($category_id);
395
                $question_id = intval($this->id);
396
                $sql = "SELECT count(*) AS nb
397
                        FROM $TBL_QUESTION_REL_CATEGORY
398
                        WHERE
399
                            category_id = $category_id
400
                            AND question_id = $question_id
401
                            AND c_id=".api_get_course_int_id();
402
                $res = Database::query($sql);
403
                $row = Database::fetch_array($res);
404
                if ($row['nb'] > 0) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
405
                    // DO nothing
406
                } else {
407
                    $sql = "INSERT INTO $TBL_QUESTION_REL_CATEGORY (c_id, question_id, category_id)
408
                            VALUES (" . api_get_course_int_id() . ", $question_id, $category_id)";
409
                    Database::query($sql);
410
                }
411
            }
412
        }
413
    }
414
415
    /**
416
     * in this version, a question can only have 1 category
417
     * if category is 0, then question has no category then delete the category entry
418
     * @param int $category
419
     *
420
     * @author Hubert Borderiou 12-10-2011
421
     */
422
    public function saveCategory($category)
423
    {
424
        if ($category <= 0) {
425
            $this->deleteCategory();
426
        } else {
427
            // update or add category for a question
428
429
            $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
430
            $category_id = intval($category);
431
            $question_id = intval($this->id);
432
            $sql = "SELECT count(*) AS nb FROM $table
433
                    WHERE
434
                        question_id = $question_id AND
435
                        c_id=" . api_get_course_int_id();
436
            $res = Database::query($sql);
437
            $row = Database::fetch_array($res);
438
            if ($row['nb'] > 0) {
439
                $sql = "UPDATE $table
440
                        SET category_id = $category_id
441
                        WHERE
442
                            question_id = $question_id AND
443
                            c_id = " . api_get_course_int_id();
444
                Database::query($sql);
445
            } else {
446
                $sql = "INSERT INTO $table (c_id, question_id, category_id)
447
                        VALUES (" . api_get_course_int_id().", $question_id, $category_id)";
448
                Database::query($sql);
449
            }
450
        }
451
    }
452
453
    /**
454
     * @author hubert borderiou 12-10-2011
455
     * delete any category entry for question id
456
     * delete the category for question
457
     */
458
    public function deleteCategory()
459
    {
460
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
461
        $question_id = intval($this->id);
462
        $sql = "DELETE FROM $table
463
                WHERE
464
                    question_id = $question_id AND
465
                    c_id = " . api_get_course_int_id();
466
        Database::query($sql);
467
    }
468
469
    /**
470
     * changes the question position
471
     *
472
     * @param integer $position - question position
473
     *
474
     * @author Olivier Brouckaert
475
     */
476
    public function updatePosition($position)
477
    {
478
        $this->position = $position;
479
    }
480
481
    /**
482
     * changes the question level
483
     *
484
     * @param integer $level - question level
485
     *
486
     * @author Nicolas Raynaud
487
     */
488
    public function updateLevel($level)
489
    {
490
        $this->level = $level;
491
    }
492
493
    /**
494
     * changes the answer type. If the user changes the type from "unique answer" to "multiple answers"
495
     * (or conversely) answers are not deleted, otherwise yes
496
     *
497
     * @param integer $type - answer type
498
     *
499
     * @author Olivier Brouckaert
500
     */
501
    public function updateType($type)
502
    {
503
        $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
504
        $course_id = $this->course['real_id'];
505
506
        if (empty($course_id)) {
507
            $course_id = api_get_course_int_id();
508
        }
509
        // if we really change the type
510
        if ($type != $this->type) {
511
            // if we don't change from "unique answer" to "multiple answers" (or conversely)
512
            if (
513
                !in_array($this->type, array(UNIQUE_ANSWER, MULTIPLE_ANSWER)) ||
514
                !in_array($type, array(UNIQUE_ANSWER, MULTIPLE_ANSWER))
515
            ) {
516
                // removes old answers
517
                $sql = "DELETE FROM $TBL_REPONSES
518
                        WHERE c_id = $course_id AND question_id = " . intval($this->id);
519
                Database::query($sql);
520
            }
521
522
            $this->type=$type;
523
        }
524
    }
525
526
    /**
527
     * adds a picture to the question
528
     *
529
     * @param string $Picture - temporary path of the picture to upload
530
     * @param string $PictureName - Name of the picture
531
     * @param string $picturePath
532
     *
533
     * @return boolean - true if uploaded, otherwise false
534
     *
535
     * @author Olivier Brouckaert
536
     */
537
    public function uploadPicture($Picture, $PictureName, $picturePath = null)
538
    {
539
        if (empty($picturePath)) {
540
            global $picturePath;
541
        }
542
543
        if (!file_exists($picturePath)) {
544
            if (mkdir($picturePath, api_get_permissions_for_new_directories())) {
545
                // document path
546
                $documentPath = api_get_path(SYS_COURSE_PATH) . $this->course['path'] . "/document";
547
                $path = str_replace($documentPath, '', $picturePath);
548
                $title_path = basename($picturePath);
549
                $doc_id = add_document($this->course, $path, 'folder', 0, $title_path);
550
                api_item_property_update(
551
                    $this->course,
552
                    TOOL_DOCUMENT,
553
                    $doc_id,
554
                    'FolderCreated',
555
                    api_get_user_id()
556
                );
557
            }
558
        }
559
560
        // if the question has got an ID
561
        if ($this->id) {
562
            $this->picture = 'quiz-' . $this->id . '.jpg';
563
            $o_img = new Image($Picture);
564
            $o_img->send_image($picturePath . '/' . $this->picture, -1, 'jpg');
565
            $document_id = add_document(
566
                $this->course,
567
                '/images/' . $this->picture,
568
                'file',
569
                filesize($picturePath . '/' . $this->picture),
570
                $this->picture
571
            );
572
            if ($document_id) {
573
                return api_item_property_update(
574
                    $this->course,
575
                    TOOL_DOCUMENT,
576
                    $document_id,
577
                    'DocumentAdded',
578
                    api_get_user_id()
579
                );
580
            }
581
        }
582
583
        return false;
584
    }
585
586
    /**
587
     * Resizes a picture || Warning!: can only be called after uploadPicture,
588
     * or if picture is already available in object.
589
     * @param string $Dimension - Resizing happens proportional according to given dimension: height|width|any
590
     * @param integer $Max - Maximum size
591
     *
592
     * @return boolean - true if success, false if failed
593
     *
594
     * @author Toon Keppens
595
     */
596
    public function resizePicture($Dimension, $Max)
597
    {
598
        global $picturePath;
599
600
        // if the question has an ID
601
        if ($this->id) {
602
            // Get dimensions from current image.
603
            $my_image = new Image($picturePath . '/' . $this->picture);
604
605
            $current_image_size = $my_image->get_image_size();
606
            $current_width = $current_image_size['width'];
607
            $current_height = $current_image_size['height'];
608
609
            if ($current_width < $Max && $current_height < $Max)
610
                return true;
611
            elseif ($current_height == "")
612
                return false;
613
614
            // Resize according to height.
615
            if ($Dimension == "height") {
616
                $resize_scale = $current_height / $Max;
617
                $new_height = $Max;
618
                $new_width = ceil($current_width / $resize_scale);
619
            }
620
621
            // Resize according to width
622
            if ($Dimension == "width") {
623
                $resize_scale = $current_width / $Max;
624
                $new_width = $Max;
625
                $new_height = ceil($current_height / $resize_scale);
626
            }
627
628
            // Resize according to height or width, both should not be larger than $Max after resizing.
629
            if ($Dimension == "any") {
630 View Code Duplication
                if ($current_height > $current_width || $current_height == $current_width) {
631
                    $resize_scale = $current_height / $Max;
632
                    $new_height = $Max;
633
                    $new_width = ceil($current_width / $resize_scale);
634
                }
635 View Code Duplication
                if ($current_height < $current_width) {
636
                    $resize_scale = $current_width / $Max;
637
                    $new_width = $Max;
638
                    $new_height = ceil($current_height / $resize_scale);
639
                }
640
            }
641
642
            $my_image->resize($new_width, $new_height);
643
            $result = $my_image->send_image($picturePath . '/' . $this->picture);
644
645
            if ($result) {
646
                return true;
647
            } else {
648
                return false;
649
            }
650
        }
651
    }
652
653
    /**
654
     * deletes the picture
655
     *
656
     * @author Olivier Brouckaert
657
     * @return boolean - true if removed, otherwise false
658
     */
659
    public function removePicture()
660
    {
661
        global $picturePath;
662
663
        // if the question has got an ID and if the picture exists
664
        if ($this->id) {
665
            $picture = $this->picture;
666
            $this->picture = '';
667
668
            return @unlink($picturePath . '/' . $picture) ? true : false;
669
        }
670
671
        return false;
672
    }
673
674
    /**
675
     * Exports a picture to another question
676
     *
677
     * @author Olivier Brouckaert
678
     * @param integer $questionId - ID of the target question
679
     * @return boolean - true if copied, otherwise false
680
     */
681
    public function exportPicture($questionId, $course_info)
682
    {
683
        $course_id = $course_info['real_id'];
684
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
685
        $destination_path = api_get_path(SYS_COURSE_PATH) . $course_info['path'] . '/document/images';
686
        $source_path = api_get_path(SYS_COURSE_PATH) . $this->course['path'] . '/document/images';
687
688
        // if the question has got an ID and if the picture exists
689
        if ($this->id && !empty($this->picture)) {
690
            $picture = explode('.', $this->picture);
691
            $extension = $picture[sizeof($picture) - 1];
692
            $picture = 'quiz-' . $questionId . '.' . $extension;
693
            $result = @copy($source_path . '/' . $this->picture, $destination_path . '/' . $picture) ? true : false;
694
            // If copy was correct then add to the database
695
            if ($result) {
696
                $sql = "UPDATE $TBL_QUESTIONS SET
697
                        picture = '" . Database::escape_string($picture) . "'
698
                        WHERE c_id = $course_id AND id='" . intval($questionId) . "'";
699
                Database::query($sql);
700
701
                $document_id = add_document(
702
                    $course_info,
703
                    '/images/' . $picture,
704
                    'file',
705
                    filesize($destination_path . '/' . $picture),
706
                    $picture
707
                );
708
                if ($document_id) {
709
                    return api_item_property_update(
710
                        $course_info,
711
                        TOOL_DOCUMENT,
712
                        $document_id,
713
                        'DocumentAdded',
714
                        api_get_user_id()
715
                    );
716
                }
717
            }
718
719
            return $result;
720
        }
721
        return false;
722
    }
723
724
    /**
725
     * Saves the picture coming from POST into a temporary file
726
     * Temporary pictures are used when we don't want to save a picture right after a form submission.
727
     * For example, if we first show a confirmation box.
728
     *
729
     * @author Olivier Brouckaert
730
     * @param string $Picture - temporary path of the picture to move
731
     * @param string $PictureName - Name of the picture
732
     */
733
    public function setTmpPicture($Picture, $PictureName)
734
    {
735
        global $picturePath;
736
        $PictureName = explode('.', $PictureName);
737
        $Extension = $PictureName[sizeof($PictureName) - 1];
738
739
        // saves the picture into a temporary file
740
        @move_uploaded_file($Picture, $picturePath . '/tmp.' . $Extension);
741
    }
742
743
    /**
744
     * Sets the title
745
     */
746
    public function setTitle($title)
747
    {
748
        $this->question = $title;
749
    }
750
751
    /**
752
     * Sets extra info
753
     */
754
    public function setExtra($extra)
755
    {
756
        $this->extra = $extra;
757
    }
758
759
    /**
760
     * Moves the temporary question "tmp" to "quiz-$questionId"
761
     * Temporary pictures are used when we don't want to save a picture right after a form submission.
762
     * For example, if we first show a confirmation box.
763
     *
764
     * @author Olivier Brouckaert
765
     * @return boolean - true if moved, otherwise false
766
     */
767
    function getTmpPicture()
768
    {
769
        global $picturePath;
770
771
        // if the question has got an ID and if the picture exists
772
        if ($this->id) {
773
            if (file_exists($picturePath . '/tmp.jpg')) {
774
                $Extension = 'jpg';
775
            } elseif (file_exists($picturePath . '/tmp.gif')) {
776
                $Extension = 'gif';
777
            } elseif (file_exists($picturePath . '/tmp.png')) {
778
                $Extension = 'png';
779
            }
780
            $this->picture = 'quiz-' . $this->id . '.' . $Extension;
781
            return @rename($picturePath . '/tmp.' . $Extension, $picturePath . '/' . $this->picture) ? true : false;
782
        }
783
        return false;
784
    }
785
786
    /**
787
     * updates the question in the data base
788
     * if an exercise ID is provided, we add that exercise ID into the exercise list
789
     *
790
     * @author Olivier Brouckaert
791
     * @param integer $exerciseId - exercise ID if saving in an exercise
792
     */
793
    public function save($exerciseId = 0)
794
    {
795
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
796
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
797
798
        $id = $this->id;
799
        $question = $this->question;
800
        $description = $this->description;
801
        $weighting = $this->weighting;
802
        $position = $this->position;
803
        $type = $this->type;
804
        $picture = $this->picture;
805
        $level = $this->level;
806
        $extra = $this->extra;
807
        $c_id = $this->course['real_id'];
808
        $category = $this->category;
809
810
        // question already exists
811
        if(!empty($id)) {
812
813
            $params = [
814
                'question' => $question,
815
                'description' => $description,
816
                'ponderation' => $weighting,
817
                'position' => $position,
818
                'type' => $type,
819
                'picture' => $picture,
820
                'extra' => $extra,
821
                'level' => $level,
822
            ];
823
824
            Database::update(
825
                $TBL_QUESTIONS,
826
                $params,
827
                ['c_id = ? AND id = ?' => [$c_id, $id]]
828
            );
829
            $this->saveCategory($category);
0 ignored issues
show
Bug introduced by
It seems like $category defined by $this->category on line 808 can also be of type array; however, Question::saveCategory() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
830
831
            if (!empty($exerciseId)) {
832
                api_item_property_update(
833
                    $this->course,
834
                    TOOL_QUIZ,
835
                    $id,
836
                    'QuizQuestionUpdated',
837
                    api_get_user_id()
838
                );
839
            }
840
            if (api_get_setting('search_enabled') == 'true') {
841
                if ($exerciseId != 0) {
842
                    $this -> search_engine_edit($exerciseId);
843
                } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
844
                    /**
845
                     * actually there is *not* an user interface for
846
                     * creating questions without a relation with an exercise
847
                     */
848
                }
849
            }
850
        } else {
851
            // creates a new question
852
            $sql = "SELECT max(position)
853
                    FROM $TBL_QUESTIONS as question,
854
                    $TBL_EXERCISE_QUESTION as test_question
855
                    WHERE
856
                        question.id = test_question.question_id AND
857
                        test_question.exercice_id = " . intval($exerciseId) . " AND
858
                        question.c_id = $c_id AND
859
                        test_question.c_id = $c_id ";
860
            $result	= Database::query($sql);
861
            $current_position = Database::result($result,0,0);
862
            $this->updatePosition($current_position+1);
863
            $position = $this->position;
864
865
            $params = [
866
                'c_id' => $c_id,
867
                'question' => $question,
868
                'description' => $description,
869
                'ponderation' => $weighting,
870
                'position' => $position,
871
                'type' => $type,
872
                'picture'  => $picture,
873
                'extra' => $extra,
874
                'level' => $level
875
            ];
876
877
            $this->id = Database::insert($TBL_QUESTIONS, $params);
878
879
            if ($this->id) {
880
881
                $sql = "UPDATE $TBL_QUESTIONS SET id = iid WHERE iid = {$this->id}";
882
                Database::query($sql);
883
884
                api_item_property_update(
885
                    $this->course,
886
                    TOOL_QUIZ,
887
                    $this->id,
888
                    'QuizQuestionAdded',
889
                    api_get_user_id()
890
                );
891
892
                // If hotspot, create first answer
893 View Code Duplication
                if ($type == HOT_SPOT || $type == HOT_SPOT_ORDER) {
894
                    $TBL_ANSWERS = Database::get_course_table(
895
                        TABLE_QUIZ_ANSWER
896
                    );
897
                    $params = [
898
                        'c_id' => $c_id,
899
                        'question_id' => $this->id,
900
                        'answer' => '',
901
                        'correct' => '',
902
                        'comment' => '',
903
                        'ponderation' => 10,
904
                        'position' => 1,
905
                        'hotspot_coordinates' => '0;0|0|0',
906
                        'hotspot_type' => 'square',
907
                    ];
908
                    $id = Database::insert($TBL_ANSWERS, $params);
909
                    if ($id) {
910
                        $sql = "UPDATE $TBL_ANSWERS SET id = iid, id_auto = iid WHERE iid = $id";
911
                        Database::query($sql);
912
                    }
913
                }
914
915 View Code Duplication
                if ($type == HOT_SPOT_DELINEATION) {
916
                    $TBL_ANSWERS = Database::get_course_table(
917
                        TABLE_QUIZ_ANSWER
918
                    );
919
                    $params = [
920
                        'c_id' => $c_id,
921
                        'question_id' => $this->id,
922
                        'answer' => '',
923
                        'correct' => '',
924
                        'comment' => '',
925
                        'ponderation' => 10,
926
                        'position' => 1,
927
                        'hotspot_coordinates' => '0;0|0|0',
928
                        'hotspot_type' => 'delineation',
929
                    ];
930
                    $id = Database::insert($TBL_ANSWERS, $params);
931
932
                    if ($id) {
933
                        $sql = "UPDATE $TBL_ANSWERS SET id = iid, id_auto = iid WHERE iid = $id";
934
                        Database::query($sql);
935
                    }
936
                }
937
938
                if (api_get_setting('search_enabled') == 'true') {
939
                    if ($exerciseId != 0) {
940
                        $this->search_engine_edit($exerciseId, true);
941
                    } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
942
                        /**
943
                         * actually there is *not* an user interface for
944
                         * creating questions without a relation with an exercise
945
                         */
946
                    }
947
                }
948
            }
949
        }
950
951
        // if the question is created in an exercise
952
        if ($exerciseId) {
953
            // adds the exercise into the exercise list of this question
954
            $this->addToList($exerciseId, TRUE);
955
        }
956
    }
957
958
    public function search_engine_edit($exerciseId, $addQs=false, $rmQs=false)
959
    {
960
        // update search engine and its values table if enabled
961
        if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian')) {
962
            $course_id = api_get_course_id();
963
            // get search_did
964
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
965 View Code Duplication
            if ($addQs || $rmQs) {
966
                //there's only one row per question on normal db and one document per question on search engine db
967
                $sql = 'SELECT * FROM %s
968
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=%s LIMIT 1';
969
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
970
            } else {
971
                $sql = 'SELECT * FROM %s
972
                    WHERE course_code=\'%s\' AND tool_id=\'%s\'
973
                    AND ref_id_high_level=%s AND ref_id_second_level=%s LIMIT 1';
974
                $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
975
            }
976
            $res = Database::query($sql);
977
978
            if (Database::num_rows($res) > 0 || $addQs) {
979
                require_once(api_get_path(LIBRARY_PATH) . 'search/ChamiloIndexer.class.php');
980
                require_once(api_get_path(LIBRARY_PATH) . 'search/IndexableChunk.class.php');
981
982
                $di = new ChamiloIndexer();
983
                if ($addQs) {
984
                    $question_exercises = array((int) $exerciseId);
985
                } else {
986
                    $question_exercises = array();
987
                }
988
                isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
989
                $di->connectDb(NULL, NULL, $lang);
990
991
                // retrieve others exercise ids
992
                $se_ref = Database::fetch_array($res);
993
                $se_doc = $di->get_document((int)$se_ref['search_did']);
994
                if ($se_doc !== FALSE) {
995
                    if (($se_doc_data = $di->get_document_data($se_doc)) !== FALSE) {
996
                        $se_doc_data = unserialize($se_doc_data);
997
                        if (
998
                            isset($se_doc_data[SE_DATA]['type']) &&
999
                            $se_doc_data[SE_DATA]['type'] == SE_DOCTYPE_EXERCISE_QUESTION
1000
                        ) {
1001
                            if (
1002
                                isset($se_doc_data[SE_DATA]['exercise_ids']) &&
1003
                                is_array($se_doc_data[SE_DATA]['exercise_ids'])
1004
                            ) {
1005
                                foreach ($se_doc_data[SE_DATA]['exercise_ids'] as $old_value) {
1006
                                    if (!in_array($old_value, $question_exercises)) {
1007
                                        $question_exercises[] = $old_value;
1008
                                    }
1009
                                }
1010
                            }
1011
                        }
1012
                    }
1013
                }
1014
                if ($rmQs) {
1015
                    while (($key = array_search($exerciseId, $question_exercises)) !== FALSE) {
1016
                        unset($question_exercises[$key]);
1017
                    }
1018
                }
1019
1020
                // build the chunk to index
1021
                $ic_slide = new IndexableChunk();
1022
                $ic_slide->addValue("title", $this->question);
1023
                $ic_slide->addCourseId($course_id);
1024
                $ic_slide->addToolId(TOOL_QUIZ);
1025
                $xapian_data = array(
1026
                    SE_COURSE_ID => $course_id,
1027
                    SE_TOOL_ID => TOOL_QUIZ,
1028
                    SE_DATA => array(
1029
                        'type' => SE_DOCTYPE_EXERCISE_QUESTION,
1030
                        'exercise_ids' => $question_exercises,
1031
                        'question_id' => (int)$this->id
1032
                    ),
1033
                    SE_USER => (int)api_get_user_id(),
1034
                );
1035
                $ic_slide->xapian_data = serialize($xapian_data);
1036
                $ic_slide->addValue("content", $this->description);
1037
1038
                //TODO: index answers, see also form validation on question_admin.inc.php
1039
1040
                $di->remove_document((int)$se_ref['search_did']);
1041
                $di->addChunk($ic_slide);
1042
1043
                //index and return search engine document id
1044
                if (!empty($question_exercises)) { // if empty there is nothing to index
1045
                    $did = $di->index();
1046
                    unset($di);
1047
                }
1048
                if ($did || $rmQs) {
1049
                    // save it to db
1050 View Code Duplication
                    if ($addQs || $rmQs) {
1051
                        $sql = "DELETE FROM %s
1052
                            WHERE course_code = '%s' AND tool_id = '%s' AND ref_id_second_level = '%s'";
1053
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id);
1054
                    } else {
1055
                        $sql = "DELETE FROM %S
1056
                            WHERE
1057
                                course_code = '%s'
1058
                                AND tool_id = '%s'
1059
                                AND tool_id = '%s'
1060
                                AND ref_id_high_level = '%s'
1061
                                AND ref_id_second_level = '%s'";
1062
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id);
1063
                    }
1064
                    Database::query($sql);
1065
                    if ($rmQs) {
1066
                        if (!empty($question_exercises)) {
1067
                            $sql = "INSERT INTO %s (
1068
                                    id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
1069
                                )
1070
                                VALUES (
1071
                                    NULL, '%s', '%s', %s, %s, %s
1072
                                )";
1073
                            $sql = sprintf(
1074
                                $sql,
1075
                                $tbl_se_ref,
1076
                                $course_id,
1077
                                TOOL_QUIZ,
1078
                                array_shift($question_exercises),
1079
                                $this->id,
1080
                                $did
1081
                            );
1082
                            Database::query($sql);
1083
                        }
1084
                    } else {
1085
                        $sql = "INSERT INTO %s (
1086
                                id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did
1087
                            )
1088
                            VALUES (
1089
                                NULL , '%s', '%s', %s, %s, %s
1090
                            )";
1091
                        $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id, $did);
1092
                        Database::query($sql);
1093
                    }
1094
                }
1095
1096
            }
1097
        }
1098
    }
1099
1100
    /**
1101
     * adds an exercise into the exercise list
1102
     *
1103
     * @author Olivier Brouckaert
1104
     * @param integer $exerciseId - exercise ID
1105
     * @param boolean $fromSave - from $this->save() or not
1106
     */
1107
    public function addToList($exerciseId, $fromSave = false)
1108
    {
1109
        $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1110
        $id = $this->id;
1111
        // checks if the exercise ID is not in the list
1112
        if (!in_array($exerciseId, $this->exerciseList)) {
1113
            $this->exerciseList[] = $exerciseId;
1114
            $new_exercise = new Exercise();
1115
            $new_exercise->read($exerciseId);
1116
            $count = $new_exercise->selectNbrQuestions();
1117
            $count++;
1118
            $sql = "INSERT INTO $exerciseRelQuestionTable (c_id, question_id, exercice_id, question_order)
1119
                    VALUES ({$this->course['real_id']}, " . intval($id) . ", " . intval($exerciseId) . ", '$count')";
1120
            Database::query($sql);
1121
1122
            // we do not want to reindex if we had just saved adnd indexed the question
1123
            if (!$fromSave) {
1124
                $this->search_engine_edit($exerciseId, TRUE);
1125
            }
1126
        }
1127
    }
1128
1129
    /**
1130
     * removes an exercise from the exercise list
1131
     *
1132
     * @author Olivier Brouckaert
1133
     * @param integer $exerciseId - exercise ID
1134
     * @return boolean - true if removed, otherwise false
1135
     */
1136
    function removeFromList($exerciseId)
1137
    {
1138
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1139
1140
        $id = $this->id;
1141
1142
        // searches the position of the exercise ID in the list
1143
        $pos = array_search($exerciseId, $this->exerciseList);
1144
1145
        $course_id = api_get_course_int_id();
1146
1147
        // exercise not found
1148
        if($pos === false) {
1149
            return false;
1150
        } else {
1151
            // deletes the position in the array containing the wanted exercise ID
1152
            unset($this->exerciseList[$pos]);
1153
            //update order of other elements
1154
            $sql = "SELECT question_order
1155
                    FROM $TBL_EXERCISE_QUESTION
1156
                    WHERE
1157
                        c_id = $course_id
1158
                        AND question_id = " . intval($id) . "
1159
                        AND exercice_id = " . intval($exerciseId);
1160
            $res = Database::query($sql);
1161 View Code Duplication
            if (Database::num_rows($res)>0) {
1162
                $row = Database::fetch_array($res);
1163
                if (!empty($row['question_order'])) {
1164
                    $sql = "UPDATE $TBL_EXERCISE_QUESTION
1165
                        SET question_order = question_order-1
1166
                        WHERE
1167
                            c_id = $course_id
1168
                            AND exercice_id = " . intval($exerciseId) . "
1169
                            AND question_order > " . $row['question_order'];
1170
                    Database::query($sql);
1171
                }
1172
            }
1173
1174
            $sql = "DELETE FROM $TBL_EXERCISE_QUESTION
1175
                    WHERE
1176
                        c_id = $course_id
1177
                        AND question_id = " . intval($id) . "
1178
                        AND exercice_id = " . intval($exerciseId);
1179
            Database::query($sql);
1180
1181
            return true;
1182
        }
1183
    }
1184
1185
    /**
1186
     * Deletes a question from the database
1187
     * the parameter tells if the question is removed from all exercises (value = 0),
1188
     * or just from one exercise (value = exercise ID)
1189
     *
1190
     * @author Olivier Brouckaert
1191
     * @param integer $deleteFromEx - exercise ID if the question is only removed from one exercise
1192
     */
1193
    public function delete($deleteFromEx = 0)
1194
    {
1195
        $course_id = api_get_course_int_id();
1196
1197
        $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1198
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1199
        $TBL_REPONSES = Database::get_course_table(TABLE_QUIZ_ANSWER);
1200
        $TBL_QUIZ_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
1201
1202
        $id = intval($this->id);
1203
1204
        // if the question must be removed from all exercises
1205
        if (!$deleteFromEx) {
1206
            //update the question_order of each question to avoid inconsistencies
1207
            $sql = "SELECT exercice_id, question_order FROM $TBL_EXERCISE_QUESTION
1208
                    WHERE c_id = $course_id AND question_id = ".intval($id)."";
1209
1210
            $res = Database::query($sql);
1211 View Code Duplication
            if (Database::num_rows($res) > 0) {
1212
                while ($row = Database::fetch_array($res)) {
1213
                    if (!empty($row['question_order'])) {
1214
                        $sql = "UPDATE $TBL_EXERCISE_QUESTION
1215
                                SET question_order = question_order-1
1216
                                WHERE
1217
                                    c_id= $course_id
1218
                                    AND exercice_id = " . intval($row['exercice_id']) . "
1219
                                    AND question_order > " . $row['question_order'];
1220
                        Database::query($sql);
1221
                    }
1222
                }
1223
            }
1224
1225
            $sql = "DELETE FROM $TBL_EXERCISE_QUESTION
1226
                    WHERE c_id = $course_id AND question_id = " . $id;
1227
            Database::query($sql);
1228
1229
            $sql = "DELETE FROM $TBL_QUESTIONS
1230
                    WHERE c_id = $course_id AND id = " . $id;
1231
            Database::query($sql);
1232
1233
            $sql = "DELETE FROM $TBL_REPONSES
1234
                    WHERE c_id = $course_id AND question_id = " . $id;
1235
            Database::query($sql);
1236
1237
            // remove the category of this question in the question_rel_category table
1238
            $sql = "DELETE FROM $TBL_QUIZ_QUESTION_REL_CATEGORY
1239
                    WHERE 
1240
                        c_id = $course_id AND 
1241
                        question_id = " . $id;
1242
            Database::query($sql);
1243
1244
            api_item_property_update(
1245
                $this->course,
1246
                TOOL_QUIZ,
1247
                $id,
1248
                'QuizQuestionDeleted',
1249
                api_get_user_id()
1250
            );
1251
            $this->removePicture();
1252
1253
        } else {
1254
            // just removes the exercise from the list
1255
            $this->removeFromList($deleteFromEx);
1256
            if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) {
1257
                // disassociate question with this exercise
1258
                $this->search_engine_edit($deleteFromEx, FALSE, TRUE);
1259
            }
1260
1261
            api_item_property_update(
1262
                $this->course,
1263
                TOOL_QUIZ,
1264
                $id,
1265
                'QuizQuestionDeleted',
1266
                api_get_user_id()
1267
            );
1268
        }
1269
    }
1270
1271
    /**
1272
     * Duplicates the question
1273
     *
1274
     * @author Olivier Brouckaert
1275
     * @param  array   $course_info Course info of the destination course
1276
     * @return int     ID of the new question
1277
     */
1278
    public function duplicate($course_info = [])
1279
    {
1280
        if (empty($course_info)) {
1281
            $course_info = $this->course;
1282
        } else {
1283
            $course_info = $course_info;
0 ignored issues
show
Bug introduced by
Why assign $course_info to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
1284
        }
1285
        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
1286
        $TBL_QUESTION_OPTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1287
1288
        $question = $this->question;
1289
        $description = $this->description;
1290
        $weighting = $this->weighting;
1291
        $position = $this->position;
1292
        $type = $this->type;
1293
        $level = intval($this->level);
1294
        $extra = $this->extra;
1295
1296
        //Using the same method used in the course copy to transform URLs
1297
1298
        if ($this->course['id'] != $course_info['id']) {
1299
            $description = DocumentManager::replace_urls_inside_content_html_from_copy_course(
1300
                $description,
1301
                $this->course['code'],
1302
                $course_info['id']
1303
            );
1304
            $question = DocumentManager::replace_urls_inside_content_html_from_copy_course(
1305
                $question,
1306
                $this->course['code'],
1307
                $course_info['id']
1308
            );
1309
        }
1310
1311
        $course_id = $course_info['real_id'];
1312
1313
        //Read the source options
1314
        $options = self::readQuestionOption($this->id, $this->course['real_id']);
1315
1316
        // Inserting in the new course db / or the same course db
1317
        $params = [
1318
            'c_id' => $course_id,
1319
            'question' => $question,
1320
            'description' => $description,
1321
            'ponderation' => $weighting,
1322
            'position' => $position,
1323
            'type' => $type,
1324
            'level' => $level,
1325
            'extra' => $extra
1326
        ];
1327
        $new_question_id = Database::insert($TBL_QUESTIONS, $params);
1328
1329
        if ($new_question_id) {
1330
1331
            $sql = "UPDATE $TBL_QUESTIONS SET id = iid
1332
                    WHERE iid = $new_question_id";
1333
            Database::query($sql);
1334
1335
            if (!empty($options)) {
1336
                //Saving the quiz_options
1337
                foreach ($options as $item) {
1338
                    $item['question_id'] = $new_question_id;
1339
                    $item['c_id'] = $course_id;
1340
                    unset($item['id']);
1341
                    unset($item['iid']);
1342
                    $id = Database::insert($TBL_QUESTION_OPTIONS, $item);
1343
                    if ($id) {
1344
                        $sql = "UPDATE $TBL_QUESTION_OPTIONS SET id = iid
1345
                                WHERE iid = $id";
1346
                        Database::query($sql);
1347
                    }
1348
                }
1349
            }
1350
1351
            // Duplicates the picture of the hotspot
1352
            $this->exportPicture($new_question_id, $course_info);
1353
        }
1354
1355
        return $new_question_id;
1356
    }
1357
1358
    /**
1359
     * @return string
1360
     */
1361
    public function get_question_type_name()
1362
    {
1363
        $key = self::$questionTypes[$this->type];
1364
        
1365
        return get_lang($key[1]);
1366
    }
1367
1368
    /**
1369
     * @param string $type
1370
     * @return null
1371
     */
1372
    public static function get_question_type($type)
1373
    {
1374
        if ($type == ORAL_EXPRESSION && api_get_setting('enable_nanogong') != 'true') {
1375
            return null;
1376
        }
1377
        return self::$questionTypes[$type];
1378
    }
1379
1380
    /**
1381
     * @return array
1382
     */
1383
    public static function get_question_type_list()
1384
    {
1385
        if (api_get_setting('enable_nanogong') != 'true') {
1386
            self::$questionTypes[ORAL_EXPRESSION] = null;
1387
            unset(self::$questionTypes[ORAL_EXPRESSION]);
1388
        }
1389
        if (api_get_setting('enable_quiz_scenario') !== 'true') {
1390
            self::$questionTypes[HOT_SPOT_DELINEATION] = null;
1391
            unset(self::$questionTypes[HOT_SPOT_DELINEATION]);
1392
        }
1393
        return self::$questionTypes;
1394
    }
1395
1396
    /**
1397
     * Returns an instance of the class corresponding to the type
1398
     * @param integer $type the type of the question
1399
     * @return $this instance of a Question subclass (or of Questionc class by default)
1400
     */
1401
    public static function getInstance($type)
1402
    {
1403
        if (!is_null($type)) {
1404
            list($file_name, $class_name) = self::get_question_type($type);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to list($file_name, $class_name) is correct as self::get_question_type($type) (which targets Question::get_question_type()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1405
            if (!empty($file_name)) {
1406
                include_once $file_name;
1407
                if (class_exists($class_name)) {
1408
                    return new $class_name();
1409
                } else {
1410
                    echo 'Can\'t instanciate class ' . $class_name . ' of type ' . $type;
1411
                }
1412
            }
1413
        }
1414
1415
        return null;
1416
    }
1417
1418
    /**
1419
     * Creates the form to create / edit a question
1420
     * A subclass can redefine this function to add fields...
1421
     * @param FormValidator $form
1422
     */
1423
    public function createForm(&$form)
1424
    {
1425
        echo '<style>
1426
                .media { display:none;}
1427
            </style>';
1428
        echo '<script>
1429
        // hack to hide http://cksource.com/forums/viewtopic.php?f=6&t=8700
1430
        function FCKeditor_OnComplete( editorInstance ) {
1431
            if (document.getElementById ( \'HiddenFCK\' + editorInstance.Name)) {
1432
                HideFCKEditorByInstanceName (editorInstance.Name);
1433
            }
1434
        }
1435
1436
        function HideFCKEditorByInstanceName ( editorInstanceName ) {
1437
            if (document.getElementById ( \'HiddenFCK\' + editorInstanceName ).className == "HideFCKEditor" ) {
1438
                document.getElementById ( \'HiddenFCK\' + editorInstanceName ).className = "media";
1439
            }
1440
        }
1441
        </script>';
1442
1443
        // question name
1444
        $form->addElement('text', 'questionName', get_lang('Question'));
1445
        $form->addRule('questionName', get_lang('GiveQuestion'), 'required');
1446
1447
        // default content
1448
        $isContent = isset($_REQUEST['isContent']) ? intval($_REQUEST['isContent']) : null;
1449
1450
        // Question type
1451
        $answerType = isset($_REQUEST['answerType']) ? intval($_REQUEST['answerType']) : null;
1452
        $form->addElement('hidden','answerType', $answerType);
1453
1454
        // html editor
1455
        $editorConfig = array(
1456
            'ToolbarSet' => 'TestQuestionDescription',
1457
            'Height' => '150'
1458
        );
1459
1460
        if (!api_is_allowed_to_edit(null,true)) {
1461
            $editorConfig['UserStatus'] = 'student';
1462
        }
1463
1464
        $form->addButtonAdvancedSettings('advanced_params');
1465
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
1466
1467
        $form->addHtmlEditor('questionDescription', get_lang('QuestionDescription'), false, false, $editorConfig);
1468
1469
        // hidden values
1470
        $my_id = isset($_REQUEST['myid']) ? intval($_REQUEST['myid']) : null;
1471
        $form->addElement('hidden', 'myid', $my_id);
1472
1473
        if ($this->type != MEDIA_QUESTION) {
1474
1475
            // Advanced parameters
1476
1477
            $select_level = Question::get_default_levels();
1478
            $form->addElement('select', 'questionLevel', get_lang('Difficulty'), $select_level);
1479
1480
            // Categories
1481
            $tabCat = TestCategory::getCategoriesIdAndName();
1482
            $form->addElement('select', 'questionCategory', get_lang('Category'), $tabCat);
1483
1484
            switch ($this->type) {
1485
                case UNIQUE_ANSWER:
1486
                    $form->addButton('convertAnswer', get_lang('ConvertToMultipleAnswer'), 'dot-circle-o', 'info');
1487
                    break;
1488
                case MULTIPLE_ANSWER:
1489
                    $form->addButton('convertAnswer', get_lang('ConvertToUniqueAnswer'), 'check-square-o', 'info');
1490
                    break;
1491
            }
1492
1493
            //Medias
1494
            //$course_medias = Question::prepare_course_media_select(api_get_course_int_id());
1495
            //$form->addElement('select', 'parent_id', get_lang('AttachToMedia'), $course_medias);
1496
        }
1497
1498
        $form->addElement('html', '</div>');
1499
1500
        if (!isset($_GET['fromExercise'])) {
1501
            switch ($answerType) {
1502
                case 1:
1503
                    $this->question = get_lang('DefaultUniqueQuestion');
1504
                    break;
1505
                case 2:
1506
                    $this->question = get_lang('DefaultMultipleQuestion');
1507
                    break;
1508
                case 3:
1509
                    $this->question = get_lang('DefaultFillBlankQuestion');
1510
                    break;
1511
                case 4:
1512
                    $this->question = get_lang('DefaultMathingQuestion');
1513
                    break;
1514
                case 5:
1515
                    $this->question = get_lang('DefaultOpenQuestion');
1516
                    break;
1517
                case 9:
1518
                    $this->question = get_lang('DefaultMultipleQuestion');
1519
                    break;
1520
            }
1521
        }
1522
1523
        // default values
1524
        $defaults = array();
1525
        $defaults['questionName'] = $this->question;
1526
        $defaults['questionDescription'] = $this->description;
1527
        $defaults['questionLevel'] = $this->level;
1528
        $defaults['questionCategory'] = $this->category;
1529
1530
        // Came from he question pool
1531
        if (isset($_GET['fromExercise'])) {
1532
            $form->setDefaults($defaults);
1533
        }
1534
1535
        if (!empty($_REQUEST['myid'])) {
1536
            $form->setDefaults($defaults);
1537
        } else {
1538
            if ($isContent == 1) {
1539
                $form->setDefaults($defaults);
1540
            }
1541
        }
1542
    }
1543
1544
    /**
1545
     * function which process the creation of questions
1546
     * @param FormValidator $form
1547
     * @param Exercise $objExercise
1548
     */
1549
    public function processCreation($form, $objExercise = null)
1550
    {
1551
        $this->updateTitle($form->getSubmitValue('questionName'));
1552
        $this->updateDescription($form->getSubmitValue('questionDescription'));
1553
        $this->updateLevel($form->getSubmitValue('questionLevel'));
1554
        $this->updateCategory($form->getSubmitValue('questionCategory'));
1555
1556
        //Save normal question if NOT media
1557
        if ($this->type != MEDIA_QUESTION) {
1558
            $this->save($objExercise->id);
1559
1560
            // modify the exercise
1561
            $objExercise->addToList($this->id);
1562
            $objExercise->update_question_positions();
1563
        }
1564
    }
1565
1566
    /**
1567
     * abstract function which creates the form to create / edit the answers of the question
1568
     * @param the FormValidator instance
1569
     */
1570
    abstract function createAnswersForm($form);
1571
1572
    /**
1573
     * abstract function which process the creation of answers
1574
     * @param the FormValidator instance
1575
     */
1576
    abstract function processAnswersCreation($form);
1577
1578
    /**
1579
     * Displays the menu of question types
1580
     *
1581
     * @param Exercise $objExercise
1582
     */
1583
    public static function display_type_menu($objExercise)
1584
    {
1585
        $feedback_type = $objExercise->feedback_type;
1586
        $exerciseId = $objExercise->id;
1587
1588
        // 1. by default we show all the question types
1589
        $question_type_custom_list = self::get_question_type_list();
1590
1591
        if (!isset($feedback_type)) {
1592
            $feedback_type = 0;
1593
        }
1594
1595
        if ($feedback_type == 1) {
1596
            //2. but if it is a feedback DIRECT we only show the UNIQUE_ANSWER type that is currently available
1597
            $question_type_custom_list = array (
1598
                UNIQUE_ANSWER => self::$questionTypes[UNIQUE_ANSWER],
1599
                HOT_SPOT_DELINEATION => self::$questionTypes[HOT_SPOT_DELINEATION]
1600
            );
1601
        } else {
1602
            unset($question_type_custom_list[HOT_SPOT_DELINEATION]);
1603
        }
1604
1605
        echo '<div class="well">';
1606
        echo '<ul class="question_menu">';
1607
1608
        foreach ($question_type_custom_list as $i => $a_type) {
1609
            // include the class of the type
1610
            require_once $a_type[0];
1611
            // get the picture of the type and the langvar which describes it
1612
            $img = $explanation = '';
1613
            eval('$img = ' . $a_type[1] . '::$typePicture;');
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1614
            eval('$explanation = get_lang(' . $a_type[1] . '::$explanationLangVar);');
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1615
            echo '<li>';
1616
            echo '<div class="icon-image">';
1617
            if ($objExercise->exercise_was_added_in_lp == true) {
1618
                $img = pathinfo($img);
1619
                $img = $img['filename'] . '_na.' . $img['extension'];
1620
                echo Display::return_icon($img, $explanation, null, ICON_SIZE_BIG);
1621
            } else {
1622
                echo '<a href="admin.php?' . api_get_cidreq() . '&newQuestion=yes&answerType=' . $i . '">' .
1623
                Display::return_icon($img, $explanation, null, ICON_SIZE_BIG) . '</a>';
1624
            }
1625
            echo '</div>';
1626
            echo '</li>';
1627
        }
1628
1629
        echo '<li>';
1630
        echo '<div class="icon_image_content">';
1631
        if ($objExercise->exercise_was_added_in_lp == true) {
1632
            echo Display::return_icon('database_na.png', get_lang('GetExistingQuestion'), null, ICON_SIZE_BIG);
1633
        } else {
1634
            if ($feedback_type == 1) {
1635
                echo $url = "<a href=\"question_pool.php?" . api_get_cidreq() . "&type=1&fromExercise=$exerciseId\">";
1636
            } else {
1637
                echo $url = '<a href="question_pool.php?' . api_get_cidreq() . '&fromExercise=' . $exerciseId . '">';
1638
            }
1639
            echo Display::return_icon('database.png', get_lang('GetExistingQuestion'), null, ICON_SIZE_BIG);
1640
        }
1641
        echo '</a>';
1642
        echo '</div></li>';
1643
        echo '</ul>';
1644
        echo '</div>';
1645
    }
1646
1647
    /**
1648
     * @param int $question_id
1649
     * @param string $name
1650
     * @param int $course_id
1651
     * @param int $position
1652
     * @return bool|int
1653
     */
1654
    static function saveQuestionOption($question_id, $name, $course_id, $position = 0)
1655
    {
1656
        $TBL_EXERCISE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1657
        $params['question_id'] = intval($question_id);
1658
        $params['name'] = $name;
1659
        $params['position'] = $position;
1660
        $params['c_id'] = $course_id;
1661
        $result = self::readQuestionOption($question_id, $course_id);
1662
        $last_id = Database::insert($TBL_EXERCISE_QUESTION_OPTION, $params);
1663
        if ($last_id) {
1664
            $sql = "UPDATE $TBL_EXERCISE_QUESTION_OPTION SET id = iid WHERE iid = $last_id";
1665
            Database::query($sql);
1666
        }
1667
1668
        return $last_id;
1669
    }
1670
1671
    /**
1672
     * @param int $question_id
1673
     * @param int $course_id
1674
     */
1675
    static function deleteAllQuestionOptions($question_id, $course_id)
1676
    {
1677
        $TBL_EXERCISE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
1678
        Database::delete(
1679
            $TBL_EXERCISE_QUESTION_OPTION,
1680
            array(
1681
                'c_id = ? AND question_id = ?' => array(
1682
                    $course_id,
1683
                    $question_id
1684
                )
1685
            )
1686
        );
1687
    }
1688
1689
    /**
1690
     * @param int $id
1691
     * @param array $params
1692
     * @param int $course_id
1693
     * @return bool|int
1694
     */
1695
    static function updateQuestionOption($id, $params, $course_id)
1696
    {
1697
        $TBL_EXERCISE_QUESTION_OPTION = Database::get_course_table(
1698
            TABLE_QUIZ_QUESTION_OPTION
1699
        );
1700
        $result = Database::update(
1701
            $TBL_EXERCISE_QUESTION_OPTION,
1702
            $params,
1703
            array('c_id = ? AND id = ?' => array($course_id, $id))
1704
        );
1705
        return $result;
1706
    }
1707
1708
    /**
1709
     * @param int $question_id
1710
     * @param int $course_id
1711
     * @return array
1712
     */
1713
    static function readQuestionOption($question_id, $course_id)
1714
    {
1715
        $TBL_EXERCISE_QUESTION_OPTION = Database::get_course_table(
1716
            TABLE_QUIZ_QUESTION_OPTION
1717
        );
1718
        $result = Database::select(
1719
            '*',
1720
            $TBL_EXERCISE_QUESTION_OPTION,
1721
            array(
1722
                'where' => array(
1723
                    'c_id = ? AND question_id = ?' => array(
1724
                        $course_id,
1725
                        $question_id
1726
                    )
1727
                ),
1728
                'order' => 'id ASC'
1729
            )
1730
        );
1731
1732
        return $result;
1733
    }
1734
1735
    /**
1736
     * Shows question title an description
1737
     *
1738
     * @param string $feedback_type
1739
     * @param int $counter
1740
     * @param float $score
1741
     */
1742
    function return_header($feedback_type = null, $counter = null, $score = null)
1743
    {
1744
        $counter_label = '';
1745
        if (!empty($counter)) {
1746
            $counter_label = intval($counter);
1747
        }
1748
        $score_label = get_lang('Wrong');
1749
        $class = 'error';
1750
        if ($score['pass'] == true) {
1751
            $score_label = get_lang('Correct');
1752
            $class = 'success';
1753
        }
1754
1755
        if ($this->type == FREE_ANSWER || $this->type == ORAL_EXPRESSION) {
1756
            $score['revised'] = isset($score['revised']) ? $score['revised'] : false;
1757
            if ($score['revised'] == true) {
1758
                $score_label = get_lang('Revised');
1759
                $class = '';
1760
            } else {
1761
                $score_label = get_lang('NotRevised');
1762
                $class = 'error';
1763
            }
1764
        }
1765
        $question_title = $this->question;
1766
1767
        // display question category, if any
1768
        $header = TestCategory::returnCategoryAndTitle($this->id);
1769
        $show_media = null;
1770
        if ($show_media) {
1771
            $header .= $this->show_media_content();
1772
        }
1773
1774
        $header .= Display::page_subheader2($counter_label . ". " . $question_title);
1775
        $header .= Display::div(
1776
            "<div class=\"rib rib-$class\"><h3>$score_label</h3></div> <h4>{$score['result']}</h4>",
1777
            array('class' => 'ribbon')
1778
        );
1779
        $header .= Display::div($this->description, array('id' => 'question_description'));
1780
1781
        return $header;
1782
    }
1783
1784
    /**
1785
     * Create a question from a set of parameters
1786
     * @param   int     Quiz ID
1787
     * @param   string  Question name
1788
     * @param   int     Maximum result for the question
1789
     * @param   int     Type of question (see constants at beginning of question.class.php)
1790
     * @param   int     Question level/category
1791
     */
1792
    public function create_question(
1793
        $quiz_id,
1794
        $question_name,
1795
        $question_description = "" ,
1796
        $max_score = 0,
1797
        $type = 1,
1798
        $level = 1
1799
    ) {
1800
        $course_id = api_get_course_int_id();
1801
1802
        $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1803
        $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
1804
1805
        $quiz_id = intval($quiz_id);
1806
        $max_score = (float) $max_score;
1807
        $type = intval($type);
1808
        $level = intval($level);
1809
1810
        // Get the max position
1811
        $sql = "SELECT max(position) as max_position
1812
                FROM $tbl_quiz_question q INNER JOIN $tbl_quiz_rel_question r
1813
                ON
1814
                    q.id = r.question_id AND
1815
                    exercice_id = $quiz_id AND
1816
                    q.c_id = $course_id AND
1817
                    r.c_id = $course_id";
1818
        $rs_max = Database::query($sql);
1819
        $row_max = Database::fetch_object($rs_max);
1820
        $max_position = $row_max->max_position + 1;
1821
1822
        $params = [
1823
            'c_id' => $course_id,
1824
            'question' => $question_name,
1825
            'description' => $question_description,
1826
            'ponderation' => $max_score,
1827
            'position' => $max_position,
1828
            'type' => $type,
1829
            'level' => $level,
1830
        ];
1831
        $question_id = Database::insert($tbl_quiz_question, $params);
1832
1833
        if ($question_id) {
1834
1835
            $sql = "UPDATE $tbl_quiz_question SET id = iid WHERE iid = $question_id";
1836
            Database::query($sql);
1837
1838
            // Get the max question_order
1839
            $sql = "SELECT max(question_order) as max_order
1840
                    FROM $tbl_quiz_rel_question
1841
                    WHERE c_id = $course_id AND exercice_id = $quiz_id ";
1842
            $rs_max_order = Database::query($sql);
1843
            $row_max_order = Database::fetch_object($rs_max_order);
1844
            $max_order = $row_max_order->max_order + 1;
1845
            // Attach questions to quiz
1846
            $sql = "INSERT INTO $tbl_quiz_rel_question (c_id, question_id, exercice_id, question_order)
1847
                    VALUES($course_id, $question_id, $quiz_id, $max_order)";
1848
            Database::query($sql);
1849
        }
1850
1851
        return $question_id;
1852
    }
1853
1854
    /**
1855
     * @return array the image filename of the question type
1856
     */
1857
    public function get_type_icon_html()
1858
    {
1859
        $type = $this->selectType();
1860
        $tabQuestionList = Question::get_question_type_list(); // [0]=file to include [1]=type name
1861
1862
        require_once $tabQuestionList[$type][0];
1863
1864
        $img = $explanation = null;
1865
        eval('$img = ' . $tabQuestionList[$type][1] . '::$typePicture;');
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1866
        eval('$explanation = get_lang(' . $tabQuestionList[$type][1] . '::$explanationLangVar);');
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1867
        return array($img, $explanation);
1868
    }
1869
1870
    /**
1871
     * Get course medias
1872
     * @param int course id
1873
     */
1874
    static function get_course_medias(
1875
        $course_id,
1876
        $start = 0,
1877
        $limit = 100,
1878
        $sidx = "question",
1879
        $sord = "ASC",
1880
        $where_condition = array()
1881
    ) {
1882
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1883
        $default_where = array('c_id = ? AND parent_id = 0 AND type = ?' => array($course_id, MEDIA_QUESTION));
1884
        $result = Database::select(
1885
            '*',
1886
            $table_question,
1887
            array(
1888
                'limit' => " $start, $limit",
1889
                'where' => $default_where,
1890
                'order' => "$sidx $sord"
1891
            )
1892
        );
1893
1894
        return $result;
1895
    }
1896
1897
    /**
1898
     * Get count course medias
1899
     * @param int course id
1900
     *
1901
     * @return int
1902
     */
1903
    static function get_count_course_medias($course_id)
1904
    {
1905
        $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
1906
        $result = Database::select(
1907
                'count(*) as count',
1908
            $table_question,
1909
            array('where' => array('c_id = ? AND parent_id = 0 AND type = ?' => array($course_id, MEDIA_QUESTION))),
1910
            'first'
1911
        );
1912
1913
        if ($result && isset($result['count'])) {
1914
            return $result['count'];
1915
        }
1916
        return 0;
1917
    }
1918
1919
    /**
1920
     * @param int $course_id
1921
     * @return array
1922
     */
1923
    public static function prepare_course_media_select($course_id)
1924
    {
1925
        $medias = self::get_course_medias($course_id);
1926
        $media_list = array();
1927
        $media_list[0] = get_lang('NoMedia');
1928
1929
        if (!empty($medias)) {
1930
            foreach($medias as $media) {
1931
                $media_list[$media['id']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
1932
            }
1933
        }
1934
        return $media_list;
1935
    }
1936
1937
    /**
1938
     * @return array
1939
     */
1940
    public static function get_default_levels()
1941
    {
1942
        $select_level = array(
1943
            1 => 1,
1944
            2 => 2,
1945
            3 => 3,
1946
            4 => 4,
1947
            5 => 5
1948
        );
1949
        return $select_level;
1950
    }
1951
1952
    /**
1953
     * @return null|string
1954
     */
1955
    public function show_media_content()
1956
    {
1957
        $html = null;
1958
        if ($this->parent_id != 0) {
1959
            $parent_question = Question::read($this->parent_id);
1960
            $html = $parent_question->show_media_content();
1961
        } else {
1962
            $html .= Display::page_subheader($this->selectTitle());
1963
            $html .= $this->selectDescription();
1964
        }
1965
        return $html;
1966
    }
1967
1968
    /**
1969
     * Swap between unique and multiple type answers
1970
     * @return object
1971
     */
1972
    public function swapSimpleAnswerTypes()
1973
    {
1974
        $oppositeAnswers = array(
1975
            UNIQUE_ANSWER => MULTIPLE_ANSWER,
1976
            MULTIPLE_ANSWER => UNIQUE_ANSWER
1977
        );
1978
        $this->type = $oppositeAnswers[$this->type];
1979
        Database::update(
1980
            Database::get_course_table(TABLE_QUIZ_QUESTION),
1981
            array('type' => $this->type),
1982
            array('c_id = ? AND id = ?' => array($this->course['real_id'], $this->id))
1983
        );
1984
        $answerClasses = array(
1985
            UNIQUE_ANSWER => 'UniqueAnswer',
1986
            MULTIPLE_ANSWER => 'MultipleAnswer'
1987
        );
1988
        $swappedAnswer = new $answerClasses[$this->type];
1989
        foreach ($this as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $this of type this<Question> is not traversable.
Loading history...
1990
            $swappedAnswer->$key = $value;
1991
        }
1992
        return $swappedAnswer;
1993
    }
1994
}
1995