Issues (1868)

public/main/exercise/answer.class.php (7 issues)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Framework\Container;
6
use Chamilo\CourseBundle\Entity\CQuizAnswer;
7
use Chamilo\CourseBundle\Entity\CQuizQuestion;
8
9
/**
10
 * Class Answer
11
 * Allows to instantiate an object of type Answer
12
 * 5 arrays are created to receive the attributes of each answer belonging to a specified question.
13
 *
14
 * @author Olivier Brouckaert
15
 */
16
class Answer
17
{
18
    public $questionId;
19
20
    // these are arrays
21
    public $answer;
22
    public $correct;
23
    public $comment;
24
    public $weighting;
25
    public $position;
26
    public $hotspot_coordinates;
27
    public $hotspot_type;
28
    public $destination;
29
    // these arrays are used to save temporarily new answers
30
    // then they are moved into the arrays above or deleted in the event of cancellation
31
    public $new_answer;
32
    public $new_correct;
33
    public $new_comment;
34
    public $new_weighting;
35
    public $new_position;
36
    public $new_hotspot_coordinates;
37
    public $new_hotspot_type;
38
    public $autoId;
39
    /** @var int Number of answers in the question */
40
    public $nbrAnswers;
41
    public $new_nbrAnswers;
42
    public $new_destination; // id of the next question if feedback option is set to Directfeedback
43
    public $course; //Course information
44
    public $iid;
45
    public $questionJSId;
46
    public $standalone;
47
    /** @var Exercise|null */
48
    private $exercise;
49
50
    /**
51
     * @author Olivier Brouckaert
52
     *
53
     * @param int      $questionId that answers belong to
54
     * @param int      $course_id
55
     * @param Exercise $exercise
56
     * @param bool     $readAnswer
57
     */
58
    public function __construct($questionId, $course_id = 0, $exercise = null, $readAnswer = true)
59
    {
60
        $this->questionId = (int) $questionId;
61
        $this->answer = [];
62
        $this->correct = [];
63
        $this->comment = [];
64
        $this->weighting = [];
65
        $this->position = [];
66
        $this->hotspot_coordinates = [];
67
        $this->hotspot_type = [];
68
        $this->destination = [];
69
        // clears $new_* arrays
70
        $this->cancel();
71
72
        if (!empty($course_id)) {
73
            $courseInfo = api_get_course_info_by_id($course_id);
74
        } else {
75
            $courseInfo = api_get_course_info();
76
        }
77
78
        $this->course = $courseInfo;
79
        $this->course_id = $courseInfo['real_id'];
80
81
        if (empty($exercise)) {
82
            // fills arrays
83
            $objExercise = new Exercise($this->course_id);
84
            $exerciseId = isset($_REQUEST['exerciseId']) ? $_REQUEST['exerciseId'] : null;
85
            $objExercise->read($exerciseId, false);
86
        } else {
87
            $objExercise = $exercise;
88
        }
89
        $this->exercise = $objExercise;
90
91
        if ($readAnswer) {
92
            if ('1' == $objExercise->random_answers && CALCULATED_ANSWER != $this->getQuestionType()) {
93
                $this->readOrderedBy('rand()', ''); // randomize answers
94
            } else {
95
                $this->read(); // natural order
96
            }
97
        }
98
    }
99
100
    /**
101
     * Clears $new_* arrays.
102
     *
103
     * @author Olivier Brouckaert
104
     */
105
    public function cancel()
106
    {
107
        $this->new_answer = [];
108
        $this->new_correct = [];
109
        $this->new_comment = [];
110
        $this->new_weighting = [];
111
        $this->new_position = [];
112
        $this->new_hotspot_coordinates = [];
113
        $this->new_hotspot_type = [];
114
        $this->new_nbrAnswers = 0;
115
        $this->new_destination = [];
116
    }
117
118
    /**
119
     * Reads answer information from the database.
120
     *
121
     * @author Olivier Brouckaert
122
     */
123
    public function read()
124
    {
125
        $table = Database::get_course_table(TABLE_QUIZ_ANSWER);
126
        $questionId = $this->questionId;
127
128
        $sql = "SELECT * FROM $table
129
                WHERE
130
                    question_id ='".$questionId."'
131
                ORDER BY position";
132
133
        $result = Database::query($sql);
134
        $i = 1;
135
136
        // while a record is found
137
        while ($object = Database::fetch_object($result)) {
138
            $this->id[$i] = $object->iid;
139
            $this->autoId[$i] = $object->iid;
140
            $this->iid[$i] = $object->iid;
141
            $this->answer[$i] = $object->answer;
142
            $this->correct[$i] = $object->correct;
143
            $this->comment[$i] = $object->comment;
144
            $this->weighting[$i] = $object->ponderation;
145
            $this->position[$i] = $object->position;
146
            $this->hotspot_coordinates[$i] = $object->hotspot_coordinates;
147
            $this->hotspot_type[$i] = $object->hotspot_type;
148
            $i++;
149
        }
150
        $this->nbrAnswers = $i - 1;
151
    }
152
153
    /**
154
     * @param int $id
155
     *
156
     * @return array
157
     */
158
    public function getAnswerByAutoId($id)
159
    {
160
        foreach ($this->autoId as $key => $autoId) {
161
            if ($autoId == $id) {
162
                return [
163
                    'answer' => $this->answer[$key],
164
                    'correct' => $this->correct[$key],
165
                    'comment' => $this->comment[$key],
166
                ];
167
            }
168
        }
169
170
        return [];
171
    }
172
173
    /**
174
     * returns all answer ids from this question Id.
175
     *
176
     * @author Yoselyn Castillo
177
     *
178
     * @return array - $id (answer ids)
179
     */
180
    public function selectAnswerId()
181
    {
182
        $table = Database::get_course_table(TABLE_QUIZ_ANSWER);
183
        $questionId = $this->questionId;
184
185
        $sql = "SELECT id FROM
186
              $table
187
              WHERE c_id = {$this->course_id} AND question_id ='".$questionId."'";
188
189
        $result = Database::query($sql);
190
        $id = [];
191
        // while a record is found
192
        if (Database::num_rows($result) > 0) {
193
            while ($object = Database::fetch_array($result)) {
194
                $id[] = $object['id'];
195
            }
196
        }
197
198
        return $id;
199
    }
200
201
    /**
202
     * Reads answer information from the data base ordered by parameter.
203
     *
204
     * @param string $field Field we want to order by
205
     * @param string $order DESC or ASC
206
     *
207
     * @return bool
208
     *
209
     * @author Frederic Vauthier
210
     */
211
    public function readOrderedBy($field, $order = 'ASC')
212
    {
213
        $field = Database::escape_string($field);
214
        if (empty($field)) {
215
            $field = 'position';
216
        }
217
218
        if ('ASC' != $order && 'DESC' != $order) {
219
            $order = 'ASC';
220
        }
221
222
        $TBL_ANSWER = Database::get_course_table(TABLE_QUIZ_ANSWER);
223
        $TBL_QUIZ = Database::get_course_table(TABLE_QUIZ_QUESTION);
224
        $questionId = (int) $this->questionId;
225
226
        $sql = "SELECT type FROM $TBL_QUIZ
227
                WHERE iid = $questionId";
228
        $result_question = Database::query($sql);
229
        $questionType = Database::fetch_array($result_question);
230
231
        if (DRAGGABLE == $questionType['type']) {
232
            // Random is done by submit.js.tpl
233
            $this->read();
234
235
            return true;
236
        }
237
238
        $sql = "SELECT
239
                    answer,
240
                    correct,
241
                    comment,
242
                    ponderation,
243
                    position,
244
                    hotspot_coordinates,
245
                    hotspot_type,
246
                    iid
247
                FROM $TBL_ANSWER
248
                WHERE
249
                    question_id='".$questionId."'
250
                ORDER BY $field $order";
251
        $result = Database::query($sql);
252
253
        $i = 1;
254
        // while a record is found
255
        $doubt_data = null;
256
        while ($object = Database::fetch_object($result)) {
257
            if (UNIQUE_ANSWER_NO_OPTION == $questionType['type'] && 666 == $object->position) {
258
                $doubt_data = $object;
259
260
                continue;
261
            }
262
            $this->answer[$i] = $object->answer;
263
            $this->correct[$i] = $object->correct;
264
            $this->comment[$i] = $object->comment;
265
            $this->weighting[$i] = $object->ponderation;
266
            $this->position[$i] = $object->position;
267
            $this->hotspot_coordinates[$i] = $object->hotspot_coordinates;
268
            $this->hotspot_type[$i] = $object->hotspot_type;
269
            $this->autoId[$i] = $object->iid;
270
            $this->iid[$i] = $object->iid;
271
            $i++;
272
        }
273
274
        if (UNIQUE_ANSWER_NO_OPTION == $questionType['type'] && !empty($doubt_data)) {
275
            $this->answer[$i] = $doubt_data->answer;
276
            $this->correct[$i] = $doubt_data->correct;
277
            $this->comment[$i] = $doubt_data->comment;
278
            $this->weighting[$i] = $doubt_data->ponderation;
279
            $this->position[$i] = $doubt_data->position;
280
            $this->hotspot_coordinates[$i] = isset($object->hotspot_coordinates) ? $object->hotspot_coordinates : 0;
281
            $this->hotspot_type[$i] = isset($object->hotspot_type) ? $object->hotspot_type : 0;
282
            $this->destination[$i] = $doubt_data->destination;
283
            $this->autoId[$i] = $doubt_data->iid;
284
            $this->iid[$i] = $doubt_data->iid;
285
            $i++;
286
        }
287
        $this->nbrAnswers = $i - 1;
288
289
        return true;
290
    }
291
292
    /**
293
     * returns the autoincrement id.
294
     *
295
     * @author Juan Carlos Ra�a
296
     *
297
     * @return int - answer num
298
     */
299
    public function selectAutoId($id)
300
    {
301
        return isset($this->iid[$id]) ? (int) $this->iid[$id] : 0;
302
    }
303
304
    /**
305
     * returns the number of answers in this question.
306
     *
307
     * @author Olivier Brouckaert
308
     *
309
     * @return int - number of answers
310
     */
311
    public function selectNbrAnswers()
312
    {
313
        return $this->nbrAnswers;
314
    }
315
316
    /**
317
     * returns the question ID which the answers belong to.
318
     *
319
     * @author Olivier Brouckaert
320
     *
321
     * @return int - the question ID
322
     */
323
    public function selectQuestionId()
324
    {
325
        return $this->questionId;
326
    }
327
328
    /**
329
     * returns the question ID of the destination question.
330
     *
331
     * @author Julio Montoya
332
     *
333
     * @param int $id
334
     *
335
     * @return int - the question ID
336
     */
337
    public function selectDestination($id)
338
    {
339
        return isset($this->destination[$id]) ? $this->destination[$id] : null;
340
    }
341
342
    /**
343
     * returns the answer title.
344
     *
345
     * @author Olivier Brouckaert
346
     *
347
     * @param - integer $id - answer ID
348
     *
349
     * @return string - answer title
350
     */
351
    public function selectAnswer($id)
352
    {
353
        return isset($this->answer[$id]) ? $this->answer[$id] : null;
354
    }
355
356
    /**
357
     * return array answer by id else return a bool.
358
     *
359
     * @param int $auto_id
360
     *
361
     * @return array
362
     */
363
    public function selectAnswerByAutoId($auto_id)
364
    {
365
        $table = Database::get_course_table(TABLE_QUIZ_ANSWER);
366
        $auto_id = (int) $auto_id;
367
        $sql = "SELECT iid, answer FROM $table
368
                WHERE iid='$auto_id'";
369
        $rs = Database::query($sql);
370
371
        if (Database::num_rows($rs) > 0) {
372
            return Database::fetch_assoc($rs);
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::fetch_assoc($rs) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
373
        }
374
375
        return false;
376
    }
377
378
    /**
379
     * returns the answer title from an answer's position.
380
     *
381
     * @author Yannick Warnier
382
     *
383
     * @param - integer $pos - answer ID
384
     *
385
     * @return bool - answer title
386
     */
387
    public function selectAnswerIdByPosition($pos)
388
    {
389
        foreach ($this->position as $k => $v) {
390
            if ($v != $pos) {
391
                continue;
392
            }
393
394
            return $k;
395
        }
396
397
        return false;
398
    }
399
400
    /**
401
     * Returns a list of answers.
402
     *
403
     * @author Yannick Warnier <[email protected]>
404
     *
405
     * @param bool $decode
406
     *
407
     * @return array List of answers where each answer is an array
408
     *               of (id, answer, comment, grade) and grade=weighting
409
     */
410
    public function getAnswersList($decode = false)
411
    {
412
        $list = [];
413
        for ($i = 1; $i <= $this->nbrAnswers; $i++) {
414
            if (!empty($this->answer[$i])) {
415
                //Avoid problems when parsing elements with accents
416
                if ($decode) {
417
                    $this->answer[$i] = api_html_entity_decode(
418
                        $this->answer[$i],
419
                        ENT_QUOTES,
420
                        api_get_system_encoding()
421
                    );
422
                    $this->comment[$i] = api_html_entity_decode(
423
                        $this->comment[$i],
424
                        ENT_QUOTES,
425
                        api_get_system_encoding()
426
                    );
427
                }
428
429
                $list[] = [
430
                    'id' => $i,
431
                    'answer' => $this->answer[$i],
432
                    'comment' => $this->comment[$i],
433
                    'grade' => $this->weighting[$i],
434
                    'hotspot_coord' => $this->hotspot_coordinates[$i],
435
                    'hotspot_type' => $this->hotspot_type[$i],
436
                    'correct' => $this->correct[$i],
437
                ];
438
            }
439
        }
440
441
        return $list;
442
    }
443
444
    /**
445
     * Returns a list of grades.
446
     *
447
     * @author Yannick Warnier <[email protected]>
448
     *
449
     * @return array List of grades where grade=weighting (?)
450
     */
451
    public function getGradesList()
452
    {
453
        $list = [];
454
        for ($i = 0; $i < $this->nbrAnswers; $i++) {
455
            if (!empty($this->answer[$i])) {
456
                $list[$i] = $this->weighting[$i];
457
            }
458
        }
459
460
        return $list;
461
    }
462
463
    /**
464
     * Returns the question type.
465
     *
466
     * @author    Yannick Warnier <[email protected]>
467
     *
468
     * @todo remove this function use CQuizQuestion
469
     *
470
     * @return int The type of the question this answer is bound to
471
     */
472
    public function getQuestionType()
473
    {
474
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION);
475
        $sql = "SELECT type FROM $table
476
                WHERE iid = '".$this->questionId."'";
477
        $res = Database::query($sql);
478
        if (Database::num_rows($res) <= 0) {
479
            return null;
480
        }
481
        $row = Database::fetch_array($res);
482
483
        return (int) $row['type'];
484
    }
