Completed
Push — master ( cf4f4c...f3c478 )
by Julito
36:36
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
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\Resource\ResourceLink;
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 id = $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['id'];
54
            $this->name = $row['title'];
55
            $this->description = $row['description'];
56
57
            return $this;
58
        }
59
60
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type TestCategory.
Loading history...
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
            $category = new CQuizQuestionCategory();
89
            $category
90
                ->setTitle($this->name)
91
                ->setCourse($courseInfo['entity'])
92
                ->setDescription($this->description);
93
            $em = $repo->getEntityManager();
94
            $em->persist($category);
95
            $em->flush();
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
                $newId = $category->getIid();
99
                $sql = "UPDATE $table SET id = iid WHERE iid = $newId";
100
                Database::query($sql);
101
                $repo->addResourceToCourse(
102
                    $category,
103
                    ResourceLink::VISIBILITY_PUBLISHED,
104
                    api_get_user_entity(api_get_user_id()),
105
                    $courseInfo['entity'],
106
                    api_get_session_entity(),
107
                    api_get_group_entity()
108
                );
109
110
                /*api_item_property_update(
111
                    $courseInfo,
112
                    TOOL_TEST_CATEGORY,
113
                    $newId,
114
                    'TestCategoryAdded',
115
                    api_get_user_id()
116
                );*/
117
118
                return $newId;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $newId returns the type integer which is incompatible with the documented return type boolean.
Loading history...
119
            }
120
        }
121
122
        return false;
123
    }
124
125
    /**
126
     * Removes the category from the database
127
     * if there were question in this category, the link between question and category is removed.
128
     *
129
     * @param int $id
130
     *
131
     * @return bool
132
     */
133
    public function removeCategory($id)
134
    {
135
        $tbl_question_rel_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
136
        $id = (int) $id;
137
        $course_id = api_get_course_int_id();
138
        $category = $this->getCategory($id, $course_id);
139
140
        if ($category) {
0 ignored issues
show
introduced by
$category is of type TestCategory, thus it always evaluated to true.
Loading history...
141
            /*$sql = "DELETE FROM $table
142
                    WHERE id= $id AND c_id=".$course_id;
143
            Database::query($sql);*/
144
145
            // remove link between question and category
146
            $sql = "DELETE FROM $tbl_question_rel_cat
147
                    WHERE category_id = $id AND c_id=".$course_id;
148
            Database::query($sql);
149
150
            $repo = Container::getQuestionCategoryRepository();
151
            $category = $repo->find($id);
152
            $repo->hardDelete($category);
0 ignored issues
show
Bug introduced by
It seems like $category can also be of type null; however, parameter $resource of Chamilo\CoreBundle\Repos...epository::hardDelete() does only seem to accept Chamilo\CoreBundle\Entit...source\AbstractResource, maybe add an additional type check? ( Ignorable by Annotation )

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

152
            $repo->hardDelete(/** @scrutinizer ignore-type */ $category);
Loading history...
153
154
            //Database::query($sql);
155
            // item_property update
156
            /*$courseInfo = api_get_course_info_by_id($course_id);
157
            api_item_property_update(
158
                $courseInfo,
159
                TOOL_TEST_CATEGORY,
160
                $this->id,
161
                'TestCategoryDeleted',
162
                api_get_user_id()
163
            );*/
164
165
            return true;
166
        }
167
168
        return false;
169
    }
170
171
    /**
172
     * Modify category name or description of category with id=in_id.
173
     *
174
     * @param int $courseId
175
     *
176
     * @return bool
177
     */
178
    public function modifyCategory($courseId = 0)
179
    {
180
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
181
        $courseInfo = api_get_course_info_by_id($courseId);
182
        if (empty($courseInfo)) {
183
            return false;
184
        }
185
186
        $repo = Container::getQuestionCategoryRepository();
187
        /** @var CQuizQuestionCategory $category */
188
        $category = $repo->find($this->id);
189
        if ($category) {
0 ignored issues
show
introduced by
$category is of type Chamilo\CourseBundle\Entity\CQuizQuestionCategory, thus it always evaluated to true.
Loading history...
190
            $category
191
                ->setTitle($this->name)
192
                ->setDescription($this->description)
193
            ;
194
195
            $repo->getEntityManager()->persist($category);
196
            $repo->getEntityManager()->flush();
197
            // item_property update
198
            /*api_item_property_update(
199
                $courseInfo,
200
                TOOL_TEST_CATEGORY,
201
                $this->id,
202
                'TestCategoryModified',
203
                api_get_user_id()
204
            );*/
205
            return true;
206
        }
207
208
        return false;
209
    }
