Completed
Push — master ( 76046e...74d281 )
by Julito
11:17
created

TestCategory::protectJSDialogQuote()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use APY\DataGridBundle\Grid\Action\MassAction;
5
use APY\DataGridBundle\Grid\Action\RowAction;
6
use APY\DataGridBundle\Grid\Source\Entity;
7
use Chamilo\CoreBundle\Entity\Resource\ResourceLink;
8
use Chamilo\CoreBundle\Framework\Container;
9
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
10
use Chamilo\CourseBundle\Entity\CQuizQuestionCategory;
11
use ChamiloSession as Session;
12
13
/**
14
 * Class TestCategory.
15
 * Manage question categories inside an exercise.
16
 *
17
 * @author hubert.borderiou
18
 * @author Julio Montoya - several fixes
19
 *
20
 * @todo rename to ExerciseCategory
21
 */
22
class TestCategory
23
{
24
    public $id;
25
    public $name;
26
    public $description;
27
28
    /**
29
     * Constructor of the class Category.
30
     */
31
    public function __construct()
32
    {
33
        $this->name = '';
34
        $this->description = '';
35
    }
36
37
    /**
38
     * return the TestCategory object with id=in_id.
39
     *
40
     * @param int $id
41
     * @param int $courseId
42
     *
43
     * @return TestCategory
44
     */
45
    public function getCategory($id, $courseId = 0)
46
    {
47
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
48
        $id = (int) $id;
49
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
50
        $sql = "SELECT * FROM $table
51
                WHERE id = $id AND c_id = ".$courseId;
52
        $res = Database::query($sql);
53
54
        if (Database::num_rows($res)) {
55
            $row = Database::fetch_array($res);
56
57
            $this->id = $row['id'];
58
            $this->name = $row['title'];
59
            $this->description = $row['description'];
60
61
            return $this;
62
        }
63
64
        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...
65
    }
66
67
    /**
68
     * Save TestCategory in the database if name doesn't exists.
69
     *
70
     * @param int $courseId
71
     *
72
     * @return bool
73
     */
74
    public function save($courseId = 0)
75
    {
76
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
77
        $courseInfo = api_get_course_info_by_id($courseId);
78
        if (empty($courseInfo)) {
79
            return false;
80
        }
81
82
        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
83
84
        // check if name already exists
85
        $sql = "SELECT count(*) AS nb FROM $table
86
                WHERE title = '".Database::escape_string($this->name)."' AND c_id = $courseId";
87
        $result = Database::query($sql);
88
        $row = Database::fetch_array($result);
89
        // lets add in BDD if not the same name
90
        if ($row['nb'] <= 0) {
91
            $repo = Container::getQuestionCategoryRepository();
92
            $category = new CQuizQuestionCategory();
93
            $category
94
                ->setTitle($this->name)
95
                ->setCourse($courseInfo['entity'])
96
                ->setDescription($this->description);
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
                $newId = $category->getIid();
103
                $sql = "UPDATE $table SET id = iid WHERE iid = $newId";
104
                Database::query($sql);
105
                $repo->addResourceToCourse(
106
                    $category,
107
                    ResourceLink::VISIBILITY_PUBLISHED,
108
                    api_get_user_entity(api_get_user_id()),
109
                    $courseInfo['entity'],
110
                    api_get_session_entity(),
111
                    api_get_group_entity()
112
                );
113
114
                return $newId;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $newId returns the type integer which is incompatible with the documented return type boolean.
Loading history...
115
            }
116
        }
117
118
        return false;
119
    }
120
121
    /**
122
     * Removes the category from the database
123
     * if there were question in this category, the link between question and category is removed.
124
     *
125
     * @param int $id
126
     *
127
     * @return bool
128
     */
129
    public function removeCategory($id)
130
    {
131
        $tbl_question_rel_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
132
        $id = (int) $id;
133
        $course_id = api_get_course_int_id();
134
        $category = $this->getCategory($id, $course_id);
135
136
        if ($category) {
0 ignored issues
show
introduced by
$category is of type TestCategory, thus it always evaluated to true.
Loading history...
137
            // remove link between question and category
138
            $sql = "DELETE FROM $tbl_question_rel_cat
139
                    WHERE category_id = $id AND c_id=".$course_id;
140
            Database::query($sql);
141
142
            $repo = Container::getQuestionCategoryRepository();
143
            $category = $repo->find($id);
144
            $repo->hardDelete($category);
0 ignored issues
show
Bug introduced by
It seems like $category can also be of type null; however, parameter $resource of Chamilo\CoreBundle\Repos...epository::hardDelete() does only seem to accept Chamilo\CoreBundle\Entit...source\AbstractResource, maybe add an additional type check? ( Ignorable by Annotation )

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

144
            $repo->hardDelete(/** @scrutinizer ignore-type */ $category);
Loading history...
145
146
            return true;
147
        }
148
149
        return false;
150
    }
