Passed
Push — master ( 251179...3b9d17 )
by Julito
09:58
created

TestCategory::getCategoryListInfo()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 19
nc 8
nop 2
dl 0
loc 27
rs 9.3222
c 0
b 0
f 0
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
     * return the TestCategory object with id=in_id.
35
     *
36
     * @param int $id
37
     * @param int $courseId
38
     *
39
     * @return TestCategory
40
     */
41
    public function getCategory($id, $courseId = 0)
42
    {
43
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
44
        $id = (int) $id;
45
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
46
        $sql = "SELECT * FROM $table
47
                WHERE iid = $id AND c_id = ".$courseId;
48
        $res = Database::query($sql);
49
50
        if (Database::num_rows($res)) {
51
            $row = Database::fetch_array($res);
52
53
            $this->id = $row['iid'];
54
            $this->name = $row['title'];
55
            $this->description = $row['description'];
56
57
            return $this;
58
        }
59
60
        return false;
61
    }
62
63
    /**
64
     * Save TestCategory in the database if name doesn't exists.
65
     *
66
     * @param int $courseId
67
     *
68
     * @return bool
69
     */
70
    public function save($courseId = 0)
71
    {
72
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
73
        $courseInfo = api_get_course_info_by_id($courseId);
74
        if (empty($courseInfo)) {
75
            return false;
76
        }
77
78
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
79
80
        // check if name already exists
81
        $sql = "SELECT count(*) AS nb FROM $table
82
                WHERE title = '".Database::escape_string($this->name)."' AND c_id = $courseId";
83
        $result = Database::query($sql);
84
        $row = Database::fetch_array($result);
85
        // lets add in BDD if not the same name
86
        if ($row['nb'] <= 0) {
87
            $repo = Container::getQuestionCategoryRepository();
88
            $course = $courseInfo['entity'];
89
            $category = new CQuizQuestionCategory();
90
            $category
91
                ->setTitle($this->name)
92
                ->setDescription($this->description)
93
                ->setParent($course)
94
                ->addCourseLink($course, api_get_session_entity());
95
            $repo->create($category);
96
97
            if ($category) {
0 ignored issues
show
introduced by
$category is of type Chamilo\CourseBundle\Entity\CQuizQuestionCategory, thus it always evaluated to true.
Loading history...
98
                return $category->getIid();
99
            }
100
        }
101
102
        return false;
103
    }
104
105
    /**
106
     * Removes the category from the database
107
     * if there were question in this category, the link between question and category is removed.
108
     *
109
     * @param int $id
110
     *
111
     * @return bool
112
     */
113
    public function removeCategory($id)
114
    {
115
        $tbl_question_rel_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
116
        $id = (int) $id;
117
        $course_id = api_get_course_int_id();
118
        $category = $this->getCategory($id, $course_id);
119
120
        if ($category) {
0 ignored issues
show
introduced by
$category is of type TestCategory, thus it always evaluated to true.
Loading history...
121
            // remove link between question and category
122
            $sql = "DELETE FROM $tbl_question_rel_cat
123
                    WHERE category_id = $id ";
124
            Database::query($sql);
125
126
            $repo = Container::getQuestionCategoryRepository();
127
            $category = $repo->find($id);
128
            $repo->hardDelete($category);
129
130
            return true;
131
        }
132
133
        return false;
134
    }
135
136
    /**
137
     * Modify category name or description of category with id=in_id.
138
     *
139
     * @param int $courseId
140
     *
141
     * @return bool
142
     */
143
    public function modifyCategory($courseId = 0)
144
    {
145
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
146
        $courseInfo = api_get_course_info_by_id($courseId);
147
        if (empty($courseInfo)) {
148
            return false;
149
        }
150
151
        $repo = Container::getQuestionCategoryRepository();
152
        /** @var CQuizQuestionCategory $category */
153
        $category = $repo->find($this->id);
154
        if ($category) {
0 ignored issues
show
introduced by
$category is of type Chamilo\CourseBundle\Entity\CQuizQuestionCategory, thus it always evaluated to true.
Loading history...
155
            $category
156
                ->setTitle($this->name)
157
                ->setDescription($this->description);
158
159
            $repo->update($category);
160
161
            return true;
162
        }
163
164
        return false;
165
    }
166
167
    /**
168
     * Gets the number of question of category id=in_id.
169
     */
170
    public function getCategoryQuestionsNumber()
171
    {
172
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
173
        $id = (int) $this->id;
174
        $sql = "SELECT count(*) AS nb
175
                FROM $table
176
                WHERE category_id = $id AND c_id=".api_get_course_int_id();
177
        $res = Database::query($sql);
178
        $row = Database::fetch_array($res);
179
180
        return $row['nb'];
181
    }