210
211
    /**
212
     * Gets the number of question of category id=in_id.
213
     *
214
     * @param int $id
215
     *
216
     * @return int
217
     */
218
    public function getCategoryQuestionsNumber($id): int
219
    {
220
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
221
        $id = (int) $id;
222
223
        $sql = "SELECT count(*) AS nb
224
                FROM $table
225
                WHERE category_id = $id AND c_id=".api_get_course_int_id();
226
        $res = Database::query($sql);
227
        $row = Database::fetch_array($res);
228
229
        return (int) $row['nb'];
230
    }
231
232
    /**
233
     * Return the TestCategory id for question with question_id = $questionId
234
     * In this version, a question has only 1 TestCategory.
235
     * Return the TestCategory id, 0 if none.
236
     *
237
     * @param int $questionId
238
     * @param int $courseId
239
     *
240
     * @return int
241
     */
242
    public static function getCategoryForQuestion($questionId, $courseId = 0)
243
    {
244
        $courseId = (int) $courseId;
245
        $questionId = (int) $questionId;
246
247
        if (empty($courseId)) {
248
            $courseId = api_get_course_int_id();
249
        }
250
251
        if (empty($courseId) || empty($questionId)) {
252
            return 0;
253
        }
254
255
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
256
        $sql = "SELECT category_id
257
                FROM $table
258
                WHERE question_id = $questionId AND c_id = $courseId";
259
        $res = Database::query($sql);
260
        $result = 0;
261
        if (Database::num_rows($res) > 0) {
262
            $data = Database::fetch_array($res);
263
            $result = (int) $data['category_id'];
264
        }
265
266
        return $result;
267
    }
268
269
    /**
270
     * Return the category name for question with question_id = $questionId
271
     * In this version, a question has only 1 category.
272
     *
273
     * @param $questionId
274
     * @param int $courseId
275
     *
276
     * @return string
277
     */
278
    public static function getCategoryNameForQuestion($questionId, $courseId = 0)
279
    {
280
        if (empty($courseId)) {
281
            $courseId = api_get_course_int_id();
282
        }
283
        $courseId = (int) $courseId;
284
        $categoryId = self::getCategoryForQuestion($questionId, $courseId);
285
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
286
        $sql = "SELECT title 
287
                FROM $table
288
                WHERE id = $categoryId AND c_id = $courseId";
289
        $res = Database::query($sql);
290
        $data = Database::fetch_array($res);
291
        $result = '';
292
        if (Database::num_rows($res) > 0) {
293
            $result = $data['title'];
294
        }
295
296
        return $result;
297
    }
298
299
    /**
300
     * Return the list of differents categories ID for a test in the current course
301
     * input : test_id
302
     * return : array of category id (integer)
303
     * hubert.borderiou 07-04-2011.
304
     *
305
     * @param int $exerciseId
306
     * @param int $courseId
307
     *
308
     * @return array
309
     */
310
    public static function getListOfCategoriesIDForTest($exerciseId, $courseId = 0)
311
    {
312
        // parcourir les questions d'un test, recup les categories uniques dans un tableau
313
        $exercise = new Exercise($courseId);
314
        $exercise->read($exerciseId, false);
315
        $categoriesInExercise = $exercise->getQuestionWithCategories();
316
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
317
        $categories = [];
318
        if (!empty($categoriesInExercise)) {
319
            foreach ($categoriesInExercise as $category) {
320
                $categories[$category['id']] = $category;
321
            }
322
        }
323
324
        return $categories;
325
    }
326
327
    /**
328
     * @param Exercise $exercise
329
     *
330
     * @return array
331
     */
332
    public static function getListOfCategoriesIDForTestObject(Exercise $exercise)
333
    {
334
        // parcourir les questions d'un test, recup les categories uniques dans un tableau
335
        $categories_in_exercise = [];
336
        $question_list = $exercise->getQuestionOrderedListByName();
337
338
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
339
        foreach ($question_list as $questionInfo) {
340
            $question_id = $questionInfo['question_id'];
341
            $category_list = self::getCategoryForQuestion($question_id);
342
            if (is_numeric($category_list)) {
343
                $category_list = [$category_list];
344
            }
345
346
            if (!empty($category_list)) {
347
                $categories_in_exercise = array_merge($categories_in_exercise, $category_list);
348
            }
349
        }
350
        if (!empty($categories_in_exercise)) {
351
            $categories_in_exercise = array_unique(array_filter($categories_in_exercise));
352
        }
353
354
        return $categories_in_exercise;
355
    }