151
152
    /**
153
     * @param                                                   $primaryKeys
154
     * @param                                                   $allPrimaryKeys
155
     * @param \Symfony\Component\HttpFoundation\Session\Session $session
156
     * @param                                                   $parameters
157
     */
158
    public function deleteResource(
159
        $primaryKeys,
160
        $allPrimaryKeys,
0 ignored issues
show
Unused Code introduced by
The parameter $allPrimaryKeys 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

160
        /** @scrutinizer ignore-unused */ $allPrimaryKeys,

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...
161
        Symfony\Component\HttpFoundation\Session\Session $session,
0 ignored issues
show
Unused Code introduced by
The parameter $session 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

161
        /** @scrutinizer ignore-unused */ Symfony\Component\HttpFoundation\Session\Session $session,

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...
162
        $parameters
0 ignored issues
show
Unused Code introduced by
The parameter $parameters 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

162
        /** @scrutinizer ignore-unused */ $parameters

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...
163
    ) {
164
        $repo = Container::getQuestionCategoryRepository();
165
        $translator = Container::getTranslator();
166
        foreach ($primaryKeys as $id) {
167
            $category = $repo->find($id);
168
            $repo->hardDelete($category);
0 ignored issues
show
Bug introduced by
It seems like $category can also be of type null; however, parameter $resource of Chamilo\CoreBundle\Repos...epository::hardDelete() does only seem to accept Chamilo\CoreBundle\Entit...source\AbstractResource, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

1094
    public function displayCategories(/** @scrutinizer ignore-unused */ $courseId, $sessionId = 0)

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...
Unused Code introduced by
The parameter $sessionId 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

1094
    public function displayCategories($courseId, /** @scrutinizer ignore-unused */ $sessionId = 0)

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...
1095
    {
1096
        // 1. Set entity
1097
        $source = new Entity('ChamiloCourseBundle:CQuizQuestionCategory');
1098
        // 2. Get query builder from repo.
1099
        $qb = Container::getQuestionCategoryRepository()->getResourcesByCourse(api_get_course_entity());
1100
1101
        // 3. Set QueryBuilder to the source.
1102
        $source-> initQueryBuilder($qb);
1103
1104
        // 4. Get the grid builder.
1105
        $builder = Container::$container->get('apy_grid.factory');
1106
1107
        // 5. Set parameters and properties.
1108
        $grid = $builder->createBuilder(
1109
            'grid',
1110
            $source,
1111
            [
1112
                'persistence' => false,
1113
                'route' => 'home',
1114
                'filterable' => true,
1115
                'sortable' => true,
1116
                'max_per_page' => 10,
1117
            ]
1118
        )->add(
1119
            'id',
1120
            'number',
1121
            [
1122
                'title' => '#',
1123
                'primary' => 'true',
1124
            ]
1125
        )->add(
1126
            'title',
1127
            'text',
1128
            [
1129
                'title' => 'title',
1130
            ]
1131
        )->add(
1132
            'description',
1133
            'text',
1134
            [
1135
                'title' => 'description',
1136
            ]
1137
        );
1138
1139
        $grid = $grid->getGrid();
1140
1141
        // 7. Add actions
1142
        if (Container::getAuthorizationChecker()->isGranted(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER)) {
1143
            // Add row actions
1144
            $myRowAction = new RowAction(
1145
                get_lang('Edit'),
1146
                'legacy_main',
1147
                false,
1148
                '_self',
1149
                ['class' => 'btn btn-secondary']
1150
            );
1151
            $myRowAction->setRouteParameters(
1152
                ['id', 'name' => 'exercise/tests_category.php', 'cidReq' => api_get_course_id(), 'action' => 'editcategory']
1153
            );
1154
            $grid->addRowAction($myRowAction);
1155
1156
            $myRowAction = new RowAction(
1157
                get_lang('Delete'),
1158
                'legacy_main',
1159
                true,
1160
                '_self',
1161
                ['class' => 'btn btn-danger', 'form_delete' => true]
1162
            );
1163
            $myRowAction->setRouteParameters(
1164
                ['id', 'name' => 'exercise/tests_category.php', 'cidReq' => api_get_course_id(), 'action' => 'deletecategory']
1165
            );
1166
            $grid->addRowAction($myRowAction);
1167
1168
            // Add mass actions
1169
            $deleteMassAction = new MassAction(
1170
                'Delete',
1171
                ['TestCategory', 'deleteResource'],
1172
                true,
1173
                []
1174
            );
1175
            $grid->addMassAction($deleteMassAction);
1176
        }
1177
1178
        // 8. Set route and request
1179
        $grid
1180
            ->setRouteUrl(api_get_self().'?'.api_get_cidreq())
1181
            ->handleRequest(Container::getRequest())
1182
        ;
1183
1184
        $html = Container::$container->get('twig')->render(
1185
            '@ChamiloTheme/Resource/grid.html.twig',
1186
            ['grid' => $grid]
1187
        );
1188
1189
        return $html;
1190
    }
1191
}
1192