182
183
    /**
184
     * Return the TestCategory id for question with question_id = $questionId
185
     * In this version, a question has only 1 TestCategory.
186
     * Return the TestCategory id, 0 if none.
187
     *
188
     * @param int $questionId
189
     * @param int $courseId
190
     *
191
     * @return int
192
     */
193
    public static function getCategoryForQuestion($questionId, $courseId = 0)
194
    {
195
        $categoryInfo = self::getCategoryInfoForQuestion($questionId, $courseId);
196
197
        if (!empty($categoryInfo) && isset($categoryInfo['category_id'])) {
198
            return (int) $categoryInfo['category_id'];
199
        }
200
201
        return 0;
202
    }
203
204
    public static function getCategoryInfoForQuestion($questionId, $courseId = 0)
205
    {
206
        $courseId = (int) $courseId;
207
        $questionId = (int) $questionId;
208
209
        if (empty($courseId)) {
210
            $courseId = api_get_course_int_id();
211
        }
212
213
        if (empty($courseId) || empty($questionId)) {
214
            return 0;
215
        }
216
217
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
218
        $sql = "SELECT *
219
                FROM $table
220
                WHERE question_id = $questionId";
221
        $res = Database::query($sql);
222
        if (Database::num_rows($res) > 0) {
223
            return Database::fetch_array($res, 'ASSOC');
224
        }
225
226
        return [];
227
    }
228
229
    /**
230
     * Return the category name for question with question_id = $questionId
231
     * In this version, a question has only 1 category.
232
     *
233
     * @param $questionId
234
     * @param int $courseId
235
     *
236
     * @return string
237
     */
238
    public static function getCategoryNameForQuestion($questionId, $courseId = 0)
239
    {
240
        if (empty($courseId)) {
241
            $courseId = api_get_course_int_id();
242
        }
243
        $courseId = (int) $courseId;
244
        $categoryId = self::getCategoryForQuestion($questionId, $courseId);
245
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
246
        $sql = "SELECT title
247
                FROM $table
248
                WHERE iid = $categoryId ";
249
        $res = Database::query($sql);
250
        $data = Database::fetch_array($res);
251
        $result = '';
252
        if (Database::num_rows($res) > 0) {
253
            $result = $data['title'];
254
        }
255
256
        return $result;
257
    }
258
259
    /**
260
     * Return the list of different categories ID for a test in the current course
261
     * hubert.borderiou 07-04-2011.
262
     *
263
     * @param int $exerciseId
264
     * @param int $courseId
265
     *
266
     * @return array
267
     */
268
    public static function getListOfCategoriesIDForTest($exerciseId, $courseId = 0)
269
    {
270
        // parcourir les questions d'un test, recup les categories uniques dans un tableau
271
        $exercise = new Exercise($courseId);
272
        $exercise->read($exerciseId, false);
273
        $categoriesInExercise = $exercise->getQuestionWithCategories();
274
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
275
        $categories = [];
276
        if (!empty($categoriesInExercise)) {
277
            foreach ($categoriesInExercise as $category) {
278
                $categories[$category['id']] = $category;
279
            }
280
        }
281
282
        return $categories;
283
    }
284
285
    /**
286
     * @return array
287
     */
288
    public static function getListOfCategoriesIDForTestObject(Exercise $exercise)
289
    {
290
        // parcourir les questions d'un test, recup les categories uniques dans un tableau
291
        $categories_in_exercise = [];
292
        $question_list = $exercise->getQuestionOrderedListByName();
293
294
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
295
        foreach ($question_list as $questionInfo) {
296
            $question_id = $questionInfo['question_id'];
297
            $category_list = self::getCategoryForQuestion($question_id);
298
            if (is_numeric($category_list)) {
299
                $category_list = [$category_list];
300
            }
301
302
            if (!empty($category_list)) {
303
                $categories_in_exercise = array_merge($categories_in_exercise, $category_list);
304
            }
305
        }
306
        if (!empty($categories_in_exercise)) {
307
            $categories_in_exercise = array_unique(array_filter($categories_in_exercise));
308
        }
309
310
        return $categories_in_exercise;
311
    }
312
313
    /**
314
     * Return the list of different categories NAME for a test.
315
     *
316
     * @param int $exerciseId
317
     * @param bool
318
     *
319
     * @return array
320
     *
321
     * @author function rewrote by jmontoya
322
     */
323
    public static function getListOfCategoriesNameForTest($exerciseId, $grouped_by_category = true)
324
    {
325
        $result = [];
326
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
327
328
        foreach ($categories as $catInfo) {
329
            $categoryId = $catInfo['id'];
330
            if (!empty($categoryId)) {
331
                $result[$categoryId] = [
332
                    'id' => $categoryId,
333
                    'title' => $catInfo['title'],
334
                    //'parent_id' =>  $catInfo['parent_id'],
335
                    'parent_id' => '',
336
                    'c_id' => $catInfo['c_id'],
337
                ];
338
            }
339
        }
340
341
        return $result;
342
    }