356
357
    /**
358
     * Return the list of different categories NAME for a test.
359
     *
360
     * @param int $exerciseId
361
     * @param bool
362
     *
363
     * @return array
364
     *
365
     * @author function rewrote by jmontoya
366
     */
367
    public static function getListOfCategoriesNameForTest($exerciseId, $grouped_by_category = true)
0 ignored issues
show
Unused Code introduced by
The parameter $grouped_by_category is not used and could be removed. ( Ignorable by Annotation )

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

367
    public static function getListOfCategoriesNameForTest($exerciseId, /** @scrutinizer ignore-unused */ $grouped_by_category = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
368
    {
369
        $result = [];
370
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
371
372
        foreach ($categories as $catInfo) {
373
            $categoryId = $catInfo['id'];
374
            if (!empty($categoryId)) {
375
                $result[$categoryId] = [
376
                    'title' => $catInfo['title'],
377
                    //'parent_id' =>  $catInfo['parent_id'],
378
                    'parent_id' => '',
379
                    'c_id' => $catInfo['c_id'],
380
                ];
381
            }
382
        }
383
384
        return $result;
385
    }
386
387
    /**
388
     * @param Exercise $exercise
389
     *
390
     * @return array
391
     */
392
    public static function getListOfCategoriesForTest(Exercise $exercise)
393
    {
394
        $result = [];
395
        $categories = self::getListOfCategoriesIDForTestObject($exercise);
396
        foreach ($categories as $cat_id) {
397
            $cat = new TestCategory();
398
            $cat = (array) $cat->getCategory($cat_id);
399
            $cat['iid'] = $cat['id'];
400
            $cat['title'] = $cat['name'];
401
            $result[$cat['id']] = $cat;
402
        }
403
404
        return $result;
405
    }
406
407
    /**
408
     * return the number of question of a category id in a test.
409
     *
410
     * @param int $exerciseId
411
     * @param int $categoryId
412
     *
413
     * @return int
414
     *
415
     * @author hubert.borderiou 07-04-2011
416
     */
417
    public static function getNumberOfQuestionsInCategoryForTest($exerciseId, $categoryId)
418
    {
419
        $nbCatResult = 0;
420
        $quiz = new Exercise();
421
        $quiz->read($exerciseId);
422
        $questionList = $quiz->selectQuestionList();
423
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ? ? ?
424
        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...
425
            if (self::getCategoryForQuestion($questionList[$i]) == $categoryId) {
426
                $nbCatResult++;
427
            }
428
        }
429
430
        return $nbCatResult;
431
    }
432
433
    /**
434
     * return the number of question for a test using random by category
435
     * input  : test_id, number of random question (min 1).
436
     *
437
     * @param int $exerciseId
438
     * @param int $random
439
     *
440
     * @return int
441
     *             hubert.borderiou 07-04-2011
442
     *             question without categories are not counted
443
     */
444
    public static function getNumberOfQuestionRandomByCategory($exerciseId, $random)
445
    {
446
        $count = 0;
447
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
448
        foreach ($categories as $category) {
449
            if (empty($category['id'])) {
450
                continue;
451
            }
452
453
            $nbQuestionInThisCat = self::getNumberOfQuestionsInCategoryForTest(
454
                $exerciseId,
455
                $category['id']
456
            );
457
458
            if ($nbQuestionInThisCat > $random) {
459
                $count += $random;
460
            } else {
461
                $count += $nbQuestionInThisCat;
462
            }
463
        }
464
465
        return $count;
466
    }
467
468
    /**
469
     * @return array
470
     */
471
    public static function getCategoriesForSelect()
472
    {
473
        $courseId = api_get_course_int_id();
474
        $categories = self::getCategories($courseId);
475
476
        $result = ['0' => get_lang('GeneralSelected')];
477
        /** @var CQuizQuestionCategory $category */
478
        foreach ($categories as $category) {
479
            $result[$category->getId()] = $category->getTitle();
480
        }
481
482
        return $result;
483
    }
484
485
    /**
486
     * Returns an array of question ids for each category
487
     * $categories[1][30] = 10, array with category id = 1 and question_id = 10
488
     * A question has "n" categories.
489
     *
490
     * @param int   $exerciseId
491
     * @param array $check_in_question_list
492
     * @param array $categoriesAddedInExercise
493
     *
494
     * @return array
495
     */