485
486
    /**
487
     * tells if answer is correct or not.
488
     *
489
     * @author Olivier Brouckaert
490
     *
491
     * @param - integer $id - answer ID
492
     *
493
     * @return int - 0 if bad answer, not 0 if good answer
494
     */
495
    public function isCorrect($id)
496
    {
497
        return isset($this->correct[$id]) ? $this->correct[$id] : null;
498
    }
499
500
    /**
501
     * returns answer comment.
502
     *
503
     * @author Olivier Brouckaert
504
     *
505
     * @param - integer $id - answer ID
506
     *
507
     * @return string - answer comment
508
     */
509
    public function selectComment($id)
510
    {
511
        return isset($this->comment[$id]) ? $this->comment[$id] : null;
512
    }
513
514
    /**
515
     * returns answer weighting.
516
     *
517
     * @author Olivier Brouckaert
518
     *
519
     * @param - integer $id - answer ID
520
     *
521
     * @return int - answer weighting
522
     */
523
    public function selectWeighting($id)
524
    {
525
        return isset($this->weighting[$id]) ? $this->weighting[$id] : null;
526
    }
527
528
    /**
529
     * returns answer position.
530
     *
531
     * @author Olivier Brouckaert
532
     *
533
     * @param - integer $id - answer ID
534
     *
535
     * @return int - answer position
536
     */