343
344
    /**
345
     * @return array
346
     */
347
    public static function getListOfCategoriesForTest(Exercise $exercise)
348
    {
349
        $result = [];
350
        $categories = self::getListOfCategoriesIDForTestObject($exercise);
351
        foreach ($categories as $cat_id) {
352
            $cat = new self();
353
            $cat = (array) $cat->getCategory($cat_id);
354
            $cat['iid'] = $cat['id'];
355
            $cat['title'] = $cat['name'];
356
            $result[$cat['id']] = $cat;
357
        }
358
359
        return $result;
360
    }
361
362
    /**
363
     * return the number of question of a category id in a test.
364
     *
365
     * @param int $exerciseId
366
     * @param int $categoryId
367
     *
368
     * @return int
369
     *
370
     * @author hubert.borderiou 07-04-2011
371
     */
372
    public static function getNumberOfQuestionsInCategoryForTest($exerciseId, $categoryId)
373
    {
374
        $nbCatResult = 0;
375
        $quiz = new Exercise();
376
        $quiz->read($exerciseId);
377
        $questionList = $quiz->selectQuestionList();
378
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ? ? ?
379
        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...
380
            if (self::getCategoryForQuestion($questionList[$i]) == $categoryId) {
381
                $nbCatResult++;
382
            }
383
        }
384
385
        return $nbCatResult;
386
    }
387
388
    /**
389
     * return the number of question for a test using random by category
390
     * input  : test_id, number of random question (min 1).
391
     *
392
     * @param int $exerciseId
393
     * @param int $random
394
     *
395
     * @return int
396
     *             hubert.borderiou 07-04-2011
397
     *             question without categories are not counted
398
     */
399
    public static function getNumberOfQuestionRandomByCategory($exerciseId, $random)
400
    {
401
        $count = 0;
402
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
403
        foreach ($categories as $category) {
404
            if (empty($category['id'])) {
405
                continue;
406
            }
407
408
            $nbQuestionInThisCat = self::getNumberOfQuestionsInCategoryForTest(
409
                $exerciseId,
410
                $category['id']
411
            );
412
413
            if ($nbQuestionInThisCat > $random) {
414
                $count += $random;
415
            } else {
416
                $count += $nbQuestionInThisCat;
417
            }
418
        }
419
420
        return $count;
421
    }
422
423
    /**
424
     * Return an array (id=>name)
425
     * array[0] = get_lang('NoCategory');.
426
     *
427
     * @param int $courseId
428
     *
429
     * @return array
430
     */
431
    public static function getCategoriesIdAndName($courseId = 0)
432
    {
433
        if (empty($courseId)) {
434
            $courseId = api_get_course_int_id();
435
        }
436
        $categories = self::getCategories($courseId);
437
        $result = [0 => get_lang('No category selected')];
438
        foreach ($categories as $category) {
439
            $result[$category->getIid()] = $category->getTitle();
440
        }
441
442
        return $result;
443
    }
444
445
    /**
446
     * Returns an array of question ids for each category
447
     * $categories[1][30] = 10, array with category id = 1 and question_id = 10
448
     * A question has "n" categories.
449
     *
450
     * @param int   $exerciseId
451
     * @param array $check_in_question_list
452
     * @param array $categoriesAddedInExercise
453
     *
454
     * @return array
455
     */