496
    public static function getQuestionsByCat(
497
        $exerciseId,
498
        $check_in_question_list = [],
499
        $categoriesAddedInExercise = []
500
    ) {
501
        $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
502
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
503
        $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
504
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
505
        $exerciseId = (int) $exerciseId;
506
        $courseId = api_get_course_int_id();
507
508
        $sql = "SELECT DISTINCT qrc.question_id, qrc.category_id
509
                FROM $TBL_QUESTION_REL_CATEGORY qrc
510
                INNER JOIN $TBL_EXERCICE_QUESTION eq
511
                ON (eq.question_id = qrc.question_id AND qrc.c_id = eq.c_id)
512
                INNER JOIN $categoryTable c
513
                ON (c.id = qrc.category_id AND c.c_id = eq.c_id)
514
                INNER JOIN $tableQuestion q
515
                ON (q.id = qrc.question_id AND q.c_id = eq.c_id)
516
                WHERE
517
                    exercice_id = $exerciseId AND
518
                    qrc.c_id = $courseId
519
                ";
520
521
        $res = Database::query($sql);
522
        $categories = [];
523
        while ($data = Database::fetch_array($res)) {
524
            if (!empty($check_in_question_list)) {
525
                if (!in_array($data['question_id'], $check_in_question_list)) {
526
                    continue;
527
                }
528
            }
529
530
            if (!isset($categories[$data['category_id']]) ||
531
                !is_array($categories[$data['category_id']])
532
            ) {
533
                $categories[$data['category_id']] = [];
534
            }
535
536
            $categories[$data['category_id']][] = $data['question_id'];
537
        }
538
539
        if (!empty($categoriesAddedInExercise)) {
540
            $newCategoryList = [];
541
            foreach ($categoriesAddedInExercise as $category) {
542
                $categoryId = $category['category_id'];
543
                if (isset($categories[$categoryId])) {
544
                    $newCategoryList[$categoryId] = $categories[$categoryId];
545
                }
546
            }
547
548
            $checkQuestionsWithNoCategory = false;
549
            foreach ($categoriesAddedInExercise as $category) {
550
                if (empty($category['category_id'])) {
551
                    // Check
552
                    $checkQuestionsWithNoCategory = true;
553
                    break;
554
                }
555
            }
556
557
            // Select questions that don't have any category related
558
            if ($checkQuestionsWithNoCategory) {
559
                $originalQuestionList = $check_in_question_list;
560
                foreach ($originalQuestionList as $questionId) {
561
                    $categoriesFlatten = array_flatten($categories);
562
                    if (!in_array($questionId, $categoriesFlatten)) {
563
                        $newCategoryList[0][] = $questionId;
564
                    }
565
                }
566
            }
567
            $categories = $newCategoryList;
568
        }
569
570
        return $categories;
571
    }
572
573
    /**
574
     * Returns an array of $numberElements from $array.
575
     *
576
     * @param array
577
     * @param int
578
     *
579
     * @return array
580
     */
581
    public static function getNElementsFromArray($array, $numberElements)
582
    {
583
        $list = $array;
584
        shuffle($list);
585
        if ($numberElements < count($list)) {
586
            $list = array_slice($list, 0, $numberElements);
587
        }
588
589
        return $list;
590
    }
591
592
    /**
593
     * @param int $questionId
594
     * @param int $displayCategoryName
595
     */
596
    public static function displayCategoryAndTitle($questionId, $displayCategoryName = 1)
597
    {
598
        echo self::returnCategoryAndTitle($questionId, $displayCategoryName);
599
    }
600
601
    /**
602
     * @param int $questionId
603
     * @param int $in_display_category_name
604
     *
605
     * @return string|null
606
     */
607
    public static function returnCategoryAndTitle($questionId, $in_display_category_name = 1)
608
    {
609
        $is_student = !(api_is_allowed_to_edit(null, true) || api_is_session_admin());
610
        $objExercise = Session::read('objExercise');
611
        if (!empty($objExercise)) {
612
            $in_display_category_name = $objExercise->display_category_name;
613
        }
614
        $content = null;
615
        if (self::getCategoryNameForQuestion($questionId) != '' &&
616
            ($in_display_category_name == 1 || !$is_student)
617
        ) {
618
            $content .= '<div class="page-header">';
619
            $content .= '<h4>'.get_lang('Category').": ".self::getCategoryNameForQuestion($questionId).'</h4>';
620
            $content .= "</div>";
621
        }
622
623
        return $content;
624
    }
