Completed
Push — master ( 27e209...a08afa )
by Julito
186:04 queued 150:53
created

TestCategory::getNElementsFromArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use ChamiloSession as Session;
5
6
/**
7
 * Class TestCategory
8
 * @author hubert.borderiou
9
 * @author Julio Montoya - several fixes
10
 * @todo rename to ExerciseCategory
11
 */
12
class TestCategory
13
{
14
    public $id;
15
    public $name;
16
    public $description;
17
18
    /**
19
     * Constructor of the class Category
20
     */
21
    public function __construct()
22
    {
23
    }
24
25
    /**
26
     * return the TestCategory object with id=in_id
27
     * @param int $id
28
     * @param int $courseId
29
     * @return TestCategory
30
     */
31
    public function getCategory($id, $courseId = 0)
32
    {
33
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
34
        $id = intval($id);
35
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
36
        $sql = "SELECT * FROM $table
37
                WHERE id = $id AND c_id = ".$courseId;
38
        $res = Database::query($sql);
39
40
        if (Database::num_rows($res)) {
41
            $row = Database::fetch_array($res);
42
43
            $this->id = $row['id'];
44
            $this->name = $row['title'];
45
            $this->description = $row['description'];
46
47
            return $this;
48
        }
49
50
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type TestCategory.
Loading history...
51
    }
52
53
    /**
54
     * Save TestCategory in the database if name doesn't exists
55
     * @param int $courseId
56
     * @return bool
57
     */
58
    public function save($courseId = 0)
59
    {
60
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
61
        $courseInfo = api_get_course_info_by_id($courseId);
62
        if (empty($courseInfo)) {
63
            return false;
64
        }
65
66
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
67
68
        // check if name already exists
69
        $sql = "SELECT count(*) AS nb FROM $table
70
                WHERE title = '".$this->name."' AND c_id = $courseId";
71
        $result = Database::query($sql);
72
        $row = Database::fetch_array($result);
73
        // lets add in BDD if not the same name
74
        if ($row['nb'] <= 0) {
75
            $params = [
76
                'c_id' => $courseId,
77
                'title' => (string) $this->name,
78
                'description' => (string) $this->description
79
            ];
80
            $newId = Database::insert($table, $params);
81
82
            if ($newId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $newId of type integer|false 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...
83
                $sql = "UPDATE $table SET id = iid WHERE iid = $newId";
84
                Database::query($sql);
85
86
                api_item_property_update(
87
                    $courseInfo,
88
                    TOOL_TEST_CATEGORY,
89
                    $newId,
90
                    'TestCategoryAdded',
91
                    api_get_user_id()
92
                );
93
            }
94
95
            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...
96
        } else {
97
            return false;
98
        }
99
    }
100
101
    /**
102
     * Removes the category from the database
103
     * if there were question in this category, the link between question and category is removed
104
     * @param int $id
105
     * @return bool
106
     */
107
    public function removeCategory($id)
108
    {
109
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
110
        $tbl_question_rel_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
111
        $id = intval($id);
112
        $course_id = api_get_course_int_id();
113
        $category = $this->getCategory($id);
114
115
        if ($category) {
116
            $sql = "DELETE FROM $table
117
                    WHERE id= $id AND c_id=".$course_id;
118
            Database::query($sql);
119
120
            // remove link between question and category
121
            $sql = "DELETE FROM $tbl_question_rel_cat
122
                    WHERE category_id = $id AND c_id=".$course_id;
123
            Database::query($sql);
124
            // item_property update
125
            $course_info = api_get_course_info_by_id($course_id);
126
            api_item_property_update(
127
                $course_info,
128
                TOOL_TEST_CATEGORY,
129
                $this->id,
130
                'TestCategoryDeleted',
131
                api_get_user_id()
132
            );
133
134
            return true;
135
        }
136
137
        return false;
138
    }
139
140
    /**
141
     * Modify category name or description of category with id=in_id
142
     * @param int $courseId
143
     * @return bool
144
     */
145
    public function modifyCategory($courseId = 0)
146
    {
147
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
148
        $id = intval($this->id);
149
        $name = Database::escape_string($this->name);
150
        $description = Database::escape_string($this->description);
151
        $cat = $this->getCategory($id);
152
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
153
        $courseInfo = api_get_course_info_by_id($courseId);
154
        if (empty($courseInfo)) {
155
            return false;
156
        }
157
158
        if ($cat) {
159
            $sql = "UPDATE $table SET
160
                        title = '$name',
161
                        description = '$description'
162
                    WHERE id = $id AND c_id = ".$courseId;
163
            Database::query($sql);
164
165
            // item_property update
166
            api_item_property_update(
167
                $courseInfo,
168
                TOOL_TEST_CATEGORY,
169
                $this->id,
170
                'TestCategoryModified',
171
                api_get_user_id()
172
            );
173
            return true;
174
        }
175
176
        return false;
177
    }
178
179
    /**
180
     * Gets the number of question of category id=in_id
181
     */
182
    public function getCategoryQuestionsNumber()
183
    {
184
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
185
        $id = intval($this->id);
186
        $sql = "SELECT count(*) AS nb
187
                FROM $table
188
                WHERE category_id = $id AND c_id=".api_get_course_int_id();
189
        $res = Database::query($sql);
190
        $row = Database::fetch_array($res);
191
192
        return $row['nb'];
193
    }
194
195
    /**
196
     * Return an array of all Category objects in the database
197
     * If $field=="" Return an array of all category objects in the database
198
     * Otherwise, return an array of all in_field value
199
     * in the database (in_field = id or name or description)
200
     *
201
     * @param string $field
202
     * @param int $courseId
203
     * @return array
204
     */
205
    public static function getCategoryListInfo($field = '', $courseId = 0)
206
    {
207
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
208
209
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
210
        $field = Database::escape_string($field);
211
        $categories = [];
212
        if (empty($field)) {
213
            $sql = "SELECT id FROM $table
214
                    WHERE c_id = $courseId 
215
                    ORDER BY title ASC";
216
            $res = Database::query($sql);
217
            while ($row = Database::fetch_array($res)) {
218
                $category = new TestCategory();
219
                $categories[] = $category->getCategory($row['id']);
220
            }
221
        } else {
222
            $sql = "SELECT $field FROM $table
223
                    WHERE c_id = $courseId
224
                    ORDER BY $field ASC";
225
            $res = Database::query($sql);
226
            while ($row = Database::fetch_array($res)) {
227
                $categories[] = $row[$field];
228
            }
229
        }
230
        return $categories;
231
    }
232
233
    /**
234
     * Return the TestCategory id for question with question_id = $questionId
235
     * In this version, a question has only 1 TestCategory.
236
     * Return the TestCategory id, 0 if none
237
     * @param int $questionId
238
     * @param int $courseId
239
     *
240
     * @return int
241
     */
242
    public static function getCategoryForQuestion($questionId, $courseId = 0)
243
    {
244
        $courseId = (int) $courseId;
245
        $questionId = (int) $questionId;
246
247
        if (empty($courseId)) {
248
            $courseId = api_get_course_int_id();
249
        }
250
251
        if (empty($courseId) || empty($questionId)) {
252
            return 0;
253
        }
254
255
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
256
        $sql = "SELECT category_id
257
                FROM $table
258
                WHERE question_id = $questionId AND c_id = $courseId";
259
        $res = Database::query($sql);
260
        $result = 0;
261
        if (Database::num_rows($res) > 0) {
262
            $data = Database::fetch_array($res);
263
            $result = (int) $data['category_id'];
264
        }
265
266
        return $result;
267
    }
268
269
    /**
270
     * true if question id has a category
271
     *
272
     * @param int $questionId
273
     * @return bool
274
     */
275
    public static function isQuestionHasCategory($questionId)
276
    {
277
        if (self::getCategoryForQuestion($questionId) > 0) {
278
            return true;
279
        }
280
281
        return false;
282
    }
283
284
    /**
285
     * Return the category name for question with question_id = $questionId
286
     * In this version, a question has only 1 category.
287
     * @param $questionId
288
     * @param int $courseId
289
     * @return string
290
     */
291
    public static function getCategoryNameForQuestion(
292
        $questionId,
293
        $courseId = 0
294
    ) {
295
        if (empty($courseId)) {
296
            $courseId = api_get_course_int_id();
297
        }
298
        $courseId = (int) $courseId;
299
        $categoryId = self::getCategoryForQuestion($questionId, $courseId);
300
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
301
        $sql = "SELECT title 
302
                FROM $table
303
                WHERE id = $categoryId AND c_id = $courseId";
304
        $res = Database::query($sql);
305
        $data = Database::fetch_array($res);
306
        $result = '';
307
        if (Database::num_rows($res) > 0) {
308
            $result = $data['title'];
309
        }
310
311
        return $result;
312
    }
313
314
    /**
315
     * Return the list of differents categories ID for a test in the current course
316
     * input : test_id
317
     * return : array of category id (integer)
318
     * hubert.borderiou 07-04-2011
319
     * @param int $exerciseId
320
     * @param int $courseId
321
     *
322
     * @return array
323
     */
324
    public static function getListOfCategoriesIDForTest($exerciseId, $courseId = 0)
325
    {
326
        // parcourir les questions d'un test, recup les categories uniques dans un tableau
327
        $exercise = new Exercise($courseId);
328
        $exercise->read($exerciseId, false);
329
        $categoriesInExercise = $exercise->getQuestionWithCategories();
330
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
331
        $categories = [];
332
        if (!empty($categoriesInExercise)) {
333
            foreach ($categoriesInExercise as $category) {
334
                $categories[$category['id']] = $category;
335
            }
336
        }
337
338
        return $categories;
339
    }
340
341
    /**
342
     * @param Exercise $exercise
343
     * @return array
344
     */
345
    public static function getListOfCategoriesIDForTestObject(Exercise $exercise)
346
    {
347
        // parcourir les questions d'un test, recup les categories uniques dans un tableau
348
        $categories_in_exercise = [];
349
        $question_list = $exercise->getQuestionOrderedListByName();
350
351
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
352
        foreach ($question_list as $questionInfo) {
353
            $question_id = $questionInfo['question_id'];
354
            $category_list = self::getCategoryForQuestion($question_id);
355
            if (is_numeric($category_list)) {
356
                $category_list = [$category_list];
357
            }
358
359
            if (!empty($category_list)) {
360
                $categories_in_exercise = array_merge($categories_in_exercise, $category_list);
361
            }
362
        }
363
        if (!empty($categories_in_exercise)) {
364
            $categories_in_exercise = array_unique(array_filter($categories_in_exercise));
365
        }
366
367
        return $categories_in_exercise;
368
    }
369
370
    /**
371
     * Return the list of different categories NAME for a test
372
     * @param int $exerciseId
373
     * @param bool
374
     * @return array
375
     *
376
     * @author function rewrote by jmontoya
377
     */
378
    public static function getListOfCategoriesNameForTest($exerciseId, $grouped_by_category = true)
0 ignored issues
show
Unused Code introduced by
The parameter $grouped_by_category is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
379
    {
380
        $result = [];
381
        $categories = self::getListOfCategoriesIDForTest(
382
            $exerciseId
383
        );
384
385
        foreach ($categories as $catInfo) {
386
            $categoryId = $catInfo['id'];
387
            if (!empty($categoryId)) {
388
                $result[$categoryId] = [
389
                    'title' => $catInfo['title'],
390
                    //'parent_id' =>  $catInfo['parent_id'],
391
                    'parent_id' => '',
392
                    'c_id' => $catInfo['c_id']
393
                ];
394
            }
395
        }
396
397
        return $result;
398
    }
399
400
    /**
401
     * @param Exercise $exercise
402
     * @return array
403
     */
404
    public static function getListOfCategoriesForTest(Exercise $exercise)
405
    {
406
        $result = [];
407
        $categories = self::getListOfCategoriesIDForTestObject($exercise);
408
        foreach ($categories as $cat_id) {
409
            $cat = new TestCategory();
410
            $cat = (array) $cat->getCategory($cat_id);
411
            $cat['iid'] = $cat['id'];
412
            $cat['title'] = $cat['name'];
413
            $result[$cat['id']] = $cat;
414
        }
415
416
        return $result;
417
    }
418
419
    /**
420
     * return the number of differents categories for a test
421
     * input : test_id
422
     * return : integer
423
     * hubert.borderiou 07-04-2011
424
     */
425
    public static function getNumberOfCategoriesForTest($id)
426
    {
427
        return count(self::getListOfCategoriesIDForTest($id));
428
    }
429
430
    /**
431
     * return the number of question of a category id in a test
432
     * @param int $exerciseId
433
     * @param int $categoryId
434
     *
435
     * @return integer
436
     *
437
     * @author hubert.borderiou 07-04-2011
438
     */
439
    public static function getNumberOfQuestionsInCategoryForTest($exerciseId, $categoryId)
440
    {
441
        $nbCatResult = 0;
442
        $quiz = new Exercise();
443
        $quiz->read($exerciseId);
444
        $questionList = $quiz->selectQuestionList();
445
        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ? ? ?
446
        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...
447
            if (self::getCategoryForQuestion($questionList[$i]) == $categoryId) {
448
                $nbCatResult++;
449
            }
450
        }
451
452
        return $nbCatResult;
453
    }