456
    public static function getQuestionsByCat(
457
        $exerciseId,
458
        $check_in_question_list = [],
459
        $categoriesAddedInExercise = [],
460
        $onlyMandatory = false
461
    ) {
462
        $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
463
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
464
        $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
465
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
466
        $exerciseId = (int) $exerciseId;
467
        $courseId = api_get_course_int_id();
468
469
        $mandatoryCondition = '';
470
        if ($onlyMandatory) {
471
            $mandatoryCondition = ' AND qrc.mandatory = 1';
472
        }
473
        $sql = "SELECT DISTINCT qrc.question_id, qrc.category_id
474
                FROM $TBL_QUESTION_REL_CATEGORY qrc
475
                INNER JOIN $TBL_EXERCICE_QUESTION eq
476
                ON (eq.question_id = qrc.question_id AND qrc.c_id = eq.c_id)
477
                INNER JOIN $categoryTable c
478
                ON (c.id = qrc.category_id AND c.c_id = eq.c_id)
479
                INNER JOIN $tableQuestion q
480
                ON (q.id = qrc.question_id AND q.c_id = eq.c_id)
481
                WHERE
482
                    quiz_id = $exerciseId AND
483
                    qrc.c_id = $courseId
484
                    $mandatoryCondition
485
                ";
486
487
        $res = Database::query($sql);
488
        $categories = [];
489
        while ($data = Database::fetch_array($res)) {
490
            if (!empty($check_in_question_list)) {
491
                if (!in_array($data['question_id'], $check_in_question_list)) {
492
                    continue;
493
                }
494
            }
495
496
            if (!isset($categories[$data['category_id']]) ||
497
                !is_array($categories[$data['category_id']])
498
            ) {
499
                $categories[$data['category_id']] = [];
500
            }
501
            $categories[$data['category_id']][] = $data['question_id'];
502
        }
503
504
        if (!empty($categoriesAddedInExercise)) {
505
            $newCategoryList = [];
506
            foreach ($categoriesAddedInExercise as $category) {
507
                $categoryId = $category['category_id'];
508
                if (isset($categories[$categoryId])) {
509
                    $newCategoryList[$categoryId] = $categories[$categoryId];
510
                }
511
            }
512
513
            $checkQuestionsWithNoCategory = false;
514
            foreach ($categoriesAddedInExercise as $category) {
515
                if (empty($category['category_id'])) {
516
                    // Check
517
                    $checkQuestionsWithNoCategory = true;
518
519
                    break;
520
                }
521
            }
522
523
            // Select questions that don't have any category related
524
            if ($checkQuestionsWithNoCategory) {
525
                $originalQuestionList = $check_in_question_list;
526
                foreach ($originalQuestionList as $questionId) {
527
                    $categoriesFlatten = array_flatten($categories);
528
                    if (!in_array($questionId, $categoriesFlatten)) {
529
                        $newCategoryList[0][] = $questionId;
530
                    }
531
                }
532
            }
533
            $categories = $newCategoryList;
534
        }
535
536
        return $categories;
537
    }
538
539
    /**
540
     * Returns an array of $numberElements from $elements.
541
     *
542
     * @param array $elements
543
     * @param int   $numberElements
544
     * @param bool  $shuffle
545
     * @param array $mandatoryElements
546
     *
547
     * @return array
548
     */
549
    public static function getNElementsFromArray($elements, $numberElements, $shuffle = true, $mandatoryElements = [])
550
    {
551
        $countElements = count($elements);
552
        $countMandatory = count($mandatoryElements);
553
554
        if (!empty($countMandatory)) {
555
            if ($countMandatory >= $numberElements) {
556
                if ($shuffle) {
557
                    shuffle($mandatoryElements);
558
                }
559
                $elements = array_slice($mandatoryElements, 0, $numberElements);
560
561
                return $elements;
562
            }
563
564
            $diffCount = $numberElements - $countMandatory;
565
            $diffElements = array_diff($elements, $mandatoryElements);
566
            if ($shuffle) {
567
                shuffle($diffElements);
568
            }
569
            $elements = array_slice($diffElements, 0, $diffCount);
570
            $totalElements = array_merge($mandatoryElements, $elements);
571
            if ($shuffle) {
572
                shuffle($totalElements);
573
            }
574
575
            return $totalElements;
576
        }
577
578
        if ($shuffle) {
579
            shuffle($elements);
580
        }
581
582
        if ($numberElements < $countElements) {
583
            $elements = array_slice($elements, 0, $numberElements);
584
        }
585
586
        return $elements;
587
    }
588
589
    /**
590
     * @param int $questionId
591
     * @param int $displayCategoryName
592
     */
593
    public static function displayCategoryAndTitle($questionId, $displayCategoryName = 1)
594
    {
595
        echo self::returnCategoryAndTitle($questionId, $displayCategoryName);
596
    }
597
598
    /**
599
     * @param int $questionId
600
     * @param int $in_display_category_name
601
     *
602
     * @return string|null
603
     */
604
    public static function returnCategoryAndTitle($questionId, $in_display_category_name = 1)
605
    {
606
        $is_student = !(api_is_allowed_to_edit(null, true) || api_is_session_admin());
607
        $objExercise = Session::read('objExercise');
608
        if (!empty($objExercise)) {
609
            $in_display_category_name = $objExercise->display_category_name;
610
        }
611
        $content = null;
612
        if ('' != self::getCategoryNameForQuestion($questionId) &&
613
            (1 == $in_display_category_name || !$is_student)
614
        ) {
615
            $content .= '<div class="page-header">';
616
            $content .= '<h4>'.get_lang('Category').': '.self::getCategoryNameForQuestion($questionId).'</h4>';
617
            $content .= '</div>';
618
        }
619
620
        return $content;
621
    }