625
626
    /**
627
     * sortTabByBracketLabel ($tabCategoryQuestions)
628
     * key of $tabCategoryQuestions are the category id (0 for not in a category)
629
     * value is the array of question id of this category
630
     * Sort question by Category.
631
     */
632
    public static function sortTabByBracketLabel($in_tab)
633
    {
634
        $tabResult = [];
635
        $tabCatName = []; // tab of category name
636
        foreach ($in_tab as $cat_id => $tabquestion) {
637
            $category = new TestCategory();
638
            $category = $category->getCategory($cat_id);
639
            $tabCatName[$cat_id] = $category->name;
640
        }
641
        reset($in_tab);
642
        // sort table by value, keeping keys as they are
643
        asort($tabCatName);
644
        // keys of $tabCatName are keys order for $in_tab
645
        foreach ($tabCatName as $key => $val) {
646
            $tabResult[$key] = $in_tab[$key];
647
        }
648
649
        return $tabResult;
650
    }
651
652
    /**
653
     * Return the number max of question in a category
654
     * count the number of questions in all categories, and return the max.
655
     *
656
     * @param int $exerciseId
657
     *
658
     * @author - hubert borderiou
659
     *
660
     * @return int
661
     */
662
    public static function getNumberMaxQuestionByCat($exerciseId)
663
    {
664
        $res_num_max = 0;
665
        // foreach question
666
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
667
        foreach ($categories as $category) {
668
            if (empty($category['id'])) {
669
                continue;
670
            }
671
672
            $nbQuestionInThisCat = self::getNumberOfQuestionsInCategoryForTest(
673
                $exerciseId,
674
                $category['id']
675
            );
676
677
            if ($nbQuestionInThisCat > $res_num_max) {
678
                $res_num_max = $nbQuestionInThisCat;
679
            }
680
        }
681
682
        return $res_num_max;
683
    }
684
685
    /**
686
     * Returns a category summary report.
687
     *
688
     * @param int   $exerciseId
689
     * @param array $category_list
690
     *                             pre filled array with the category_id, score, and weight
691
     *                             example: array(1 => array('score' => '10', 'total' => 20));
692
     *
693
     * @return string
694
     */
695
    public static function get_stats_table_by_attempt(
696
        $exerciseId,
697
        $category_list = []
698
    ) {
699
        if (empty($category_list)) {
700
            return null;
701
        }
702
        $category_name_list = self::getListOfCategoriesNameForTest($exerciseId);
703
704
        $table = new HTML_Table(['class' => 'table table-bordered', 'id' => 'category_results']);
705
        $table->setHeaderContents(0, 0, get_lang('Categories'));
706
        $table->setHeaderContents(0, 1, get_lang('Absolute score'));
707
        $table->setHeaderContents(0, 2, get_lang('Relative score'));
708
        $row = 1;
709
710
        $none_category = [];
711
        if (isset($category_list['none'])) {
712
            $none_category = $category_list['none'];
713
            unset($category_list['none']);
714
        }
715
716
        $total = [];
717
        if (isset($category_list['total'])) {
718
            $total = $category_list['total'];
719
            unset($category_list['total']);
720
        }
721
        if (count($category_list) > 1) {
722
            foreach ($category_list as $category_id => $category_item) {
723
                $table->setCellContents($row, 0, $category_name_list[$category_id]);
724
                $table->setCellContents(
725
                    $row,
726
                    1,
727
                    ExerciseLib::show_score(
728
                        $category_item['score'],
729
                        $category_item['total'],
730
                        false
731
                    )
732
                );
733
                $table->setCellContents(
734
                    $row,
735
                    2,
736
                    ExerciseLib::show_score(
737
                        $category_item['score'],
738
                        $category_item['total'],
739
                        true,
740
                        false,
741
                        true
742
                    )
743
                );
744
                $row++;
745
            }
746
747
            if (!empty($none_category)) {
748
                $table->setCellContents($row, 0, get_lang('none'));
749
                $table->setCellContents(
750
                    $row,
751
                    1,
752
                    ExerciseLib::show_score(
753
                        $none_category['score'],
754
                        $none_category['total'],
755
                        false
756
                    )
757
                );
758
                $table->setCellContents(
759
                    $row,
760
                    2,
761
                    ExerciseLib::show_score(
762
                        $none_category['score'],
763
                        $none_category['total'],
764
                        true,
765
                        false,
766
                        true
767
                    )
768
                );
769
                $row++;
770
            }
771
            if (!empty($total)) {
772
                $table->setCellContents($row, 0, get_lang('Total'));
773
                $table->setCellContents(
774
                    $row,
775
                    1,
776
                    ExerciseLib::show_score(
777
                        $total['score'],
778
                        $total['total'],
779
                        false
780
                    )
781
                );
782
                $table->setCellContents(
783
                    $row,
784
                    2,
785
                    ExerciseLib::show_score(
786
                        $total['score'],
787
                        $total['total'],
788
                        true,
789
                        false,
790
                        true
791
                    )
792
                );
793
            }
794
795
            return $table->toHtml();
796
        }
797
798
        return '';
799
    }