537
    public function selectPosition($id)
538
    {
539
        return isset($this->position[$id]) ? $this->position[$id] : null;
540
    }
541
542
    /**
543
     * returns answer hotspot coordinates.
544
     *
545
     * @author Olivier Brouckaert
546
     *
547
     * @param int $id Answer ID
548
     *
549
     * @return int Answer position
550
     */
551
    public function selectHotspotCoordinates($id)
552
    {
553
        return isset($this->hotspot_coordinates[$id]) ? $this->hotspot_coordinates[$id] : null;
554
    }
555
556
    /**
557
     * returns answer hotspot type.
558
     *
559
     * @author Toon Keppens
560
     *
561
     * @param int $id Answer ID
562
     *
563
     * @return int Answer position
564
     */
565
    public function selectHotspotType($id)
566
    {
567
        return isset($this->hotspot_type[$id]) ? $this->hotspot_type[$id] : null;
568
    }
569
570
    /**
571
     * Creates a new answer.
572
     *
573
     * @author Olivier Brouckaert
574
     *
575
     * @param string $answer                  answer title
576
     * @param int    $correct                 0 if bad answer, not 0 if good answer
577
     * @param string $comment                 answer comment
578
     * @param int    $weighting               answer weighting
579
     * @param int    $position                answer position
580
     * @param array  $new_hotspot_coordinates Coordinates for hotspot exercises (optional)
581
     * @param int    $new_hotspot_type        Type for hotspot exercises (optional)
582
     * @param string $destination
583
     */