622
623
    /**
624
     * sortTabByBracketLabel ($tabCategoryQuestions)
625
     * key of $tabCategoryQuestions are the category id (0 for not in a category)
626
     * value is the array of question id of this category
627
     * Sort question by Category.
628
     */
629
    public static function sortTabByBracketLabel($in_tab)
630
    {
631
        $tabResult = [];
632
        $tabCatName = []; // tab of category name
633
        foreach ($in_tab as $cat_id => $tabquestion) {
634
            $category = new self();
635
            $category = $category->getCategory($cat_id);
636
            $tabCatName[$cat_id] = $category->name;
637
        }
638
        reset($in_tab);
639
        // sort table by value, keeping keys as they are
640
        asort($tabCatName);
641
        // keys of $tabCatName are keys order for $in_tab
642
        foreach ($tabCatName as $key => $val) {
643
            $tabResult[$key] = $in_tab[$key];
644
        }
645
646
        return $tabResult;
647
    }
648
649
    /**
650
     * Return the number max of question in a category
651
     * count the number of questions in all categories, and return the max.
652
     *
653
     * @param int $exerciseId
654
     *
655
     * @author - hubert borderiou
656
     *
657
     * @return int
658
     */
659
    public static function getNumberMaxQuestionByCat($exerciseId)
660
    {
661
        $res_num_max = 0;
662
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
663
        foreach ($categories as $category) {
664
            if (empty($category['id'])) {
665
                continue;
666
            }
667
668
            $nbQuestionInThisCat = self::getNumberOfQuestionsInCategoryForTest(
669
                $exerciseId,
670
                $category['id']
671
            );
672
673
            if ($nbQuestionInThisCat > $res_num_max) {
674
                $res_num_max = $nbQuestionInThisCat;
675
            }
676
        }
677
678
        return $res_num_max;
679
    }
680
681
    /**
682
     * Returns a category summary report.
683
     *
684
     * @param Exercise $exercise
685
     * @param array    $category_list
686
     *                                pre filled array with the category_id, score, and weight
687
     *                                example: array(1 => array('score' => '10', 'total' => 20));
688
     *
689
     * @return string
690
     */
691
    public static function get_stats_table_by_attempt($exercise, $category_list = [])
692
    {
693
        if (empty($category_list) || empty($exercise)) {
694
            return '';
695
        }
696
697
        $hide = (int) $exercise->getPageConfigurationAttribute('hide_category_table');
698
        if (1 === $hide) {
699
            return '';
700
        }
701
        $exerciseId = $exercise->iId;
702
        $categoryNameList = self::getListOfCategoriesNameForTest($exerciseId);
703
        $table = new HTML_Table(
704
            [
705
                'class' => 'table table-hover table-striped table-bordered',
706
                'id' => 'category_results',
707
            ]
708
        );
709
        $table->setHeaderContents(0, 0, get_lang('Categories'));
710
        $table->setHeaderContents(0, 1, get_lang('Absolute score'));
711
        $table->setHeaderContents(0, 2, get_lang('Relative score'));
712
        $row = 1;
713
714
        $none_category = [];
715
        if (isset($category_list['none'])) {
716
            $none_category = $category_list['none'];
717
            unset($category_list['none']);
718
        }
719
720
        $total = [];
721
        if (isset($category_list['total'])) {
722
            $total = $category_list['total'];
723
            unset($category_list['total']);
724
        }
725
        $radar = '';
726
        $countCategories = count($category_list);
727
        if ($countCategories > 1) {
728
            $tempResult = [];
729
            $labels = [];
730
            $labelsWithId = array_column($categoryNameList, 'title', 'id');
731
            asort($labelsWithId);
732
            foreach ($labelsWithId as $category_id => $title) {
733
                if (!isset($category_list[$category_id])) {
734
                    continue;
735
                }
736
                $labels[] = $title;
737
                $category_item = $category_list[$category_id];
738
739
                $table->setCellContents($row, 0, $title);
740
                $table->setCellContents(
741
                    $row,
742
                    1,
743
                    ExerciseLib::show_score(
744
                        $category_item['score'],
745
                        $category_item['total'],
746
                        false
747
                    )
748
                );
749
                $table->setCellContents(
750
                    $row,
751
                    2,
752
                    ExerciseLib::show_score(
753
                        $category_item['score'],
754
                        $category_item['total'],
755
                        true,
756
                        false,
757
                        true
758
                    )
759
                );
760
                $tempResult[$category_id] = round($category_item['score'] / $category_item['total'] * 10);
761
                $row++;
762
            }
763
764
            if ($countCategories > 2 && RESULT_DISABLE_RADAR === (int) $exercise->results_disabled) {
765
                $resultsArray = [];
766
                foreach ($labelsWithId as $categoryId => $label) {
767
                    if (isset($tempResult[$categoryId])) {
768
                        $resultsArray[] = $tempResult[$categoryId];
769
                    } else {
770
                        $resultsArray[] = 0;
771
                    }
772
                }
773
                $radar = $exercise->getRadar($labels, [$resultsArray]);
774
            }
775
            if (!empty($none_category)) {
776
                $table->setCellContents($row, 0, get_lang('None'));
777
                $table->setCellContents(
778
                    $row,
779
                    1,
780
                    ExerciseLib::show_score(
781
                        $none_category['score'],
782
                        $none_category['total'],
783
                        false
784
                    )
785
                );
786
                $table->setCellContents(
787
                    $row,
788
                    2,
789
                    ExerciseLib::show_score(
790
                        $none_category['score'],
791
                        $none_category['total'],
792
                        true,
793
                        false,
794
                        true
795
                    )
796
                );
797
                $row++;
798
            }
799
            if (!empty($total)) {
800
                $table->setCellContents($row, 0, get_lang('Total'));
801
                $table->setCellContents(
802
                    $row,
803
                    1,
804
                    ExerciseLib::show_score(
805
                        $total['score'],
806
                        $total['total'],
807
                        false
808
                    )
809
                );
810
                $table->setCellContents(
811
                    $row,
812
                    2,
813
                    ExerciseLib::show_score(
814
                        $total['score'],
815
                        $total['total'],
816
                        true,
817
                        false,
818
                        true
819
                    )
820
                );
821
            }
822
823
            return $radar.$table->toHtml();
824
        }
825
826
        return '';
827
    }