800
801
    /**
802
     * @param Exercise $exercise
803
     * @param int      $courseId
804
     * @param string   $order
805
     * @param bool     $shuffle
806
     * @param bool     $excludeCategoryWithNoQuestions
807
     *
808
     * @return array
809
     */
810
    public function getCategoryExerciseTree(
811
        $exercise,
812
        $courseId,
813
        $order = null,
814
        $shuffle = false,
815
        $excludeCategoryWithNoQuestions = true
816
    ) {
817
        if (empty($exercise)) {
818
            return [];
819
        }
820
821
        $courseId = (int) $courseId;
822
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
823
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
824
        $exercise->id = (int) $exercise->id;
825
826
        $sql = "SELECT * FROM $table qc
827
                LEFT JOIN $categoryTable c
828
                ON (qc.c_id = c.c_id AND c.id = qc.category_id)
829
                WHERE qc.c_id = $courseId AND exercise_id = {$exercise->id} ";
830
831
        if (!empty($order)) {
832
            $order = Database::escape_string($order);
833
            $sql .= "ORDER BY $order";
834
        }
835
836
        $categories = [];
837
        $result = Database::query($sql);
838
        if (Database::num_rows($result)) {
839
            while ($row = Database::fetch_array($result, 'ASSOC')) {
840
                if ($excludeCategoryWithNoQuestions) {
841
                    if ($row['count_questions'] == 0) {
842
                        continue;
843
                    }
844
                }
845
                if (empty($row['title']) && empty($row['category_id'])) {
846
                    $row['title'] = get_lang('General');
847
                }
848
                $categories[$row['category_id']] = $row;
849
            }
850
        }
851
852
        if ($shuffle) {
853
            shuffle_assoc($categories);
854
        }
855
856
        return $categories;
857
    }
858
859
    /**
860
     * @param FormValidator $form
861
     * @param string        $action
862
     */
863
    public function getForm(&$form, $action = 'new')
864
    {
865
        switch ($action) {
866
            case 'new':
867
                $header = get_lang('Add category');
868
                $submit = get_lang('Add test category');
869
                break;
870
            case 'edit':
871
                $header = get_lang('Edit this category');
872
                $submit = get_lang('Edit category');
873
                break;
874
        }
875
876
        // Setting the form elements
877
        $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...
878
        $form->addElement('hidden', 'category_id');
879
        $form->addElement(
880
            'text',
881
            'category_name',
882
            get_lang('Category name'),
883
            ['class' => 'span6']
884
        );
885
        $form->add_html_editor(
886
            'category_description',
887
            get_lang('Category description'),
888
            false,
889
            false,
890
            [
891
                'ToolbarSet' => 'test_category',
892
                'Width' => '90%',
893
                'Height' => '200',
894
            ]
895
        );
896
        $category_parent_list = [];
897
898
        $options = [
899
                '1' => get_lang('Visible'),
900
                '0' => get_lang('Hidden'),
901
        ];
902
        $form->addElement(
903
            'select',
904
            'visibility',
905
            get_lang('Visibility'),
906
            $options
907
        );
908
        $script = null;
909
        if (!empty($this->parent_id)) {
910
            $parent_cat = new TestCategory();
911
            $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...
912
            $category_parent_list = [$parent_cat->id => $parent_cat->name];
913
            $script .= '<script>$(function() { $("#parent_id").trigger("addItem",[{"title": "'.$parent_cat->name.'", "value": "'.$parent_cat->id.'"}]); });</script>';
914
        }
915
        $form->addElement('html', $script);
916
917
        $form->addElement('select', 'parent_id', get_lang('Parent'), $category_parent_list, ['id' => 'parent_id']);
918
        $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...
919
920
        // setting the defaults
921
        $defaults = [];
922
        $defaults["category_id"] = $this->id;
923
        $defaults["category_name"] = $this->name;
924
        $defaults["category_description"] = $this->description;
925
        $defaults["parent_id"] = $this->parent_id;
926
        $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...
927
        $form->setDefaults($defaults);
928
929
        // setting the rules
930
        $form->addRule('category_name', get_lang('Required field'), 'required');
931
    }