584
    public function createAnswer(
585
        $answer,
586
        $correct,
587
        $comment,
588
        $weighting,
589
        $position,
590
        $new_hotspot_coordinates = null,
591
        $new_hotspot_type = null,
592
        $destination = ''
593
    ) {
594
        $this->new_nbrAnswers++;
595
        $id = $this->new_nbrAnswers;
596
        $this->new_answer[$id] = $answer;
597
        $this->new_correct[$id] = $correct;
598
        $this->new_comment[$id] = $comment;
599
        $this->new_weighting[$id] = $weighting;
600
        $this->new_position[$id] = $position;
601
        $this->new_hotspot_coordinates[$id] = $new_hotspot_coordinates;
602
        $this->new_hotspot_type[$id] = $new_hotspot_type;
603
    }
604
605
    /**
606
     * Updates an answer.
607
     *
608
     * @author Toon Keppens
609
     *
610
     * @param int    $iid
611
     * @param string $answer
612
     * @param string $comment
613
     * @param string $correct
614
     * @param string $weighting
615
     * @param string $position
616
     * @param string $destination
617
     * @param string $hotSpotCoordinates
618
     * @param string $hotSpotType
619
     *
620
     * @return CQuizAnswer
621
     */
622
    public function updateAnswers(
623
        $iid,
624
        $answer,
625
        $comment,
626
        $correct,
627
        $weighting,
628
        $position,
629
        $destination,
630
        $hotSpotCoordinates,
631
        $hotSpotType
632
    ) {
633
        $em = Database::getManager();
634
635
        /** @var CQuizAnswer $quizAnswer */
636
        $quizAnswer = $em->find(CQuizAnswer::class, $iid);
637
        if ($quizAnswer) {
0 ignored issues
show
$quizAnswer is of type Chamilo\CourseBundle\Entity\CQuizAnswer, thus it always evaluated to true.
Loading history...
638
            $quizAnswer
639
                ->setAnswer((string) $answer)
640
                ->setComment((string) $comment)
641
                ->setCorrect((int) $correct)
642
                ->setPonderation($weighting)
643
                ->setPosition($position)
644
                ->setHotspotCoordinates($hotSpotCoordinates)
645
                ->setHotspotType($hotSpotType)
646
            ;
647
648
            $em->persist($quizAnswer);
649
            $em->flush();
650
651
            return $quizAnswer;
652
        }
653
654
        return false;
655
    }