828
829
    /**
830
     * @param Exercise $exercise
831
     * @param int      $courseId
832
     * @param string   $order
833
     * @param bool     $shuffle
834
     * @param bool     $excludeCategoryWithNoQuestions
835
     *
836
     * @return array
837
     */
838
    public function getCategoryExerciseTree(
839
        $exercise,
840
        $courseId,
841
        $order = null,
842
        $shuffle = false,
843
        $excludeCategoryWithNoQuestions = true
844
    ) {
845
        if (empty($exercise)) {
846
            return [];
847
        }
848
849
        $courseId = (int) $courseId;
850
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
851
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
852
        $exercise->id = (int) $exercise->id;
853
854
        $sql = "SELECT * FROM $table qc
855
                LEFT JOIN $categoryTable c
856
                ON (qc.c_id = c.c_id AND c.id = qc.category_id)
857
                WHERE qc.c_id = $courseId AND exercise_id = {$exercise->id} ";
858
859
        if (!empty($order)) {
860
            $order = Database::escape_string($order);
861
            $sql .= "ORDER BY $order";
862
        }
863
864
        $categories = [];
865
        $result = Database::query($sql);
866
        if (Database::num_rows($result)) {
867
            while ($row = Database::fetch_array($result, 'ASSOC')) {
868
                if ($excludeCategoryWithNoQuestions) {
869
                    if (0 == $row['count_questions']) {
870
                        continue;
871
                    }
872
                }
873
                if (empty($row['title']) && empty($row['category_id'])) {
874
                    $row['title'] = get_lang('General');
875
                }
876
                $categories[$row['category_id']] = $row;
877
            }
878
        }
879
880
        if ($shuffle) {
881
            shuffle_assoc($categories);
882
        }
883
884
        return $categories;
885
    }
886
887
    /**
888
     * @param FormValidator $form
889
     * @param string        $action
890
     */
891
    public function getForm(&$form, $action = 'new')
892
    {
893
        switch ($action) {
894
            case 'new':
895
                $header = get_lang('Add category');
896
                $submit = get_lang('Add test category');
897
898
                break;
899
            case 'edit':
900
                $header = get_lang('Edit this category');
901
                $submit = get_lang('Edit category');
902
903
                break;
904
        }
905
906
        // Setting the form elements
907
        $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...
908
        $form->addElement('hidden', 'category_id');
909
        $form->addElement(
910
            'text',
911
            'category_name',
912
            get_lang('Category name'),
913
            ['class' => 'span6']
914
        );
915
        $form->addHtmlEditor(
916
            'category_description',
917
            get_lang('Category description'),
918
            false,
919
            false,
920
            [
921
                'ToolbarSet' => 'test_category',
922
                'Width' => '90%',
923
                'Height' => '200',
924
            ]
925
        );
926
        $category_parent_list = [];
927
928
        $options = [
929
            '1' => get_lang('Visible'),
930
            '0' => get_lang('Hidden'),
931
        ];
932
        $form->addElement(
933
            'select',
934
            'visibility',
935
            get_lang('Visibility'),
936
            $options
937
        );
938
        $script = null;
939
        if (!empty($this->parent_id)) {
940
            $parent_cat = new self();
941
            $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...
942
            $category_parent_list = [$parent_cat->id => $parent_cat->name];
943
            $script .= '<script>$(function() { $("#parent_id").trigger("addItem",[{"title": "'.$parent_cat->name.'", "value": "'.$parent_cat->id.'"}]); });</script>';
944
        }
945
        $form->addElement('html', $script);
946
947
        $form->addElement('select', 'parent_id', get_lang('Parent'), $category_parent_list, ['id' => 'parent_id']);
948
        $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...
949
950
        // setting the defaults
951
        $defaults = [];
952
        $defaults['category_id'] = $this->id;
953
        $defaults['category_name'] = $this->name;
954
        $defaults['category_description'] = $this->description;
955
        $defaults['parent_id'] = $this->parent_id;
956
        $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...
957
        $form->setDefaults($defaults);
958
959
        // setting the rules
960
        $form->addRule('category_name', get_lang('Required field'), 'required');
961
    }