932
933
    /**
934
     * Returns the category form.
935
     *
936
     * @param Exercise $exercise
937
     *
938
     * @return string
939
     */
940
    public function returnCategoryForm(Exercise $exercise)
941
    {
942
        $categories = $this->getListOfCategoriesForTest($exercise);
943
        $saved_categories = $exercise->getCategoriesInExercise();
944
        $return = null;
945
946
        if (!empty($categories)) {
947
            $nbQuestionsTotal = $exercise->getNumberQuestionExerciseCategory();
948
            $exercise->setCategoriesGrouping(true);
949
            $real_question_count = count($exercise->getQuestionList());
950
951
            $warning = null;
952
            if ($nbQuestionsTotal != $real_question_count) {
953
                $warning = Display::return_message(
954
                    get_lang('Make sure you have enough questions in your categories.'),
955
                    'warning'
956
                );
957
            }
958
959
            $return .= $warning;
960
            $return .= '<table class="data_table">';
961
            $return .= '<tr>';
962
            $return .= '<th height="24">'.get_lang('Categories').'</th>';
963
            $return .= '<th width="70" height="24">'.get_lang('N°').'</th></tr>';
964
965
            $emptyCategory = [
966
                'id' => '0',
967
                'name' => get_lang('General'),
968
                'description' => '',
969
                'iid' => '0',
970
                'title' => get_lang('General'),
971
            ];
972
973
            $categories[] = $emptyCategory;
974
975
            foreach ($categories as $category) {
976
                $cat_id = $category['iid'];
977
                $return .= '<tr>';
978
                $return .= '<td>';
979
                $return .= Display::div($category['name']);
980
                $return .= '</td>';
981
                $return .= '<td>';
982
                $value = isset($saved_categories) && isset($saved_categories[$cat_id]) ? $saved_categories[$cat_id]['count_questions'] : -1;
983
                $return .= '<input name="category['.$cat_id.']" value="'.$value.'" />';
984
                $return .= '</td>';
985
                $return .= '</tr>';
986
            }
987
988
            $return .= '</table>';
989
            $return .= get_lang('-1 = All questions will be selected.');
990
        }
991
992
        return $return;
993
    }
994
995
    /**
996
     * Return true if a category already exists with the same name.
997
     *
998
     * @param string $name
999
     * @param int    $courseId
1000
     *
1001
     * @return bool
1002
     */
1003
    public static function categoryTitleExists($name, $courseId)
1004
    {
1005
        $repo = Container::getQuestionCategoryRepository();
1006
        $criteria = [
1007
            'title' => $name,
1008
            'course' => $courseId,
1009
        ];
1010
1011
        return $repo->getRepository()->count($criteria) > 0;
1012
    }
1013
1014
    /**
1015
     * Return the id of the test category with title = $in_title.
1016
     *
1017
     * @param string $title
1018
     * @param int    $courseId
1019
     *
1020
     * @return int is id of test category
1021
     */
1022
    public static function get_category_id_for_title($title, $courseId = 0)
1023
    {
1024
        $out_res = 0;
1025
        if (empty($courseId)) {
1026
            $courseId = api_get_course_int_id();
1027
        }
1028
        $courseId = (int) $courseId;
1029
        $tbl_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
1030
        $sql = "SELECT id FROM $tbl_cat
1031
                WHERE c_id = $courseId AND title = '".Database::escape_string($title)."'";
1032
        $res = Database::query($sql);
1033
        if (Database::num_rows($res) > 0) {
1034
            $data = Database::fetch_array($res);
1035
            $out_res = $data['id'];
1036
        }
1037
1038
        return $out_res;
1039
    }
1040
1041
    /**
1042
     * Add a relation between question and category in table c_quiz_question_rel_category.
1043
     *
1044
     * @param int $categoryId
1045
     * @param int $questionId
1046
     * @param int $courseId
1047
     *
1048
     * @return string|false
1049
     */