656
657
    /**
658
     * Records answers into the data base.
659
     *
660
     * @author Olivier Brouckaert
661
     */
662
    public function save()
663
    {
664
        $answerTable = Database::get_course_table(TABLE_QUIZ_ANSWER);
665
        $em = Database::getManager();
666
        $questionId = (int) $this->questionId;
667
668
        $courseId = $this->course['real_id'];
669
        $answerList = [];
670
        $questionRepo = Container::getQuestionRepository();
671
        /** @var CQuizQuestion $question */
672
        $question = $questionRepo->find($questionId);
673
        $questionType = $question->getType();
674
675
        for ($i = 1; $i <= $this->new_nbrAnswers; $i++) {
676
            $answer = (string) $this->new_answer[$i];
677
            $correct = isset($this->new_correct[$i]) ? (int) $this->new_correct[$i] : null;
678
            $comment = isset($this->new_comment[$i]) ? $this->new_comment[$i] : null;
679
            $weighting = isset($this->new_weighting[$i]) ? (float) $this->new_weighting[$i] : null;
680
            $position = isset($this->new_position[$i]) ? $this->new_position[$i] : null;
681
            $hotspot_coordinates = isset($this->new_hotspot_coordinates[$i]) ? $this->new_hotspot_coordinates[$i] : null;
682
            $hotspot_type = isset($this->new_hotspot_type[$i]) ? $this->new_hotspot_type[$i] : null;
683
            $iid = isset($this->iid[$i]) ? $this->iid[$i] : 0;
684
685
            if (!isset($this->position[$i])) {
686
                $quizAnswer = new CQuizAnswer();
687
                $quizAnswer
688
                    ->setQuestion($question)
689
                    ->setAnswer($answer)
690
                    ->setCorrect($correct)
691
                    ->setComment($comment)
692
                    ->setPonderation(!is_null($weighting) ? $weighting : 0.0)
693
                    ->setPosition($position)
694
                    ->setHotspotCoordinates($hotspot_coordinates)
695
                    ->setHotspotType($hotspot_type);
696
697
                $em->persist($quizAnswer);
698
                $em->flush();
699
700
                $iid = $quizAnswer->getIid();
701
702
                if ($iid) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $iid of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
703
                    if (in_array($questionType, [MATCHING, MATCHING_DRAGGABLE])) {
704
                        $answer = new self($this->questionId, $courseId, $this->exercise, false);
705
                        $answer->read();
706
                        $correctAnswerId = $answer->selectAnswerIdByPosition($correct);
707
708
                        // Continue to avoid matching question bug if $correctAnswerId returns false
709
                        // See : https://support.chamilo.org/issues/8334
710
                        if (MATCHING == $questionType && !$correctAnswerId) {
711
                            continue;
712
                        }
713
                        $correctAnswerAutoId = $answer->selectAutoId($correct);
714
                        $quizAnswer->setCorrect($correctAnswerAutoId ?: 0);
715
                    }
716
717
                    $em->persist($quizAnswer);
718
                    $em->flush();
719
                }
720
            } else {
721
                // https://support.chamilo.org/issues/6558
722
                // function updateAnswers already escape_string, error if we do it twice.
723
                // Feed function updateAnswers with none escaped strings
724
                $this->updateAnswers(
725
                    $iid,
726
                    $this->new_answer[$i],
727
                    $this->new_comment[$i],
728
                    $this->new_correct[$i],
729
                    $this->new_weighting[$i],
730
                    $this->new_position[$i],
731
                    null,
732
                    $this->new_hotspot_coordinates[$i],
733
                    $this->new_hotspot_type[$i]
734
                );
735
            }
736
737
            $answerList[$i] = $iid;
738
        }