962
963
    /**
964
     * Returns the category form.
965
     *
966
     * @return string
967
     */
968
    public function returnCategoryForm(Exercise $exercise)
969
    {
970
        $categories = $this->getListOfCategoriesForTest($exercise);
971
        $sortedCategories = [];
972
        foreach ($categories as $catId => $cat) {
973
            $sortedCategories[$cat['title']] = $cat;
974
        }
975
        ksort($sortedCategories);
976
        $saved_categories = $exercise->getCategoriesInExercise();
977
        $return = null;
978
979
        if (!empty($sortedCategories)) {
980
            $nbQuestionsTotal = $exercise->getNumberQuestionExerciseCategory();
981
            $exercise->setCategoriesGrouping(true);
982
            $real_question_count = count($exercise->getQuestionList());
983
984
            $warning = null;
985
            if ($nbQuestionsTotal != $real_question_count) {
986
                $warning = Display::return_message(
987
                    get_lang('Make sure you have enough questions in your categories.'),
988
                    'warning'
989
                );
990
            }
991
992
            $return .= $warning;
993
            $return .= '<table class="table table-hover table-bordered data_table">';
994
            $return .= '<tr>';
995
            $return .= '<th height="24">'.get_lang('Categories').'</th>';
996
            $return .= '<th width="70" height="24">'.get_lang('N°').'</th></tr>';
997
998
            $emptyCategory = [
999
                'id' => '0',
1000
                'name' => get_lang('General'),
1001
                'description' => '',
1002
                'iid' => '0',
1003
                'title' => get_lang('General'),
1004
            ];
1005
1006
            $sortedCategories[] = $emptyCategory;
1007
1008
            foreach ($sortedCategories as $category) {
1009
                $cat_id = $category['iid'];
1010
                $return .= '<tr>';
1011
                $return .= '<td>';
1012
                $return .= Display::div($category['name']);
1013
                $return .= '</td>';
1014
                $return .= '<td>';
1015
                $value = isset($saved_categories) && isset($saved_categories[$cat_id]) ? $saved_categories[$cat_id]['count_questions'] : -1;
1016
                $return .= Display::input(
1017
                    'number',
1018
                    "category[$cat_id]",
1019
                    $value,
1020
                    ['class' => 'form-control', 'min' => -1, 'step' => 1]
1021
                );
1022
                $return .= '</td>';
1023
                $return .= '</tr>';
1024
            }
1025
1026
            $return .= '</table>';
1027
            $return .= get_lang('-1 = All questions will be selected.');
1028
        }
1029
1030
        return $return;
1031
    }
1032
1033
    /**
1034
     * Return true if a category already exists with the same name.
1035
     *
1036
     * @param string $name
1037
     * @param int    $courseId
1038
     *
1039
     * @return bool
1040
     */
1041
    public static function categoryTitleExists($name, $courseId = 0)
1042
    {
1043
        $repo = Container::getQuestionCategoryRepository();
1044
        $courseEntity = api_get_course_entity($courseId);
1045
        $resource = $repo->findResourceByTitle($name, $courseEntity->getResourceNode(), $courseEntity);
1046
1047
        return null !== $resource;
1048
    }
1049
1050
    /**
1051
     * Return the id of the test category with title = $in_title.
1052
     *
1053
     * @param string $title
1054
     * @param int    $courseId
1055
     *
1056
     * @return int is id of test category
1057
     */
1058
    public static function get_category_id_for_title($title, $courseId = 0)
1059
    {
1060
        $out_res = 0;
1061
        if (empty($courseId)) {
1062
            $courseId = api_get_course_int_id();
1063
        }
1064
        $courseId = (int) $courseId;
1065
        $tbl_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
1066
        $sql = "SELECT id FROM $tbl_cat
1067
                WHERE c_id = $courseId AND title = '".Database::escape_string($title)."'";
1068
        $res = Database::query($sql);
1069
        if (Database::num_rows($res) > 0) {
1070
            $data = Database::fetch_array($res);
1071
            $out_res = $data['id'];
1072
        }
1073
1074
        return $out_res;
1075
    }