454
455
    /**
456
     * return the number of question for a test using random by category
457
     * input  : test_id, number of random question (min 1)
458
     * @param int $exerciseId
459
     * @param int $random
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
     */
497
    public static function getCategoriesIdAndName($courseId = 0)
498
    {
499
        if (empty($courseId)) {
500
            $courseId = api_get_course_int_id();
501
        }
502
        $categories = self::getCategoryListInfo('', $courseId);
503
        $result = ['0' => get_lang('NoCategorySelected')];
504
        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...
505
            $result[$categories[$i]->id] = $categories[$i]->name;
506
        }
507
508
        return $result;
509
    }
510
511
    /**
512
     * Returns an array of question ids for each category
513
     * $categories[1][30] = 10, array with category id = 1 and question_id = 10
514
     * A question has "n" categories
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
    ) {
526
        $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
527
        $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
528
        $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
529
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
530
        $exerciseId = intval($exerciseId);
531
        $courseId = api_get_course_int_id();
532
533
        $sql = "SELECT DISTINCT qrc.question_id, qrc.category_id
534
                FROM $TBL_QUESTION_REL_CATEGORY qrc
535
                INNER JOIN $TBL_EXERCICE_QUESTION eq
536
                ON (eq.question_id = qrc.question_id AND qrc.c_id = eq.c_id)
537
                INNER JOIN $categoryTable c
538
                ON (c.id = qrc.category_id AND c.c_id = eq.c_id)
539
                INNER JOIN $tableQuestion q
540
                ON (q.id = qrc.question_id AND q.c_id = eq.c_id)
541
                WHERE
542
                    exercice_id = $exerciseId AND
543
                    qrc.c_id = $courseId
544
                ";
545
546
        $res = Database::query($sql);
547
        $categories = [];
548
        while ($data = Database::fetch_array($res)) {
549
            if (!empty($check_in_question_list)) {
550
                if (!in_array($data['question_id'], $check_in_question_list)) {
551
                    continue;
552
                }
553
            }
554
555
            if (!isset($categories[$data['category_id']]) ||
556
                !is_array($categories[$data['category_id']])
557
            ) {
558
                $categories[$data['category_id']] = [];
559
            }
560
561
            $categories[$data['category_id']][] = $data['question_id'];
562
        }
563
564
        if (!empty($categoriesAddedInExercise)) {
565
            $newCategoryList = [];
566
            foreach ($categoriesAddedInExercise as $category) {
567
                $categoryId = $category['category_id'];
568
                if (isset($categories[$categoryId])) {
569
                    $newCategoryList[$categoryId] = $categories[$categoryId];
570
                }
571
            }
572
573
            $checkQuestionsWithNoCategory = false;
574
            foreach ($categoriesAddedInExercise as $category) {
575
                if (empty($category['category_id'])) {
576
                    // Check
577
                    $checkQuestionsWithNoCategory = true;
578
                    break;
579
                }
580
            }
581
582
            // Select questions that don't have any category related
583
            if ($checkQuestionsWithNoCategory) {
584
                $originalQuestionList = $check_in_question_list;
585
                foreach ($originalQuestionList as $questionId) {
586
                    $categoriesFlatten = array_flatten($categories);
587
                    if (!in_array($questionId, $categoriesFlatten)) {
588
                        $newCategoryList[0][] = $questionId;
589
                    }
590
                }
591
            }
592
            $categories = $newCategoryList;
593
        }
594
595
        return $categories;
596
    }
597
598
    /**
599
     * Returns an array of $numberElements from $array
600
     * @param array
601
     * @param int
602
     *
603
     * @return array
604
     */