739
740
        switch ($questionType) {
741
            case MATCHING_DRAGGABLE:
742
                foreach ($this->new_correct as $value => $status) {
743
                    if (!empty($status)) {
744
                        if (isset($answerList[$status])) {
745
                            $correct = $answerList[$status];
746
                        } else {
747
                            $correct = $status;
748
                        }
749
                        $myAutoId = $answerList[$value];
750
                        $sql = "UPDATE $answerTable
751
                            SET correct = '$correct'
752
                            WHERE
753
                                iid = $myAutoId
754
                            ";
755
                        Database::query($sql);
756
                    }
757
                }
758
759
                break;
760
            case DRAGGABLE:
761
                foreach ($this->new_correct as $value => $status) {
762
                    if (!empty($status)) {
763
                        $correct = $answerList[$status];
764
                        $myAutoId = $answerList[$value];
765
                        $sql = "UPDATE $answerTable
766
                            SET correct = '$correct'
767
                            WHERE
768
                                iid = $myAutoId
769
                            ";
770
                        Database::query($sql);
771
                    }
772
                }
773
774
                break;
775
        }
776
777
        if (count($this->position) > $this->new_nbrAnswers) {
778
            $i = $this->new_nbrAnswers + 1;
779
            while (isset($this->position[$i])) {
780
                $position = $this->position[$i];
781
                $sql = "DELETE FROM $answerTable
782
                    WHERE
783
                        question_id = '".$questionId."' AND
784
                        position ='$position'";
785
                Database::query($sql);
786
                $i++;
787
            }
788
        }
789
790
        // moves $new_* arrays
791
        $this->answer = $this->new_answer;
792
        $this->correct = $this->new_correct;
793
        $this->comment = $this->new_comment;
794
        $this->weighting = $this->new_weighting;
795
        $this->position = $this->new_position;
796
        $this->hotspot_coordinates = $this->new_hotspot_coordinates;
797
        $this->hotspot_type = $this->new_hotspot_type;
798
        $this->nbrAnswers = $this->new_nbrAnswers;