1050
    public static function addCategoryToQuestion($categoryId, $questionId, $courseId)
1051
    {
1052
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
1053
        // if question doesn't have a category
1054
        // @todo change for 1.10 when a question can have several categories
1055
        if (self::getCategoryForQuestion($questionId, $courseId) == 0 &&
1056
            $questionId > 0 &&
1057
            $courseId > 0
1058
        ) {
1059
            $sql = "INSERT INTO $table (c_id, question_id, category_id)
1060
                    VALUES (".intval($courseId).", ".intval($questionId).", ".intval($categoryId).")";
1061
            Database::query($sql);
1062
            $id = Database::insert_id();
1063
1064
            return $id;
1065
        }
1066
1067
        return false;
1068
    }
1069
1070
    /**
1071
     * @param int $courseId
1072
     * @param int $sessionId
1073
     *
1074
     * @return array
1075
     */
1076
    public static function getCategories($courseId, $sessionId = 0)
1077
    {
1078
        if (empty($courseId)) {
1079
            return [];
1080
        }
1081
1082
        $sessionId = (int) $sessionId;
1083
        $courseId = (int) $courseId;
1084
        $sessionEntity = null;
1085
        if (!empty($sessionId)) {
1086
            $sessionEntity = api_get_session_entity($sessionId);
1087
        }
1088
        if (empty($sessionId)) {
1089
            $sessionCondition = api_get_session_condition(
1090
                $sessionId,
1091
                true,
1092
                false,
1093
                'i.session_id'
1094
            );
1095
        } else {
1096
            $sessionCondition = api_get_session_condition(
1097
                $sessionId,
1098
                true,
1099
                true,
1100
                'i.session_id'
1101
            );
1102
        }
1103
1104
        $courseEntity = api_get_course_entity($courseId);
1105
        $repo = Container::getQuestionCategoryRepository();
1106
        $resources = $repo->getResourcesByCourse($courseEntity, $sessionEntity);
1107
1108
        return $resources;
1109
    }
1110
1111
    /**
1112
     * @param int $courseId
1113
     * @param int $sessionId
1114
     *
1115
     * @return string
1116
     */
1117
    public function displayCategories($courseId, $sessionId = 0)
1118
    {
1119
        $sessionId = (int) $sessionId;
1120
        $categories = $this->getCategories($courseId, $sessionId);
1121
        $html = '';
1122
        $deleteIcon = Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_SMALL);
1123
        /** @var CQuizQuestionCategory $category */
1124
        foreach ($categories as $category) {
1125
            $id = $category->getId();
1126
            $count = $this->getCategoryQuestionsNumber($id);
1127
            $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

1127
            /** @scrutinizer ignore-call */ 
1128
            $rowname = self::protectJSDialogQuote($category->getTitle());
Loading history...
1128
            $label = $count == 1 ? $count.' '.get_lang('Question') : $count.' '.get_lang('Questions');
1129
            $content = "<span style='float:right'>".$label."</span>";
1130
            $content .= '<div class="sectioncomment">';
1131
            $content .= $category->getTitle();
1132
            $content .= '</div>';
1133
1134
            $links = '';
1135
            if (!$sessionId) {
1136
                $links .= '<a href="'.api_get_self().'?action=editcategory&category_id='.$id.'&'.api_get_cidreq().'">'.
1137
                    Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
1138
                $links .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=deletecategory&category_id='.$id.'" ';
1139
                $links .= 'onclick="return confirmDelete(\''.self::protectJSDialogQuote(get_lang('Are you sure you want to delete this category?').'['.$rowname).'] ?\', \'id_cat'.$id.'\');">';
1140
                $links .= $deleteIcon.'</a>';
1141
            }
1142
            $html .= Display::panel($content, $category->getTitle().$links);
1143
        }
1144
1145
        return $html;
1146
    }
1147
1148
    /**
1149
     * To allowed " in javascript dialog box without bad surprises
1150
     * replace " with two '.
1151
     *
1152
     * @param string $text
1153
     *
1154
     * @return mixed
1155
     */
1156
    public function protectJSDialogQuote($text)
1157
    {
1158
        $res = $text;
1159
        $res = str_replace("'", "\'", $res);
1160
        // super astuce pour afficher les " dans les boite de dialogue
1161
        $res = str_replace('"', "\'\'", $res);
1162
1163
        return $res;
1164
    }
1165
}
1166