605
    public static function getNElementsFromArray($array, $numberElements)
606
    {
607
        $list = $array;
608
        shuffle($list);
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
     * @return null|string
629
     */
630
    public static function returnCategoryAndTitle($questionId, $in_display_category_name = 1)
631
    {
632
        $is_student = !(api_is_allowed_to_edit(null, true) || api_is_session_admin());
633
        $objExercise = Session::read('objExercise');
634
        if (!empty($objExercise)) {
635
            $in_display_category_name = $objExercise->display_category_name;
636
        }
637
        $content = null;
638
        if (self::getCategoryNameForQuestion($questionId) != '' &&
639
            ($in_display_category_name == 1 || !$is_student)
640
        ) {
641
            $content .= '<div class="page-header">';
642
            $content .= '<h4>'.get_lang('Category').": ".self::getCategoryNameForQuestion($questionId).'</h4>';
643
            $content .= "</div>";
644
        }
645
        return $content;
646
    }
647
648
    /**
649
     * Display signs [+] and/or (>0) after question title if question has options
650
     * scoreAlwaysPositive and/or uncheckedMayScore
651
     * @param $objQuestion
652
     */
653
    public function displayQuestionOption($objQuestion)
654
    {
655
        if ($objQuestion->type == MULTIPLE_ANSWER && $objQuestion->scoreAlwaysPositive) {
656
            echo "<span style='font-size:75%'> (>0)</span>";
657
        }
658
        if ($objQuestion->type == MULTIPLE_ANSWER && $objQuestion->uncheckedMayScore) {
659
            echo "<span style='font-size:75%'> [+]</span>";
660
        }
661
    }
662
663
    /**
664
     * sortTabByBracketLabel ($tabCategoryQuestions)
665
     * key of $tabCategoryQuestions are the category id (0 for not in a category)
666
     * value is the array of question id of this category
667
     * Sort question by Category
668
    */
669
    public static function sortTabByBracketLabel($in_tab)
670
    {
671
        $tabResult = [];
672
        $tabCatName = []; // tab of category name
673
        while (list($cat_id, $tabquestion) = each($in_tab)) {
0 ignored issues
show
Deprecated Code introduced by
The function each() has been deprecated: 7.2 ( Ignorable by Annotation )

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

673
        while (list($cat_id, $tabquestion) = /** @scrutinizer ignore-deprecated */ each($in_tab)) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
674
            $category = new TestCategory();
675
            $category = $category->getCategory($cat_id);
676
            $tabCatName[$cat_id] = $category->name;
677
        }
678
        reset($in_tab);
679
        // sort table by value, keeping keys as they are
680
        asort($tabCatName);
681
        // keys of $tabCatName are keys order for $in_tab
682
        while (list($key, $val) = each($tabCatName)) {
0 ignored issues
show
Deprecated Code introduced by
The function each() has been deprecated: 7.2 ( Ignorable by Annotation )

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

682
        while (list($key, $val) = /** @scrutinizer ignore-deprecated */ each($tabCatName)) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
683
            $tabResult[$key] = $in_tab[$key];
684
        }
685
686
        return $tabResult;
687
    }
688
689
    /**
690
     * return total score for test exe_id for all question in the category $in_cat_id for user
691
     * If no question for this category, return ""
692
     */
693
    public static function getCatScoreForExeidForUserid($in_cat_id, $in_exe_id, $in_user_id)
694
    {
695
        $tbl_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
696
        $tbl_question_rel_category = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
697
        $in_cat_id = intval($in_cat_id);
698
        $in_exe_id = intval($in_exe_id);
699
        $in_user_id = intval($in_user_id);
700
701
        $query = "SELECT DISTINCT
702
                        marks, 
703
                        exe_id, 
704
                        user_id, 
705
                        ta.question_id, 
706
                        category_id
707
                  FROM $tbl_track_attempt ta 
708
                  INNER JOIN $tbl_question_rel_category qrc
709
                  ON (ta.question_id=qrc.question_id)
710
                  WHERE
711
                    qrc.category_id = $in_cat_id AND
712
                    exe_id = $in_exe_id AND 
713
                    user_id = $in_user_id";
714
        $res = Database::query($query);
715
        $score = '';
716
        while ($data = Database::fetch_array($res)) {
717
            $score += $data['marks'];
718
        }
719
720
        return $score;
721
    }
722
723
    /**
724
     * Return the number max of question in a category
725
     * count the number of questions in all categories, and return the max
726
     * @param int $exerciseId
727
     * @author - hubert borderiou
728
     * @return int
729
    */
730
    public static function getNumberMaxQuestionByCat($exerciseId)
731
    {
732
        $res_num_max = 0;
733
        // foreach question
734
        $categories = self::getListOfCategoriesIDForTest($exerciseId);
735
        foreach ($categories as $category) {
736
            if (empty($category['id'])) {
737
                continue;
738
            }
739
740
            $nbQuestionInThisCat = self::getNumberOfQuestionsInCategoryForTest(
741
                $exerciseId,
742
                $category['id']
743
            );
744
745
            if ($nbQuestionInThisCat > $res_num_max) {
746
                $res_num_max = $nbQuestionInThisCat;
747
            }
748
        }
749
750
        return $res_num_max;
751
    }
752
753
    /**
754
     * Returns a category summary report
755
     * @param int $exerciseId
756
     * @param array $category_list
757
     * pre filled array with the category_id, score, and weight
758
     * example: array(1 => array('score' => '10', 'total' => 20));
759
     *
760
     * @return string
761
     */
762
    public static function get_stats_table_by_attempt(
763
        $exerciseId,
764
        $category_list = []
765
    ) {
766
        if (empty($category_list)) {
767
            return null;
768
        }
769
        $category_name_list = self::getListOfCategoriesNameForTest($exerciseId);
770
771
        $table = new HTML_Table(['class' => 'data_table']);
772
        $table->setHeaderContents(0, 0, get_lang('Categories'));
773
        $table->setHeaderContents(0, 1, get_lang('AbsoluteScore'));
774
        $table->setHeaderContents(0, 2, get_lang('RelativeScore'));
775
        $row = 1;
776
777
        $none_category = [];
778
        if (isset($category_list['none'])) {
779
            $none_category = $category_list['none'];
780
            unset($category_list['none']);
781
        }
782
783
        $total = [];
784
        if (isset($category_list['total'])) {
785
            $total = $category_list['total'];
786
            unset($category_list['total']);
787
        }
788
        if (count($category_list) > 1) {
789
            foreach ($category_list as $category_id => $category_item) {
790
                $table->setCellContents($row, 0, $category_name_list[$category_id]);
791
                $table->setCellContents(
792
                    $row,
793
                    1,
794
                    ExerciseLib::show_score(
795
                        $category_item['score'],
796
                        $category_item['total'],
797
                        false
798
                    )
799
                );
800
                $table->setCellContents(
801
                    $row,
802
                    2,
803
                    ExerciseLib::show_score(
804
                        $category_item['score'],
805
                        $category_item['total'],
806
                        true,
807
                        false,
808
                        true
809
                    )
810
                );
811
                $row++;
812
            }
813
814
            if (!empty($none_category)) {
815
                $table->setCellContents($row, 0, get_lang('None'));
816
                $table->setCellContents(
817
                    $row,
818
                    1,
819
                    ExerciseLib::show_score(
820
                        $none_category['score'],
821
                        $none_category['total'],
822
                        false
823
                    )
824
                );
825
                $table->setCellContents(
826
                    $row,
827
                    2,
828
                    ExerciseLib::show_score(
829
                        $none_category['score'],
830
                        $none_category['total'],
831
                        true,
832
                        false,
833
                        true
834
                    )
835
                );
836
                $row++;
837
            }
838
            if (!empty($total)) {
839
                $table->setCellContents($row, 0, get_lang('Total'));
840
                $table->setCellContents(
841
                    $row,
842
                    1,
843
                    ExerciseLib::show_score(
844
                        $total['score'],
845
                        $total['total'],
846
                        false
847
                    )
848
                );
849
                $table->setCellContents(
850
                    $row,
851
                    2,
852
                    ExerciseLib::show_score(
853
                        $total['score'],
854
                        $total['total'],
855
                        true,
856
                        false,
857
                        true
858
                    )
859
                );
860
            }
861
            return $table->toHtml();
862
        }
863
864
        return '';
865
    }
866
867
    /**
868
     * @return array
869
     */
870
    public static function get_all_categories()
871
    {
872
        $table = Database::get_course_table(TABLE_QUIZ_CATEGORY);
0 ignored issues
show
Bug introduced by
The constant TABLE_QUIZ_CATEGORY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
873
        $sql = "SELECT * FROM $table ORDER BY title ASC";
874
        $res = Database::query($sql);
875
        $array = [];
876
        while ($row = Database::fetch_array($res, 'ASSOC')) {
877
            $array[] = $row;
878
        }
879
880
        return $array;
881
    }
882
883
    /**
884
     * @param Exercise $exercise
885
     * @param int $courseId
886
     * @param string $order
887
     * @param bool $shuffle
888
     * @param bool $excludeCategoryWithNoQuestions
889
     * @return array
890
     */
891
    public function getCategoryExerciseTree(
892
        $exercise,
893
        $courseId,
894
        $order = null,
895
        $shuffle = false,
896
        $excludeCategoryWithNoQuestions = true
897
    ) {
898
        if (empty($exercise)) {
899
            return [];
900
        }
901
902
        $courseId = (int) $courseId;
903
        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
904
        $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
905
        $sql = "SELECT * FROM $table qc
906
                LEFT JOIN $categoryTable c
907
                ON (qc.c_id = c.c_id AND c.id = qc.category_id)
908
                WHERE qc.c_id = $courseId AND exercise_id = {$exercise->id} ";
909
910
        if (!empty($order)) {
911
            $sql .= "ORDER BY $order";
912
        }
913
914
        $categories = [];
915
        $result = Database::query($sql);
916
        if (Database::num_rows($result)) {
917
            while ($row = Database::fetch_array($result, 'ASSOC')) {
918
                if ($excludeCategoryWithNoQuestions) {
919
                    if ($row['count_questions'] == 0) {
920
                        continue;
921
                    }
922
                }
923
                if (empty($row['title']) && empty($row['category_id'])) {
924
                    $row['title'] = get_lang('NoCategory');
925
                }
926
                $categories[$row['category_id']] = $row;
927
            }
928
        }
929
930
        if ($shuffle) {
931
            shuffle_assoc($categories);
932
        }
933
934
        return $categories;
935
    }
936
937
    /**
938
     * @param FormValidator $form
939
     * @param string $action
940
     */
941
    public function getForm(& $form, $action = 'new')
942
    {
943
        switch ($action) {
944
            case 'new':
945
                $header = get_lang('AddACategory');
946
                $submit = get_lang('AddTestCategory');
947
                break;
948
            case 'edit':
949
                $header = get_lang('EditCategory');
950
                $submit = get_lang('ModifyCategory');
951
                break;
952
        }
953
954
        // Setting the form elements
955
        $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...
956
        $form->addElement('hidden', 'category_id');
957
        $form->addElement(
958
            'text',
959
            'category_name',
960
            get_lang('CategoryName'),
961
            ['class' => 'span6']
962
        );
963
        $form->add_html_editor(
0 ignored issues
show
Bug introduced by
The method add_html_editor() does not exist on FormValidator. ( Ignorable by Annotation )

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

963
        $form->/** @scrutinizer ignore-call */ 
964
               add_html_editor(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
964
            'category_description',
965
            get_lang('CategoryDescription'),
966
            false,
967
            false,
968
            [
969
                'ToolbarSet' => 'test_category',
970
                'Width' => '90%',
971
                'Height' => '200',
972
            ]
973
        );
974
        $category_parent_list = [];
975
976
        $options = [
977
                '1' => get_lang('Visible'),
978
                '0' => get_lang('Hidden')
979
        ];
980
        $form->addElement(
981
            'select',
982
            'visibility',
983
            get_lang('Visibility'),
984
            $options
985
        );
986
        $script = null;
987
        if (!empty($this->parent_id)) {
988
            $parent_cat = new TestCategory();
989
            $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...
990
            $category_parent_list = [$parent_cat->id => $parent_cat->name];
991
            $script .= '<script>$(function() { $("#parent_id").trigger("addItem",[{"title": "'.$parent_cat->name.'", "value": "'.$parent_cat->id.'"}]); });</script>';
992
        }
993
        $form->addElement('html', $script);
994
995
        $form->addElement('select', 'parent_id', get_lang('Parent'), $category_parent_list, ['id' => 'parent_id']);
996
        $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...
997
998
        // setting the defaults
999
        $defaults = [];
1000
        $defaults["category_id"] = $this->id;
1001
        $defaults["category_name"] = $this->name;
1002
        $defaults["category_description"] = $this->description;
1003
        $defaults["parent_id"] = $this->parent_id;
1004
        $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...
1005
        $form->setDefaults($defaults);
1006
1007
        // setting the rules
1008
        $form->addRule('category_name', get_lang('ThisFieldIsRequired'), 'required');
1009
    }
1010
1011
    /**
1012
     * Returns the category form.
1013
     * @param Exercise $exercise
1014
     * @return string
1015
     */
1016
    public function returnCategoryForm(Exercise $exercise)
1017
    {
1018
        $categories = $this->getListOfCategoriesForTest($exercise);
1019
        $saved_categories = $exercise->get_categories_in_exercise();
1020
        $return = null;
1021
1022
        if (!empty($categories)) {
1023
            $nbQuestionsTotal = $exercise->getNumberQuestionExerciseCategory();
1024
            $exercise->setCategoriesGrouping(true);
1025
            $real_question_count = count($exercise->getQuestionList());
1026
1027
            $warning = null;
1028
            if ($nbQuestionsTotal != $real_question_count) {
1029
                $warning = Display::return_message(
1030
                    get_lang('CheckThatYouHaveEnoughQuestionsInYourCategories'),
1031
                    'warning'
1032
                );
1033
            }
1034
1035
            $return .= $warning;
1036
            $return .= '<table class="data_table">';
1037
            $return .= '<tr>';
1038
            $return .= '<th height="24">'.get_lang('Categories').'</th>';
1039
            $return .= '<th width="70" height="24">'.get_lang('Number').'</th></tr>';
1040
1041
            $emptyCategory = [
1042
                'id' => '0',
1043
                'name' => get_lang('NoCategory'),
1044
                'description' => '',
1045
                'iid' => '0',
1046
                'title' => get_lang('NoCategory')
1047
            ];
1048
1049
            $categories[] = $emptyCategory;
1050
1051
            foreach ($categories as $category) {
1052
                $cat_id = $category['iid'];
1053
                $return .= '<tr>';
1054
                $return .= '<td>';
1055
                //$return .= Display::div(isset($category['parent_path']) ? $category['parent_path'] : '');
1056
                $return .= Display::div($category['name']);
1057
                $return .= '</td>';
1058
                $return .= '<td>';
1059
                $value = isset($saved_categories) && isset($saved_categories[$cat_id]) ? $saved_categories[$cat_id]['count_questions'] : -1;
1060
                $return .= '<input name="category['.$cat_id.']" value="'.$value.'" />';
1061
                $return .= '</td>';
1062
                $return .= '</tr>';
1063
            }
1064
1065
            $return .= '</table>';
1066
            $return .= get_lang('ZeroMeansNoQuestionWillBeSelectedMinusOneMeansThatAllQuestionsWillBeSelected');
1067
        }
1068
        return $return;
1069
    }
1070
1071
    /**
1072
     * Sorts an array
1073
     * @param array $array
1074
     * @return array
1075
     */
1076
    public function sort_tree_array($array)
1077
    {
1078
        foreach ($array as $key => $row) {
1079
            $parent[$key] = $row['parent_id'];
1080
        }
1081
        if (count($array) > 0) {
1082
            array_multisort($parent, SORT_ASC, $array);
1083
        }
1084
        return $array;
1085
    }
1086
1087
    /**
1088
     * Return true if a category already exists with the same name
1089
     * @param string $name
1090
     * @param int $courseId
1091
     *
1092
     * @return bool
1093
     */
1094
    public static function categoryTitleExists($name, $courseId = 0)
1095
    {
1096
        $categories = self::getCategoryListInfo('title', $courseId);
1097
        foreach ($categories as $title) {
1098
            if ($title == $name) {
1099
                return true;
1100
            }
1101
        }
1102
1103
        return false;
1104
    }
1105
1106
    /**
1107
     * Return the id of the test category with title = $in_title
1108
     * @param string $title
1109
     * @param int $courseId
1110
     *
1111
     * @return int is id of test category
1112
     */
1113
    public static function get_category_id_for_title($title, $courseId = 0)
1114
    {
1115
        $out_res = 0;
1116
        if (empty($courseId)) {
1117
            $courseId = api_get_course_int_id();
1118
        }
1119
        $courseId = intval($courseId);
1120
        $tbl_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
1121
        $sql = "SELECT id FROM $tbl_cat
1122
                WHERE c_id = $courseId AND title = '".Database::escape_string($title)."'";
1123
        $res = Database::query($sql);
1124
        if (Database::num_rows($res) > 0) {
1125
            $data = Database::fetch_array($res);
1126
            $out_res = $data['id'];
1127
        }
1128
        return $out_res;
1129
    }
1130
1131
    /**
1132
     * Add a relation between question and category in table c_quiz_question_rel_category
1133
     * @param int $categoryId
1134
     * @param int $questionId
1135
     * @param int $courseId
1136
     *
1137
     * @return string|false
1138
     */
1139
    public static function addCategoryToQuestion($categoryId, $questionId, $courseId)
1140
    {
1141
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
1142
        // if question doesn't have a category
1143
        // @todo change for 1.10 when a question can have several categories
1144
        if (self::getCategoryForQuestion($questionId, $courseId) == 0 &&
1145
            $questionId > 0 &&
1146
            $courseId > 0
1147
        ) {
1148
            $sql = "INSERT INTO $table (c_id, question_id, category_id)
1149
                    VALUES (".intval($courseId).", ".intval($questionId).", ".intval($categoryId).")";
1150
            Database::query($sql);
1151
            $id = Database::insert_id();
1152
1153
            return $id;
1154
        }
1155
1156
        return false;
1157
    }
1158
1159
    /**
1160
     * @param int $courseId
1161
     * @param int $sessionId
1162
     *
1163
     * @return array
1164
     */
1165
    public function getCategories($courseId, $sessionId = 0)
1166
    {
1167
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
1168
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
1169
        $sessionId = intval($sessionId);
1170
        $courseId = intval($courseId);
1171
1172
        if (empty($sessionId)) {
1173
            $sessionCondition = api_get_session_condition(
1174
                $sessionId,
1175
                true,
1176
                false,
1177
                'i.session_id'
1178
            );
1179
        } else {
1180
            $sessionCondition = api_get_session_condition(
1181
                $sessionId,
1182
                true,
1183
                true,
1184
                'i.session_id'
1185
            );
1186
        }
1187
1188
        if (empty($courseId)) {
1189
            return [];
1190
        }
1191
1192
        $sql = "SELECT c.* FROM $table c
1193
                INNER JOIN $itemProperty i
1194
                ON c.c_id = i.c_id AND i.ref = c.id
1195
                WHERE
1196
                    c.c_id = $courseId AND
1197
                    i.tool = '".TOOL_TEST_CATEGORY."'
1198
                    $sessionCondition
1199
                ORDER BY title";
1200
        $result = Database::query($sql);
1201
1202
        return Database::store_result($result, 'ASSOC');
1203
    }
1204
1205
    /**
1206
     * @param int $courseId
1207
     * @param int $sessionId
1208
     * @return string
1209
     */
1210
    public function displayCategories($courseId, $sessionId = 0)
1211
    {
1212
        $categories = $this->getCategories($courseId, $sessionId);
1213
        $html = '';
1214
        foreach ($categories as $category) {
1215
            $tmpobj = new TestCategory();
1216
            $tmpobj = $tmpobj->getCategory($category['id']);
1217
            $nb_question = $tmpobj->getCategoryQuestionsNumber();
1218
            $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

1218
            /** @scrutinizer ignore-call */ 
1219
            $rowname = self::protectJSDialogQuote($category['title']);
Loading history...
1219
            $nb_question_label = $nb_question == 1 ? $nb_question.' '.get_lang('Question') : $nb_question.' '.get_lang('Questions');
1220
            $content = "<span style='float:right'>".$nb_question_label."</span>";
1221
            $content .= '<div class="sectioncomment">';
1222
            $content .= $category['description'];
1223
            $content .= '</div>';
1224
            $links = '<a href="'.api_get_self().'?action=editcategory&category_id='.$category['id'].'&'.api_get_cidreq().'">'.
1225
                Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
1226
            $links .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=deletecategory&category_id='.$category['id'].'" ';
1227
            $links .= 'onclick="return confirmDelete(\''.self::protectJSDialogQuote(get_lang('DeleteCategoryAreYouSure').'['.$rowname).'] ?\', \'id_cat'.$category['id'].'\');">';
1228
            $links .= Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_SMALL).'</a>';
1229
            $html .= Display::panel($content, $category['title'].$links);
1230
        }
1231
1232
        return $html;
1233
    }
1234
1235
    // To allowed " in javascript dialog box without bad surprises
1236
    // replace " with two '
1237
    public function protectJSDialogQuote($in_txt)
1238
    {
1239
        $res = $in_txt;
1240
        $res = str_replace("'", "\'", $res);
1241
        $res = str_replace('"', "\'\'", $res); // super astuce pour afficher les " dans les boite de dialogue
1242
        return $res;
1243
    }
1244
}
1245