799
800
        $this->cancel();
801
    }
802
803
    /**
804
     * Duplicates answers by copying them into another question.
805
     *
806
     * @author Olivier Brouckaert
807
     *
808
     * @param null $courseInfo destination course info (result of the function api_get_course_info() )
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $courseInfo is correct as it would always require null to be passed?
Loading history...
809
     *
810
     * @throws \Doctrine\ORM\ORMException
811
     * @throws \Doctrine\ORM\OptimisticLockException
812
     */
813
    public function duplicate(Question $newQuestion, $courseInfo = null)
814
    {
815
        $newQuestionId = $newQuestion->id;
816
817
        $em = Database::getManager();
818
        $questionRepo = Container::getQuestionRepository();
819
        $answerRepo = $em->getRepository(CQuizAnswer::class);
820
        $question = $questionRepo->find($newQuestionId);
821
822
        if (empty($courseInfo)) {
823
            $courseInfo = $this->course;
824
        }
825
826
        $fixedList = [];
827
828
        if (MULTIPLE_ANSWER_TRUE_FALSE === self::getQuestionType() ||
0 ignored issues
show
Bug Best Practice introduced by
The method Answer::getQuestionType() is not static, but was called statically. ( Ignorable by Annotation )

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

828
        if (MULTIPLE_ANSWER_TRUE_FALSE === self::/** @scrutinizer ignore-call */ getQuestionType() ||
Loading history...
829
            MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY === self::getQuestionType()
830
        ) {
831
            // Selecting origin options
832
            $originOptions = Question::readQuestionOption($this->selectQuestionId());
833
834
            if (!empty($originOptions)) {
835
                foreach ($originOptions as $item) {
836
                    $newOptionList[] = $item['iid'];
837
                }
838
            }
839
840
            $destinationOptions = Question::readQuestionOption($newQuestionId);
841
            $i = 0;
842
            if (!empty($destinationOptions)) {
843
                foreach ($destinationOptions as $item) {
844
                    $fixedList[$newOptionList[$i]] = $item['iid'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $newOptionList does not seem to be defined for all execution paths leading up to this point.
Loading history...
845
                    $i++;
846
                }
847
            }
848
        }
849
850
        // if at least one answer
851
        if (empty($this->nbrAnswers)) {
852
            return;
853
        }
854
        // inserts new answers into data base
855
        $correctAnswers = [];
856
        $onlyAnswers = [];
857
        $allAnswers = [];
858
859
        if (in_array($newQuestion->type, [MATCHING, MATCHING_DRAGGABLE])) {
860
            $temp = [];
861
            for ($i = 1; $i <= $this->nbrAnswers; $i++) {
862
                $answer = [
863
                    'id' => $this->id[$i],
864
                    'answer' => $this->answer[$i],
865
                    'correct' => $this->correct[$i],
866
                    'comment' => $this->comment[$i],
867
                    'weighting' => $this->weighting[$i],
868
                    'ponderation' => $this->weighting[$i],
869
                    'position' => $this->position[$i],
870
                    'hotspot_coordinates' => $this->hotspot_coordinates[$i],
871
                    'hotspot_type' => $this->hotspot_type[$i],
872
                ];
873
                $temp[$answer['position']] = $answer;
874
                $allAnswers[$this->id[$i]] = $this->answer[$i];
875
            }
876
877
            foreach ($temp as $answer) {
878
                if ($this->course['id'] != $courseInfo['id']) {
879
                    // check resources inside html from ckeditor tool and copy correct urls into recipient course
880
                    $answer['answer'] = DocumentManager::replaceUrlWithNewCourseCode(
881
                        $answer['answer'],
882
                        $this->course['id'],
883
                        $courseInfo['id']
884
                    );
885
886
                    $answer['comment'] = DocumentManager::replaceUrlWithNewCourseCode(
887
                        $answer['comment'],
888
                        $this->course['id'],
889
                        $courseInfo['id']
890
                    );
891
                }
892
893
                $quizAnswer = new CQuizAnswer();
894
                $quizAnswer
895
                    ->setQuestion($question)
896
                    ->setAnswer($answer['answer'])
897
                    ->setCorrect($answer['correct'])
898
                    ->setComment($answer['comment'])
899
                    ->setPonderation($answer['ponderation'])
900
                    ->setPosition($answer['position'])
901
                    ->setHotspotCoordinates($answer['hotspot_coordinates'])
902
                    ->setHotspotType($answer['hotspot_type'])
903
                ;
904
905
                $em->persist($quizAnswer);
906
                $em->flush();
907
908
                $answerId = $quizAnswer->getIid();
909
910
                if ($answerId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $answerId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
911
                    $em->persist($quizAnswer);
912
                    $em->flush();
913
914
                    $correctAnswers[$answerId] = $answer['correct'];
915
                    $onlyAnswers[$answerId] = $answer['answer'];
916
                }
917
            }
918
        } else {
919
            for ($i = 1; $i <= $this->nbrAnswers; $i++) {
920
                if ($this->course['id'] != $courseInfo['id']) {
921
                    $this->answer[$i] = DocumentManager::replaceUrlWithNewCourseCode(
922
                        $this->answer[$i],
923
                        $this->course['id'],
924
                        $courseInfo['id']
925
                    );
926
                    $this->comment[$i] = DocumentManager::replaceUrlWithNewCourseCode(
927
                        $this->comment[$i],
928
                        $this->course['id'],
929
                        $courseInfo['id']
930
                    );
931
                }
932
933
                $correct = $this->correct[$i];
934
                if (MULTIPLE_ANSWER_TRUE_FALSE == $newQuestion->type ||
935
                    MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY == $newQuestion->type
936
                ) {
937
                    $correct = $fixedList[(int) $correct];
938
                }
939
940
                $quizAnswer = new CQuizAnswer();
941
                $quizAnswer
942
                    ->setQuestion($question)
943
                    ->setAnswer($this->answer[$i])
944
                    ->setCorrect($correct)
945
                    ->setComment($this->comment[$i])
946
                    ->setPonderation($this->weighting[$i])
947
                    ->setPosition($this->position[$i])
948
                    ->setHotspotCoordinates($this->hotspot_coordinates[$i])
949
                    ->setHotspotType($this->hotspot_type[$i]);
950
951
                $em->persist($quizAnswer);
952
                $em->flush();
953
954
                $answerId = $quizAnswer->getIid();
955
956
                $correctAnswers[$answerId] = $correct;
957
                $onlyAnswers[$answerId] = $this->answer[$i];
958
                $allAnswers[$this->id[$i]] = $this->answer[$i];
959
            }
960
        }
961
962
        // Fix correct answers
963
        if (in_array($newQuestion->type, [DRAGGABLE, MATCHING, MATCHING_DRAGGABLE])) {
964
            $onlyAnswersFlip = array_flip($onlyAnswers);
965
            foreach ($correctAnswers as $answerIdItem => $correctAnswer) {
966
                if (!isset($allAnswers[$correctAnswer]) || !isset($onlyAnswersFlip[$allAnswers[$correctAnswer]])) {
967
                    continue;
968
                }
969
                $answer = $answerRepo->find($answerIdItem);
970
                $answer->setCorrect((int) $onlyAnswersFlip[$allAnswers[$correctAnswer]]);
971
972
                $em->persist($answer);
973
                $em->flush();
974
            }
975
        }
976
    }
977
978
    /**
979
     * Get the necessary JavaScript for some answers.
980
     *
981
     * @return string
982
     */
983
    public function getJs()
984
    {
985
        return "<script>
986
                $(window).on('load', function() {
987
                    jsPlumb.ready(function() {
988
                        if ($('#drag{$this->questionId}_question').length > 0) {
989
                            setTimeout(function() {
990
                                // Initialize MatchingDraggable
991
                                MatchingDraggable.init('{$this->questionId}');
992
                            }, 1000);
993
                        }
994
                    });
995
                });
996
            </script>";
997
    }
998
999
    /**
1000
     * Check if a answer is correct by an answer auto id.
1001
     *
1002
     * @param int $needle The answer auto id
1003
     *
1004
     * @return bool
1005
     */
1006
    public function isCorrectByAutoId($needle)
1007
    {
1008
        $key = 0;
1009
        if (is_array($this->autoId)) {
1010
            foreach ($this->autoId as $autoIdKey => $autoId) {
1011
                if ($autoId == $needle) {
1012
                    $key = $autoIdKey;
1013
                }
1014
            }
1015
        }
1016
1017
        if (!$key) {
1018
            return false;
1019
        }
1020
1021
        return $this->isCorrect($key) ? true : false;
1022
    }
1023
}
1024