Passed
Push — master ( 2d9a22...0085e5 )
by Julito
10:53 queued 02:39
created

TestCategory::get_stats_table_by_attempt()   C

Complexity

Conditions 10
Paths 69

Size

Total Lines 114
Code Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 78
nc 69
nop 2
dl 0
loc 114
rs 6.6133
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

1209
            /** @scrutinizer ignore-call */ 
1210
            $rowname = self::protectJSDialogQuote($category->getTitle());
Loading history...
1210
            $nb_question_label = 1 == $nb_question ? $nb_question.' '.get_lang('Question') : $nb_question.' '.get_lang(
1211
                    'Questions'
1212
                );
1213
            $content = "<span style='float:right'>".$nb_question_label."</span>";
1214
            $content .= '<div class="sectioncomment">';
1215
            $content .= $category->getDescription();
1216
            $content .= '</div>';
1217
            $links = '';
1218
1219
            if (!$sessionId) {
1220
                $links .= '<a href="'.api_get_self(
1221
                    ).'?action=editcategory&category_id='.$id.'&'.api_get_cidreq().'">'.
1222
                    Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
1223
                $links .= ' <a href="'.api_get_self().'?'.api_get_cidreq(
1224
                    ).'&action=deletecategory&category_id='.$id.'" ';
1225
                $links .= 'onclick="return confirmDelete(\''.self::protectJSDialogQuote(
1226
                        get_lang('DeleteCategoryAreYouSure').'['.$rowname
1227
                    ).'] ?\', \'id_cat'.$id.'\');">';
1228
                $links .= Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_SMALL).'</a>';
1229
            }
1230
1231
            $html .= Display::panel($content, $category->getTitle().$links);
1232
        }
1233
1234
        return $html;
1235
    }
1236
1237
1238
    /**
1239
     * To allowed " in javascript dialog box without bad surprises
1240
     * replace " with two '.
1241
     *
1242
     * @param string $text
1243
     *
1244
     * @return mixed
1245
     */
1246
    public function protectJSDialogQuote($text)
1247
    {
1248
        $res = $text;
1249
        $res = str_replace("'", "\'", $res);
1250
        // 8. Set route and request
1251
        $res = str_replace('"', "\'\'", $res);
1252
1253
        return $res;
1254
    }
1255
}
1256