Passed
Push — 1.11.x ( ed53f7...9ff550 )
by Julito
13:55
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 ChamiloSession as Session;
6
7
/**
8
 * Class TestCategory.
9
 * Manage question categories inside an exercise.
10
 *
11
 * @author hubert.borderiou
12
 * @author Julio Montoya - several fixes
13
 *
14
 * @todo rename to ExerciseCategory
15
 */
16
class TestCategory
17
{
18
    public $id;
19
    public $name;
20
    public $description;
21
22
    /**
23
     * Constructor of the class Category.
24
     */
25
    public function __construct()
26
    {
27
        $this->name = '';
28
        $this->description = '';
29
    }
30
31
    /**
32
     * return the TestCategory object with id=in_id.
33
     *
34
     * @param int $id
35
     * @param int $courseId
36
     *
37
     * @return TestCategory
38
     */
39
    public function getCategory($id, $courseId = 0)
40
    {
41
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
42
        $id = (int) $id;
43
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
44
        $sql = "SELECT * FROM $table
45
                WHERE id = $id AND c_id = ".$courseId;
46
        $res = Database::query($sql);
47
48
        if (Database::num_rows($res)) {
49
            $row = Database::fetch_array($res);
50
51
            $this->id = $row['id'];
52
            $this->name = $row['title'];
53
            $this->description = $row['description'];
54
55
            return $this;
56
        }
57
58
        return false;
59
    }
60
61
    /**
62
     * Save TestCategory in the database if name doesn't exists.
63
     *
64
     * @param int $courseId
65
     *
66
     * @return bool
67
     */
68
    public function save($courseId = 0)
69
    {
70
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
71
        $courseInfo = api_get_course_info_by_id($courseId);
72
        if (empty($courseInfo)) {
73
            return false;
74
        }
75
76
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
77
78
        // check if name already exists
79
        $sql = "SELECT count(*) AS nb FROM $table
80
                WHERE title = '".Database::escape_string($this->name)."' AND c_id = $courseId";
81
        $result = Database::query($sql);
82
        $row = Database::fetch_array($result);
83
        // lets add in BDD if not the same name
84
        if ($row['nb'] <= 0) {
85
            $params = [
86
                'c_id' => $courseId,
87
                'title' => $this->name,
88
                'description' => $this->description,
89
            ];
90
            $newId = Database::insert($table, $params);
91
92
            if ($newId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $newId of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

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

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

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

1198
            /** @scrutinizer ignore-call */ 
1199
            $rowname = self::protectJSDialogQuote($category['title']);
Loading history...
1199
            $nb_question_label = $nb_question == 1 ? $nb_question.' '.get_lang('Question') : $nb_question.' '.get_lang('Questions');
1200
            $content = "<span style='float:right'>".$nb_question_label."</span>";
1201
            $content .= '<div class="sectioncomment">';
1202
            $content .= $category['description'];
1203
            $content .= '</div>';
1204
            $links = '';
1205
1206
            if (!$sessionId) {
1207
                $links .= '<a href="'.api_get_self().'?action=editcategory&category_id='.$category['id'].'&'.api_get_cidreq().'">'.
1208
                    Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
1209
                $links .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=deletecategory&category_id='.$category['id'].'" ';
1210
                $links .= 'onclick="return confirmDelete(\''.self::protectJSDialogQuote(get_lang('DeleteCategoryAreYouSure').'['.$rowname).'] ?\', \'id_cat'.$category['id'].'\');">';
1211
                $links .= Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_SMALL).'</a>';
1212
            }
1213
1214
            $html .= Display::panel($content, $category['title'].$links);
1215
        }
1216
1217
        return $html;
1218
    }
1219
1220
    /**
1221
     * To allowed " in javascript dialog box without bad surprises
1222
     * replace " with two '.
1223
     *
1224
     * @param string $text
1225
     *
1226
     * @return mixed
1227
     */
1228
    public function protectJSDialogQuote($text)
1229
    {
1230
        $res = $text;
1231
        $res = str_replace("'", "\'", $res);
1232
        // super astuce pour afficher les " dans les boite de dialogue
1233
        $res = str_replace('"', "\'\'", $res);
1234
1235
        return $res;
1236
    }
1237
}
1238