1076
1077
    /**
1078
     * Add a relation between question and category in table c_quiz_question_rel_category.
1079
     *
1080
     * @param int $categoryId
1081
     * @param int $questionId
1082
     * @param int $courseId
1083
     *
1084
     * @return string|false
1085
     */
1086
    public static function addCategoryToQuestion($categoryId, $questionId, $courseId)
1087
    {
1088
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
1089
        // if question doesn't have a category
1090
        // @todo change for 1.10 when a question can have several categories
1091
        if (0 == self::getCategoryForQuestion($questionId, $courseId) &&
1092
            $questionId > 0 &&
1093
            $courseId > 0
1094
        ) {
1095
            $sql = "INSERT INTO $table (c_id, question_id, category_id)
1096
                    VALUES (".intval($courseId).", ".intval($questionId).", ".intval($categoryId).")";
1097
            Database::query($sql);
1098
            $id = Database::insert_id();
1099
1100
            return $id;
1101
        }
1102
1103
        return false;
1104
    }
1105
1106
    /**
1107
     * @param int $courseId
1108
     * @param int $sessionId
1109
     *
1110
     * @return CQuizQuestionCategory[]
1111
     */
1112
    public static function getCategories($courseId, $sessionId = 0)
1113
    {
1114
        if (empty($courseId)) {
1115
            return [];
1116
        }
1117
1118
        $sessionId = (int) $sessionId;
1119
        $courseId = (int) $courseId;
1120
        $sessionEntity = null;
1121
        if (!empty($sessionId)) {
1122
            $sessionEntity = api_get_session_entity($sessionId);
1123
        }
1124
1125
        $courseEntity = api_get_course_entity($courseId);
1126
        $repo = Container::getQuestionCategoryRepository();
1127
        $resources = $repo->getResourcesByCourse($courseEntity, $sessionEntity);
1128
1129
        return $resources->getQuery()->getResult();
1130
    }
1131
1132
    /**
1133
     * @param int $courseId
1134
     * @param int $sessionId
1135
     *
1136
     * @return string
1137
     */
1138
    public function displayCategories($courseId, $sessionId = 0)
1139
    {
1140
        $course = api_get_course_entity($courseId);
1141
        $session = api_get_session_entity($sessionId);
1142
1143
        $sessionId = (int) $sessionId;
1144
        $categories = $this->getCategories($courseId, $sessionId);
1145
        $html = '';
1146
        foreach ($categories as $category) {
1147
            $id = $category->getIid();
1148
            $nb_question = $category->getQuestions()->count();
1149
            $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

1149
            /** @scrutinizer ignore-call */ 
1150
            $rowname = self::protectJSDialogQuote($category->getTitle());
Loading history...
1150
            $nb_question_label = 1 == $nb_question ? $nb_question.' '.get_lang('Question') : $nb_question.' '.get_lang(
1151
                    'Questions'
1152
                );
1153
            $content = "<span style='float:right'>".$nb_question_label."</span>";
1154
            $content .= '<div class="sectioncomment">';
1155
            $content .= $category->getDescription();
1156
            $content .= '</div>';
1157
            $links = '';
1158
1159
            if (!$sessionId) {
1160
                $links .= '<a href="'.api_get_self(
1161
                    ).'?action=editcategory&category_id='.$id.'&'.api_get_cidreq().'">'.
1162
                    Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
1163
                $links .= ' <a href="'.api_get_self().'?'.api_get_cidreq(
1164
                    ).'&action=deletecategory&category_id='.$id.'" ';
1165
                $links .= 'onclick="return confirmDelete(\''.self::protectJSDialogQuote(
1166
                        get_lang('DeleteCategoryAreYouSure').'['.$rowname
1167
                    ).'] ?\', \'id_cat'.$id.'\');">';
1168
                $links .= Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_SMALL).'</a>';
1169
            }
1170
1171
            $html .= Display::panel($content, $category->getTitle().$links);
1172
        }
1173
1174
        return $html;
1175
    }
1176
1177
    /**
1178
     * To allowed " in javascript dialog box without bad surprises
1179
     * replace " with two '.
1180
     *
1181
     * @param string $text
1182
     *
1183
     * @return mixed
1184
     */
1185
    public function protectJSDialogQuote($text)
1186
    {
1187
        $res = $text;
1188
        $res = str_replace("'", "\'", $res);
1189
        // 8. Set route and request
1190
        $res = str_replace('"', "\'\'", $res);
1191
1192
        return $res;
1193
    }
1194
}
1195