Passed
Push — master ( a02707...7dc539 )
by Julito
12:00
created

TestCategory::getQuestionsByCat()   C

Complexity

Conditions 15
Paths 84

Size

Total Lines 82
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 46
nc 84
nop 4
dl 0
loc 82
rs 5.9166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Framework\Container;
6
use Chamilo\CourseBundle\Entity\CQuizQuestionCategory;
7
use ChamiloSession as Session;
8
9
/**
10
 * Class TestCategory.
11
 * Manage question categories inside an exercise.
12
 *
13
 * @author hubert.borderiou
14
 * @author Julio Montoya - several fixes
15
 *
16
 * @todo   rename to ExerciseCategory
17
 */
18
class TestCategory
19
{
20
    public $id;
21
    public $name;
22
    public $description;
23
24
    /**
25
     * Constructor of the class Category.
26
     */
27
    public function __construct()
28
    {
29
        $this->name = '';
30
        $this->description = '';
31
    }
32
33
34
    /**
35
     * return the TestCategory object with id=in_id.
36
     *
37
     * @param int $id
38
     * @param int $courseId
39
     *
40
     * @return TestCategory
41
     */
42
    public function getCategory($id, $courseId = 0)
43
    {
44
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
45
        $id = (int) $id;
46
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
47
        $sql = "SELECT * FROM $table
48
                WHERE id = $id AND c_id = ".$courseId;
49
        $res = Database::query($sql);
50
51
        if (Database::num_rows($res)) {
52
            $row = Database::fetch_array($res);
53
54
            $this->id = $row['id'];
55
            $this->name = $row['title'];
56
            $this->description = $row['description'];
57
58
            return $this;
59
        }
60
61
        return false;
62
    }
63
64
    /**
65
     * Save TestCategory in the database if name doesn't exists.
66
     *
67
     * @param int $courseId
68
     *
69
     * @return bool
70
     */
71
    public function save($courseId = 0)
72
    {
73
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
74
        $courseInfo = api_get_course_info_by_id($courseId);
75
        if (empty($courseInfo)) {
76
            return false;
77
        }
78
79
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
80
81
        // check if name already exists
82
        $sql = "SELECT count(*) AS nb FROM $table
83
                WHERE title = '".Database::escape_string($this->name)."' AND c_id = $courseId";
84
        $result = Database::query($sql);
85
        $row = Database::fetch_array($result);
86
        // lets add in BDD if not the same name
87
        if ($row['nb'] <= 0) {
88
            $repo = Container::getQuestionCategoryRepository();
89
            $course = $courseInfo['entity'];
90
            $category = new CQuizQuestionCategory();
91
            $category
92
                ->setTitle($this->name)
93
                ->setCourse($course)
94
                ->setDescription($this->description)
95
                ->setParent($course)
96
                ->addCourseLink($course, api_get_session_entity());
97
            $em = $repo->getEntityManager();
98
            $em->persist($category);
99
            $em->flush();
100
101
            if ($category) {
0 ignored issues
show
introduced by
$category is of type Chamilo\CourseBundle\Entity\CQuizQuestionCategory, thus it always evaluated to true.
Loading history...
102
                return $category->getIid();
103
            }
104
        }
105
106
        return false;
107
    }
108
109
    /**
110
     * Removes the category from the database
111
     * if there were question in this category, the link between question and category is removed.
112
     *
113
     * @param int $id
114
     *
115
     * @return bool
116
     */
117
    public function removeCategory($id)
118
    {
119
        $tbl_question_rel_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
120
        $id = (int) $id;
121
        $course_id = api_get_course_int_id();
122
        $category = $this->getCategory($id, $course_id);
123
124
        if ($category) {
0 ignored issues
show
introduced by
$category is of type TestCategory, thus it always evaluated to true.
Loading history...
125
            // remove link between question and category
126
            $sql = "DELETE FROM $tbl_question_rel_cat
127
                    WHERE category_id = $id AND c_id=".$course_id;
128
            Database::query($sql);
129
130
            $repo = Container::getQuestionCategoryRepository();
131
            $category = $repo->find($id);
132
            $repo->hardDelete($category);
133
134
            return true;
135
        }
136
137
        return false;
138
    }
139
140
141
    /**
142
     * Modify category name or description of category with id=in_id.
143
     *
144
     * @param int $courseId
145
     *
146
     * @return bool
147
     */
148
    public function modifyCategory($courseId = 0)
149
    {
150
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
151
        $courseInfo = api_get_course_info_by_id($courseId);
152
        if (empty($courseInfo)) {
153
            return false;
154
        }
155
156
        $repo = Container::getQuestionCategoryRepository();
157
        /** @var CQuizQuestionCategory $category */
158
        $category = $repo->find($this->id);
159
        if ($category) {
0 ignored issues
show
introduced by
$category is of type Chamilo\CourseBundle\Entity\CQuizQuestionCategory, thus it always evaluated to true.
Loading history...
160
            $category
161
                ->setTitle($this->name)
162
                ->setDescription($this->description);
163
164
            $repo->getEntityManager()->persist($category);
165
            $repo->getEntityManager()->flush();
166
167
            return true;
168
        }
169
170
        return false;
171
    }
172
173
174
    /**
175
     * Gets the number of question of category id=in_id.
176
     */
177
    public function getCategoryQuestionsNumber()
178
    {
179
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
180
        $id = (int) $this->id;
181
        $sql = "SELECT count(*) AS nb
182
                FROM $table
183
                WHERE category_id = $id AND c_id=".api_get_course_int_id();
184
        $res = Database::query($sql);
185
        $row = Database::fetch_array($res);
186
187
        return $row['nb'];
188
    }
189
190
    /**
191
     * Return an array of all Category objects in the database
192
     * If $field=="" Return an array of all category objects in the database
193
     * Otherwise, return an array of all in_field value
194
     * in the database (in_field = id or name or description).
195
     *
196
     * @param string $field
197
     * @param int    $courseId
198
     *
199
     * @return array
200
     */
201
    public static function getCategoryListInfo($field = '', $courseId = 0)
202
    {
203
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
204
205
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
206
        $categories = [];
207
        if (empty($field)) {
208
            $sql = "SELECT id FROM $table
209
                    WHERE c_id = $courseId
210
                    ORDER BY title ASC";
211
            $res = Database::query($sql);
212
            while ($row = Database::fetch_array($res)) {
213
                $category = new TestCategory();
214
                $categories[] = $category->getCategory($row['id'], $courseId);
215
            }
216
        } else {
217
            $field = Database::escape_string($field);
218
            $sql = "SELECT $field FROM $table
219
                    WHERE c_id = $courseId
220
                    ORDER BY $field ASC";
221
            $res = Database::query($sql);
222
            while ($row = Database::fetch_array($res)) {
223
                $categories[] = $row[$field];
224
            }
225
        }
226
227
        return $categories;
228
    }
229
230
231
    /**
232
     * Return the TestCategory id for question with question_id = $questionId
233
     * In this version, a question has only 1 TestCategory.
234
     * Return the TestCategory id, 0 if none.
235
     *
236
     * @param int $questionId
237
     * @param int $courseId
238
     *
239
     * @return int
240
     */
241
    public static function getCategoryForQuestion($questionId, $courseId = 0)
242
    {
243
        $categoryInfo = self::getCategoryInfoForQuestion($questionId, $courseId);
244
245
        if (!empty($categoryInfo) && isset($categoryInfo['category_id'])) {
246
            return (int) $categoryInfo['category_id'];
247
        }
248
249
        return 0;
250
    }
251
252
    public static function getCategoryInfoForQuestion($questionId, $courseId = 0)
253
    {
254
        $courseId = (int) $courseId;
255
        $questionId = (int) $questionId;
256
257
        if (empty($courseId)) {
258
            $courseId = api_get_course_int_id();
259
        }
260
261
        if (empty($courseId) || empty($questionId)) {
262
            return 0;
263
        }
264
265
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
266
        $sql = "SELECT *
267
                FROM $table
268
                WHERE question_id = $questionId AND c_id = $courseId";
269
        $res = Database::query($sql);
270
        if (Database::num_rows($res) > 0) {
271
            return Database::fetch_array($res, 'ASSOC');
272
        }
273
274
        return [];
275
    }
276
277
278
    /**
279
     * Return the category name for question with question_id = $questionId
280
     * In this version, a question has only 1 category.
281
     *
282
     * @param $questionId
283
     * @param int $courseId
284
     *
285
     * @return string
286
     */
287
    public static function getCategoryNameForQuestion($questionId, $courseId = 0)
288
    {
289
        if (empty($courseId)) {
290
            $courseId = api_get_course_int_id();
291
        }
292
        $courseId = (int) $courseId;
293
        $categoryId = self::getCategoryForQuestion($questionId, $courseId);
294
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
295
        $sql = "SELECT title
296
                FROM $table
297
                WHERE id = $categoryId AND c_id = $courseId";
298
        $res = Database::query($sql);
299
        $data = Database::fetch_array($res);
300
        $result = '';
301
        if (Database::num_rows($res) > 0) {
302
            $result = $data['title'];
303
        }
304
305
        return $result;
306
    }
307
308
    /**
309
     * Return the list of different categories ID for a test in the current course
310
     * hubert.borderiou 07-04-2011.
311
     *
312
     * @param int $exerciseId
313
     * @param int $courseId
314
     *
315
     * @return array
316
     */
317
    public static function getListOfCategoriesIDForTest($exerciseId, $courseId = 0)
318
    {
319
        // parcourir les questions d'un test, recup les categories uniques dans un tableau
320
        $exercise = new Exercise($courseId);
321
        $exercise->read($exerciseId, false);
322
        $categoriesInExercise = $exercise->getQuestionWithCategories();
323
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
324
        $categories = [];
325
        if (!empty($categoriesInExercise)) {
326
            foreach ($categoriesInExercise as $category) {
327
                $categories[$category['id']] = $category;
328
            }
329
        }
330
331
        return $categories;
332
    }
333
334
    /**
335
     * @return array
336
     */
337
    public static function getListOfCategoriesIDForTestObject(Exercise $exercise)
338
    {
339
        // parcourir les questions d'un test, recup les categories uniques dans un tableau
340
        $categories_in_exercise = [];
341
        $question_list = $exercise->getQuestionOrderedListByName();
342
343
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
344
        foreach ($question_list as $questionInfo) {
345
            $question_id = $questionInfo['question_id'];
346
            $category_list = self::getCategoryForQuestion($question_id);
347
            if (is_numeric($category_list)) {
348
                $category_list = [$category_list];
349
            }
350
351
            if (!empty($category_list)) {
352
                $categories_in_exercise = array_merge($categories_in_exercise, $category_list);
353
            }
354
        }
355
        if (!empty($categories_in_exercise)) {
356
            $categories_in_exercise = array_unique(array_filter($categories_in_exercise));
357
        }
358
359
        return $categories_in_exercise;
360
    }
361
362
363
    /**
364
     * Return the list of different categories NAME for a test.
365
     *
366
     * @param int $exerciseId
367
     * @param bool
368
     *
369
     * @return array
370
     *
371
     * @author function rewrote by jmontoya
372
     */
373
    public static function getListOfCategoriesNameForTest($exerciseId, $grouped_by_category = true)
374
    {
375
        $result = [];
376
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
377
378
        foreach ($categories as $catInfo) {
379
            $categoryId = $catInfo['id'];
380
            if (!empty($categoryId)) {
381
                $result[$categoryId] = [
382
                    'id' => $categoryId,
383
                    'title' => $catInfo['title'],
384
                    //'parent_id' =>  $catInfo['parent_id'],
385
                    'parent_id' => '',
386
                    'c_id' => $catInfo['c_id'],
387
                ];
388
            }
389
        }
390
391
        return $result;
392
    }
393
394
395
    /**
396
     * @param int $courseId
397
     * @param int $sessionId
398
     *
399
     * @return array
400
     */
401
    public static function getListOfCategoriesForTest(Exercise $exercise)
402
    {
403
        $result = [];
404
        $categories = self::getListOfCategoriesIDForTestObject($exercise);
405
        foreach ($categories as $cat_id) {
406
            $cat = new self();
407
            $cat = (array) $cat->getCategory($cat_id);
408
            $cat['iid'] = $cat['id'];
409
            $cat['title'] = $cat['name'];
410
            $result[$cat['id']] = $cat;
411
        }
412
413
        return $result;
414
    }
415
416
    /**
417
     * return the number of question of a category id in a test.
418
     *
419
     * @param int $exerciseId
420
     * @param int $categoryId
421
     *
422
     * @return int
423
     *
424
     * @author hubert.borderiou 07-04-2011
425
     */
426
    public static function getNumberOfQuestionsInCategoryForTest($exerciseId, $categoryId)
427
    {
428
        $nbCatResult = 0;
429
        $quiz = new Exercise();
430
        $quiz->read($exerciseId);
431
        $questionList = $quiz->selectQuestionList();
432
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ? ? ?
433
        for ($i = 1; $i <= count($questionList); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
434
            if (self::getCategoryForQuestion($questionList[$i]) == $categoryId) {
435
                $nbCatResult++;
436
            }
437
        }
438
439
        return $nbCatResult;
440
    }
441
442
    /**
443
     * return the number of question for a test using random by category
444
     * input  : test_id, number of random question (min 1).
445
     *
446
     * @param int $exerciseId
447
     * @param int $random
448
     *
449
     * @return int
450
     *             hubert.borderiou 07-04-2011
451
     *             question without categories are not counted
452
     */
453
    public static function getNumberOfQuestionRandomByCategory($exerciseId, $random)
454
    {
455
        $count = 0;
456
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
457
        foreach ($categories as $category) {
458
            if (empty($category['id'])) {
459
                continue;
460
            }
461
462
            $nbQuestionInThisCat = self::getNumberOfQuestionsInCategoryForTest(
463
                $exerciseId,
464
                $category['id']
465
            );
466
467
            if ($nbQuestionInThisCat > $random) {
468
                $count += $random;
469
            } else {
470
                $count += $nbQuestionInThisCat;
471
            }
472
        }
473
474
        return $count;
475
    }
476
477
    /**
478
     * Return an array (id=>name)
479
     * array[0] = get_lang('NoCategory');.
480
     *
481
     * @param int $courseId
482
     *
483
     * @return array
484
     */
485
    public static function getCategoriesIdAndName($courseId = 0)
486
    {
487
        if (empty($courseId)) {
488
            $courseId = api_get_course_int_id();
489
        }
490
        $categories = self::getCategoryListInfo('', $courseId);
491
        $result = ['0' => get_lang('NoCategorySelected')];
492
        for ($i = 0; $i < count($categories); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
493
            $result[$categories[$i]->id] = $categories[$i]->name;
494
        }
495
496
        return $result;
497
    }
498
499
500
    /**
501
     * Returns an array of question ids for each category
502
     * $categories[1][30] = 10, array with category id = 1 and question_id = 10
503
     * A question has "n" categories.
504
     *
505
     * @param int   $exerciseId
506
     * @param array $check_in_question_list
507
     * @param array $categoriesAddedInExercise
508
     *
509
     * @return array
510
     */
511
    public static function getQuestionsByCat(
512
        $exerciseId,
513
        $check_in_question_list = [],
514
        $categoriesAddedInExercise = [],
515
        $onlyMandatory = false
516
    ) {
517
        $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
518
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
519
        $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
520
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
521
        $exerciseId = (int) $exerciseId;
522
        $courseId = api_get_course_int_id();
523
524
        $mandatoryCondition = '';
525
        if ($onlyMandatory) {
526
            $mandatoryCondition = ' AND qrc.mandatory = 1';
527
        }
528
        $sql = "SELECT DISTINCT qrc.question_id, qrc.category_id
529
                FROM $TBL_QUESTION_REL_CATEGORY qrc
530
                INNER JOIN $TBL_EXERCICE_QUESTION eq
531
                ON (eq.question_id = qrc.question_id AND qrc.c_id = eq.c_id)
532
                INNER JOIN $categoryTable c
533
                ON (c.id = qrc.category_id AND c.c_id = eq.c_id)
534
                INNER JOIN $tableQuestion q
535
                ON (q.id = qrc.question_id AND q.c_id = eq.c_id)
536
                WHERE
537
                    exercice_id = $exerciseId AND
538
                    qrc.c_id = $courseId
539
                    $mandatoryCondition
540
                ";
541
542
        $res = Database::query($sql);
543
        $categories = [];
544
        while ($data = Database::fetch_array($res)) {
545
            if (!empty($check_in_question_list)) {
546
                if (!in_array($data['question_id'], $check_in_question_list)) {
547
                    continue;
548
                }
549
            }
550
551
            if (!isset($categories[$data['category_id']]) ||
552
                !is_array($categories[$data['category_id']])
553
            ) {
554
                $categories[$data['category_id']] = [];
555
            }
556
557
            $categories[$data['category_id']][] = $data['question_id'];
558
        }
559
560
        if (!empty($categoriesAddedInExercise)) {
561
            $newCategoryList = [];
562
            foreach ($categoriesAddedInExercise as $category) {
563
                $categoryId = $category['category_id'];
564
                if (isset($categories[$categoryId])) {
565
                    $newCategoryList[$categoryId] = $categories[$categoryId];
566
                }
567
            }
568
569
            $checkQuestionsWithNoCategory = false;
570
            foreach ($categoriesAddedInExercise as $category) {
571
                if (empty($category['category_id'])) {
572
                    // Check
573
                    $checkQuestionsWithNoCategory = true;
574
575
                    break;
576
                }
577
            }
578
579
            // Select questions that don't have any category related
580
            if ($checkQuestionsWithNoCategory) {
581
                $originalQuestionList = $check_in_question_list;
582
                foreach ($originalQuestionList as $questionId) {
583
                    $categoriesFlatten = array_flatten($categories);
584
                    if (!in_array($questionId, $categoriesFlatten)) {
585
                        $newCategoryList[0][] = $questionId;
586
                    }
587
                }
588
            }
589
            $categories = $newCategoryList;
590
        }
591
592
        return $categories;
593
    }
594
595
    /**
596
     * Returns an array of $numberElements from $elements.
597
     *
598
     * @param array $elements
599
     * @param int   $numberElements
600
     * @param bool  $shuffle
601
     * @param array $mandatoryElements
602
     *
603
     * @return array
604
     */
605
    public static function getNElementsFromArray($elements, $numberElements, $shuffle = true, $mandatoryElements = [])
606
    {
607
        $countElements = count($elements);
608
        $countMandatory = count($mandatoryElements);
609
610
        if (!empty($countMandatory)) {
611
            if ($countMandatory >= $numberElements) {
612
        if ($shuffle) {
613
                    shuffle($mandatoryElements);
614
        }
615
                $elements = array_slice($mandatoryElements, 0, $numberElements);
616
617
                return $elements;
618
        }
619
620
            $diffCount = $numberElements - $countMandatory;
621
            $diffElements = array_diff($elements, $mandatoryElements);
622
            if ($shuffle) {
623
                shuffle($diffElements);
624
            }
625
            $elements = array_slice($diffElements, 0, $diffCount);
626
            $totalElements = array_merge($mandatoryElements, $elements);
627
            if ($shuffle) {
628
                shuffle($totalElements);
629
            }
630
631
            return $totalElements;
632
        }
633
634
        if ($shuffle) {
635
            shuffle($elements);
636
        }
637
638
        if ($numberElements < $countElements) {
639
            $elements = array_slice($elements, 0, $numberElements);
640
        }
641
642
        return $elements;
643
    }
644
645
    /**
646
     * @param int $questionId
647
     * @param int $displayCategoryName
648
     */
649
    public static function displayCategoryAndTitle($questionId, $displayCategoryName = 1)
650
    {
651
        echo self::returnCategoryAndTitle($questionId, $displayCategoryName);
652
    }
653
654
    /**
655
     * @param int $questionId
656
     * @param int $in_display_category_name
657
     *
658
     * @return string|null
659
     */
660
    public static function returnCategoryAndTitle($questionId, $in_display_category_name = 1)
661
    {
662
        $is_student = !(api_is_allowed_to_edit(null, true) || api_is_session_admin());
663
        $objExercise = Session::read('objExercise');
664
        if (!empty($objExercise)) {
665
            $in_display_category_name = $objExercise->display_category_name;
666
        }
667
        $content = null;
668
        if ('' != self::getCategoryNameForQuestion($questionId) &&
669
            (1 == $in_display_category_name || !$is_student)
670
        ) {
671
            $content .= '<div class="page-header">';
672
            $content .= '<h4>'.get_lang('Category').': '.self::getCategoryNameForQuestion($questionId).'</h4>';
673
            $content .= '</div>';
674
        }
675
676
        return $content;
677
    }
678
679
    /**
680
     * sortTabByBracketLabel ($tabCategoryQuestions)
681
     * key of $tabCategoryQuestions are the category id (0 for not in a category)
682
     * value is the array of question id of this category
683
     * Sort question by Category.
684
     */
685
    public static function sortTabByBracketLabel($in_tab)
686
    {
687
        $tabResult = [];
688
        $tabCatName = []; // tab of category name
689
        foreach ($in_tab as $cat_id => $tabquestion) {
690
            $category = new self();
691
            $category = $category->getCategory($cat_id);
692
            $tabCatName[$cat_id] = $category->name;
693
        }
694
        reset($in_tab);
695
        // sort table by value, keeping keys as they are
696
        asort($tabCatName);
697
        // keys of $tabCatName are keys order for $in_tab
698
        foreach ($tabCatName as $key => $val) {
699
            $tabResult[$key] = $in_tab[$key];
700
        }
701
702
        return $tabResult;
703
    }
704
705
    /**
706
     * Return the number max of question in a category
707
     * count the number of questions in all categories, and return the max.
708
     *
709
     * @param int $exerciseId
710
     *
711
     * @author - hubert borderiou
712
     *
713
     * @return int
714
     */
715
    public static function getNumberMaxQuestionByCat($exerciseId)
716
    {
717
        $res_num_max = 0;
718
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
719
        foreach ($categories as $category) {
720
            if (empty($category['id'])) {
721
                continue;
722
            }
723
724
            $nbQuestionInThisCat = self::getNumberOfQuestionsInCategoryForTest(
725
                $exerciseId,
726
                $category['id']
727
            );
728
729
            if ($nbQuestionInThisCat > $res_num_max) {
730
                $res_num_max = $nbQuestionInThisCat;
731
            }
732
        }
733
734
        return $res_num_max;
735
    }
736
737
    /**
738
     * Returns a category summary report.
739
     *
740
     * @param Exercise $exercise
741
     * @param array $category_list
742
     *                             pre filled array with the category_id, score, and weight
743
     *                             example: array(1 => array('score' => '10', 'total' => 20));
744
     *
745
     * @return string
746
     */
747
    public static function get_stats_table_by_attempt($exercise, $category_list = [])
748
    {
749
        if (empty($category_list) || empty($exercise)) {
750
            return '';
751
        }
752
753
        $hide = (int) $exercise->getPageConfigurationAttribute('hide_category_table');
754
        if (1 === $hide) {
755
            return '';
756
        }
757
        $exerciseId = $exercise->iId;
758
        $categoryNameList = self::getListOfCategoriesNameForTest($exerciseId);
759
        $table = new HTML_Table(
760
            [
761
                'class' => 'table table-hover table-striped table-bordered',
762
                'id' => 'category_results',
763
            ]
764
        );
765
        $table->setHeaderContents(0, 0, get_lang('Categories'));
766
        $table->setHeaderContents(0, 1, get_lang('Absolute score'));
767
        $table->setHeaderContents(0, 2, get_lang('Relative score'));
768
        $row = 1;
769
770
        $none_category = [];
771
        if (isset($category_list['none'])) {
772
            $none_category = $category_list['none'];
773
            unset($category_list['none']);
774
        }
775
776
        $total = [];
777
        if (isset($category_list['total'])) {
778
            $total = $category_list['total'];
779
            unset($category_list['total']);
780
        }
781
        $radar = '';
782
        $countCategories = count($category_list);
783
        if ($countCategories > 1) {
784
            $tempResult = [];
785
            $labels = [];
786
            $labelsWithId = array_column($categoryNameList, 'title', 'id');
787
            asort($labelsWithId);
788
            foreach ($labelsWithId as $category_id => $title) {
789
                if (!isset($category_list[$category_id])) {
790
                    continue;
791
                }
792
                $labels[] = $title;
793
                $category_item = $category_list[$category_id];
794
795
                $table->setCellContents($row, 0, $title);
796
                $table->setCellContents(
797
                    $row,
798
                    1,
799
                    ExerciseLib::show_score(
800
                        $category_item['score'],
801
                        $category_item['total'],
802
                        false
803
                    )
804
                );
805
                $table->setCellContents(
806
                    $row,
807
                    2,
808
                    ExerciseLib::show_score(
809
                        $category_item['score'],
810
                        $category_item['total'],
811
                        true,
812
                        false,
813
                        true
814
                    )
815
                );
816
                $tempResult[$category_id] = round($category_item['score'] / $category_item['total'] * 10);
817
                $row++;
818
            }
819
820
            if ($countCategories > 2 && RESULT_DISABLE_RADAR === (int) $exercise->results_disabled) {
821
                $resultsArray = [];
822
                foreach ($labelsWithId as $categoryId => $label) {
823
                    if (isset($tempResult[$categoryId])) {
824
                        $resultsArray[] = $tempResult[$categoryId];
825
                    } else {
826
                        $resultsArray[] = 0;
827
            }
828
                }
829
                $radar = $exercise->getRadar($labels, [$resultsArray]);
830
            }
831
            if (!empty($none_category)) {
832
                $table->setCellContents($row, 0, get_lang('None'));
833
                $table->setCellContents(
834
                    $row,
835
                    1,
836
                    ExerciseLib::show_score(
837
                        $none_category['score'],
838
                        $none_category['total'],
839
                        false
840
                    )
841
                );
842
                $table->setCellContents(
843
                    $row,
844
                    2,
845
                    ExerciseLib::show_score(
846
                        $none_category['score'],
847
                        $none_category['total'],
848
                        true,
849
                        false,
850
                        true
851
                    )
852
                );
853
                $row++;
854
            }
855
            if (!empty($total)) {
856
                $table->setCellContents($row, 0, get_lang('Total'));
857
                $table->setCellContents(
858
                    $row,
859
                    1,
860
                    ExerciseLib::show_score(
861
                        $total['score'],
862
                        $total['total'],
863
                        false
864
                    )
865
                );
866
                $table->setCellContents(
867
                    $row,
868
                    2,
869
                    ExerciseLib::show_score(
870
                        $total['score'],
871
                        $total['total'],
872
                        true,
873
                        false,
874
                        true
875
                    )
876
                );
877
            }
878
879
            return $radar.$table->toHtml();
880
        }
881
882
        return '';
883
    }
884
885
    /**
886
     * @param                                                   $primaryKeys
887
     * @param                                                   $allPrimaryKeys
888
     * @param \Symfony\Component\HttpFoundation\Session\Session $session
889
     * @param                                                   $parameters
890
     */
891
    public function deleteResource(
892
        $primaryKeys,
893
        $allPrimaryKeys,
894
        Symfony\Component\HttpFoundation\Session\Session $session,
895
        $parameters
896
    ) {
897
        $repo = Container::getQuestionCategoryRepository();
898
        $translator = Container::getTranslator();
899
        foreach ($primaryKeys as $id) {
900
            $category = $repo->find($id);
901
            $repo->hardDelete($category);
902
        }
903
904
        Display::addFlash(Display::return_message($translator->trans('Deleted')));
905
        header('Location:'.api_get_self().'?'.api_get_cidreq());
906
        exit;
907
    }
908
909
910
    /**
911
     * @param Exercise $exercise
912
     * @param int      $courseId
913
     * @param string   $order
914
     * @param bool     $shuffle
915
     * @param bool     $excludeCategoryWithNoQuestions
916
     *
917
     * @return array
918
     */
919
    public function getCategoryExerciseTree(
920
        $exercise,
921
        $courseId,
922
        $order = null,
923
        $shuffle = false,
924
        $excludeCategoryWithNoQuestions = true
925
    ) {
926
        if (empty($exercise)) {
927
            return [];
928
        }
929
930
        $courseId = (int) $courseId;
931
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
932
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
933
        $exercise->id = (int) $exercise->id;
934
935
        $sql = "SELECT * FROM $table qc
936
                LEFT JOIN $categoryTable c
937
                ON (qc.c_id = c.c_id AND c.id = qc.category_id)
938
                WHERE qc.c_id = $courseId AND exercise_id = {$exercise->id} ";
939
940
        if (!empty($order)) {
941
            $order = Database::escape_string($order);
942
            $sql .= "ORDER BY $order";
943
        }
944
945
        $categories = [];
946
        $result = Database::query($sql);
947
        if (Database::num_rows($result)) {
948
            while ($row = Database::fetch_array($result, 'ASSOC')) {
949
                if ($excludeCategoryWithNoQuestions) {
950
                    if (0 == $row['count_questions']) {
951
                        continue;
952
                    }
953
                }
954
                if (empty($row['title']) && empty($row['category_id'])) {
955
                    $row['title'] = get_lang('General');
956
                }
957
                $categories[$row['category_id']] = $row;
958
            }
959
        }
960
961
        if ($shuffle) {
962
            shuffle_assoc($categories);
963
        }
964
965
        return $categories;
966
    }
967
968
    /**
969
     * @param FormValidator $form
970
     * @param string        $action
971
     */
972
    public function getForm(&$form, $action = 'new')
973
    {
974
        switch ($action) {
975
            case 'new':
976
                $header = get_lang('Add category');
977
                $submit = get_lang('Add test category');
978
979
                break;
980
            case 'edit':
981
                $header = get_lang('Edit this category');
982
                $submit = get_lang('Edit category');
983
984
                break;
985
        }
986
987
        // Setting the form elements
988
        $form->addElement('header', $header);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $header does not seem to be defined for all execution paths leading up to this point.
Loading history...
989
        $form->addElement('hidden', 'category_id');
990
        $form->addElement(
991
            'text',
992
            'category_name',
993
            get_lang('Category name'),
994
            ['class' => 'span6']
995
        );
996
        $form->addHtmlEditor(
997
            'category_description',
998
            get_lang('Category description'),
999
            false,
1000
            false,
1001
            [
1002
                'ToolbarSet' => 'test_category',
1003
                'Width' => '90%',
1004
                'Height' => '200',
1005
            ]
1006
        );
1007
        $category_parent_list = [];
1008
1009
        $options = [
1010
            '1' => get_lang('Visible'),
1011
            '0' => get_lang('Hidden'),
1012
        ];
1013
        $form->addElement(
1014
            'select',
1015
            'visibility',
1016
            get_lang('Visibility'),
1017
            $options
1018
        );
1019
        $script = null;
1020
        if (!empty($this->parent_id)) {
1021
            $parent_cat = new self();
1022
            $parent_cat = $parent_cat->getCategory($this->parent_id);
0 ignored issues
show
Bug Best Practice introduced by
The property parent_id does not exist on TestCategory. Did you maybe forget to declare it?
Loading history...
1023
            $category_parent_list = [$parent_cat->id => $parent_cat->name];
1024
            $script .= '<script>$(function() { $("#parent_id").trigger("addItem",[{"title": "'.$parent_cat->name.'", "value": "'.$parent_cat->id.'"}]); });</script>';
1025
        }
1026
        $form->addElement('html', $script);
1027
1028
        $form->addElement('select', 'parent_id', get_lang('Parent'), $category_parent_list, ['id' => 'parent_id']);
1029
        $form->addElement('style_submit_button', 'SubmitNote', $submit, 'class="add"');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $submit does not seem to be defined for all execution paths leading up to this point.
Loading history...
1030
1031
        // setting the defaults
1032
        $defaults = [];
1033
        $defaults['category_id'] = $this->id;
1034
        $defaults['category_name'] = $this->name;
1035
        $defaults['category_description'] = $this->description;
1036
        $defaults['parent_id'] = $this->parent_id;
1037
        $defaults['visibility'] = $this->visibility;
0 ignored issues
show
Bug Best Practice introduced by
The property visibility does not exist on TestCategory. Did you maybe forget to declare it?
Loading history...
1038
        $form->setDefaults($defaults);
1039
1040
        // setting the rules
1041
        $form->addRule('category_name', get_lang('Required field'), 'required');
1042
    }
1043
1044
    /**
1045
     * Returns the category form.
1046
     *
1047
     * @return string
1048
     */
1049
    public function returnCategoryForm(Exercise $exercise)
1050
    {
1051
        $categories = $this->getListOfCategoriesForTest($exercise);
1052
        $sortedCategories = [];
1053
        foreach ($categories as $catId => $cat) {
1054
            $sortedCategories[$cat['title']] = $cat;
1055
        }
1056
        ksort($sortedCategories);
1057
        $saved_categories = $exercise->getCategoriesInExercise();
1058
        $return = null;
1059
1060
        if (!empty($sortedCategories)) {
1061
            $nbQuestionsTotal = $exercise->getNumberQuestionExerciseCategory();
1062
            $exercise->setCategoriesGrouping(true);
1063
            $real_question_count = count($exercise->getQuestionList());
1064
1065
            $warning = null;
1066
            if ($nbQuestionsTotal != $real_question_count) {
1067
                $warning = Display::return_message(
1068
                    get_lang('Make sure you have enough questions in your categories.'),
1069
                    'warning'
1070
                );
1071
            }
1072
1073
            $return .= $warning;
1074
            $return .= '<table class="table table-hover table-bordered data_table">';
1075
            $return .= '<tr>';
1076
            $return .= '<th height="24">'.get_lang('Categories').'</th>';
1077
            $return .= '<th width="70" height="24">'.get_lang('N°').'</th></tr>';
1078
1079
            $emptyCategory = [
1080
                'id' => '0',
1081
                'name' => get_lang('General'),
1082
                'description' => '',
1083
                'iid' => '0',
1084
                'title' => get_lang('General'),
1085
            ];
1086
1087
            $sortedCategories[] = $emptyCategory;
1088
1089
            foreach ($sortedCategories as $category) {
1090
                $cat_id = $category['iid'];
1091
                $return .= '<tr>';
1092
                $return .= '<td>';
1093
                $return .= Display::div($category['name']);
1094
                $return .= '</td>';
1095
                $return .= '<td>';
1096
                $value = isset($saved_categories) && isset($saved_categories[$cat_id]) ? $saved_categories[$cat_id]['count_questions'] : -1;
1097
                $return .= Display::input(
1098
                    'number',
1099
                    "category[$cat_id]",
1100
                    $value,
1101
                    ['class' => 'form-control', 'min' => -1, 'step' => 1]
1102
                );
1103
                $return .= '</td>';
1104
                $return .= '</tr>';
1105
            }
1106
1107
            $return .= '</table>';
1108
            $return .= get_lang('-1 = All questions will be selected.');
1109
        }
1110
1111
        return $return;
1112
    }
1113
1114
    /**
1115
     * Return true if a category already exists with the same name.
1116
     *
1117
     * @param string $name
1118
     * @param int    $courseId
1119
     *
1120
     * @return bool
1121
     */
1122
    public static function categoryTitleExists($name, $courseId = 0)
1123
    {
1124
        $categories = self::getCategoryListInfo('title', $courseId);
1125
        foreach ($categories as $title) {
1126
            if ($title == $name) {
1127
                return true;
1128
            }
1129
        }
1130
1131
        return false;
1132
    }
1133
1134
    /**
1135
     * Return the id of the test category with title = $in_title.
1136
     *
1137
     * @param string $title
1138
     * @param int    $courseId
1139
     *
1140
     * @return int is id of test category
1141
     */
1142
    public static function get_category_id_for_title($title, $courseId = 0)
1143
    {
1144
        $out_res = 0;
1145
        if (empty($courseId)) {
1146
            $courseId = api_get_course_int_id();
1147
        }
1148
        $courseId = (int) $courseId;
1149
        $tbl_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
1150
        $sql = "SELECT id FROM $tbl_cat
1151
                WHERE c_id = $courseId AND title = '".Database::escape_string($title)."'";
1152
        $res = Database::query($sql);
1153
        if (Database::num_rows($res) > 0) {
1154
            $data = Database::fetch_array($res);
1155
            $out_res = $data['id'];
1156
        }
1157
1158
        return $out_res;
1159
    }
1160
1161
    /**
1162
     * Add a relation between question and category in table c_quiz_question_rel_category.
1163
     *
1164
     * @param int $categoryId
1165
     * @param int $questionId
1166
     * @param int $courseId
1167
     *
1168
     * @return string|false
1169
     */
1170
    public static function addCategoryToQuestion($categoryId, $questionId, $courseId)
1171
    {
1172
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
1173
        // if question doesn't have a category
1174
        // @todo change for 1.10 when a question can have several categories
1175
        if (self::getCategoryForQuestion($questionId, $courseId) == 0 &&
1176
            $questionId > 0 &&
1177
            $courseId > 0
1178
        ) {
1179
            $sql = "INSERT INTO $table (c_id, question_id, category_id)
1180
                    VALUES (".intval($courseId).", ".intval($questionId).", ".intval($categoryId).")";
1181
            Database::query($sql);
1182
            $id = Database::insert_id();
1183
1184
            return $id;
1185
        }
1186
1187
        return false;
1188
    }
1189
1190
    /**
1191
     * @param int $courseId
1192
     * @param int $sessionId
1193
     *
1194
     * @return CQuizQuestionCategory[]
1195
     */
1196
    public static function getCategories($courseId, $sessionId = 0)
1197
    {
1198
        if (empty($courseId)) {
1199
            return [];
1200
        }
1201
1202
        $sessionId = (int) $sessionId;
1203
        $courseId = (int) $courseId;
1204
        $sessionEntity = null;
1205
        if (!empty($sessionId)) {
1206
            $sessionEntity = api_get_session_entity($sessionId);
1207
        }
1208
1209
        $courseEntity = api_get_course_entity($courseId);
1210
        $repo = Container::getQuestionCategoryRepository();
1211
        $resources = $repo->getResourcesByCourse($courseEntity, $sessionEntity);
1212
1213
        return $resources->getQuery()->getResult();
1214
    }
1215
1216
1217
1218
    /**
1219
     * @param int $courseId
1220
     * @param int $sessionId
1221
     *
1222
     * @return string
1223
     */
1224
    public function displayCategories($courseId, $sessionId = 0)
1225
    {
1226
        $course = api_get_course_entity($courseId);
1227
        $session = api_get_session_entity($sessionId);
1228
1229
        $sessionId = (int) $sessionId;
1230
        $categories = $this->getCategories($courseId, $sessionId);
1231
        $html = '';
1232
        foreach ($categories as $category) {
1233
            $id = $category->getIid();
1234
            $nb_question = $category->getQuestions()->count();
1235
            $rowname = self::protectJSDialogQuote($category->getTitle());
0 ignored issues
show
Bug Best Practice introduced by
The method TestCategory::protectJSDialogQuote() 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

1235
            /** @scrutinizer ignore-call */ 
1236
            $rowname = self::protectJSDialogQuote($category->getTitle());
Loading history...
1236
            $nb_question_label = 1 == $nb_question ? $nb_question.' '.get_lang('Question') : $nb_question.' '.get_lang(
1237
                    'Questions'
1238
                );
1239
            $content = "<span style='float:right'>".$nb_question_label."</span>";
1240
            $content .= '<div class="sectioncomment">';
1241
            $content .= $category->getDescription();
1242
            $content .= '</div>';
1243
            $links = '';
1244
1245
            if (!$sessionId) {
1246
                $links .= '<a href="'.api_get_self(
1247
                    ).'?action=editcategory&category_id='.$id.'&'.api_get_cidreq().'">'.
1248
                    Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
1249
                $links .= ' <a href="'.api_get_self().'?'.api_get_cidreq(
1250
                    ).'&action=deletecategory&category_id='.$id.'" ';
1251
                $links .= 'onclick="return confirmDelete(\''.self::protectJSDialogQuote(
1252
                        get_lang('DeleteCategoryAreYouSure').'['.$rowname
1253
                    ).'] ?\', \'id_cat'.$id.'\');">';
1254
                $links .= Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_SMALL).'</a>';
1255
            }
1256
1257
            $html .= Display::panel($content, $category->getTitle().$links);
1258
        }
1259
1260
        return $html;
1261
    }
1262
1263
1264
    /**
1265
     * To allowed " in javascript dialog box without bad surprises
1266
     * replace " with two '.
1267
     *
1268
     * @param string $text
1269
     *
1270
     * @return mixed
1271
     */
1272
    public function protectJSDialogQuote($text)
1273
    {
1274
        $res = $text;
1275
        $res = str_replace("'", "\'", $res);
1276
        // 8. Set route and request
1277
        $res = str_replace('"', "\'\'", $res);
1278
1279
        return $res;
1280
    }
1281
}
1282