Category   F
last analyzed

Complexity

Total Complexity 394

Size/Duplication

Total Lines 2870
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 394
eloc 1307
c 0
b 0
f 0
dl 0
loc 2870
rs 0.8

100 Methods

Rating   Name   Duplication   Size   Complexity  
A set_course_code() 0 3 1
A set_parent_id() 0 3 1
A set_description() 0 3 1
A getIsRequirement() 0 3 1
A set_user_id() 0 3 1
A set_id() 0 3 1
A set_certificate_min_score() 0 3 1
A set_name() 0 3 1
A is_visible() 0 3 1
A set_locked() 0 3 1
A set_grade_model_id() 0 3 1
A set_weight() 0 3 1
A set_session_id() 0 3 1
A set_visible() 0 3 1
A setIsRequirement() 0 3 1
B loadSessionCategories() 0 32 7
A get_type() 0 3 1
A setGenerateCertificates() 0 3 1
A setMinimumToValidate() 0 3 1
A getMinimumToValidate() 0 3 1
A get_skills() 0 11 2
A getCourseListDependency() 0 3 1
A getGenerateCertificates() 0 3 1
A get_grade_model_id() 0 7 2
A getSkillsForSelect() 0 11 3
C add() 0 71 12
F load() 0 97 18
A createCategoryObjectFromEntity() 0 19 1
B save() 0 70 10
A get_description() 0 3 1
A get_user_id() 0 3 1
A getCertificateMinScore() 0 7 2
A __construct() 0 18 1
A get_course_code() 0 3 1
A get_parent_id() 0 3 1
A get_id() 0 3 1
A get_weight() 0 3 1
A get_name() 0 3 1
A lock() 0 6 1
A find_category() 0 10 3
A setCourseListDependency() 0 10 3
A setDocumentId() 0 3 1
A create_root_category() 0 15 1
A updateAllowSkillBySubCategory() 0 6 1
A hasEvaluationsWithStudentResults() 0 25 5
A updateParentId() 0 6 1
A get_icon_name() 0 3 1
A getRemainingWeight() 0 12 2
B get_target_categories() 0 49 9
A setUrl() 0 11 3
B create_category_objects_from_sql_result() 0 37 7
C get_evaluations() 0 95 17
A updateChildrenWeight() 0 11 4
B exportAllCertificates() 0 54 10
B get_subcategories() 0 82 10
F calc_score() 0 286 67
A calculateCurrentScore() 0 27 5
A show_message_resource_delete() 0 15 2
F generateUserCertificate() 0 180 20
A get_item_type() 0 3 1
A get_date() 0 3 1
A addTargetSubcategories() 0 19 3
A get_tree() 0 43 5
A getAllowSkillBySubCategory() 0 13 2
A setGradeBooksToValidateInDependence() 0 5 1
A showAllCategoryInfo() 0 14 2
A get_all_courses() 0 18 3
A can_be_moved_to_cat() 0 3 1
A getStudentList() 0 3 1
A set_skills() 0 3 1
A applyCourseCodeToChildren() 0 27 4
A getCategories() 0 11 1
A getGradeBooksToValidateInDependence() 0 3 1
A getCurrentScore() 0 34 4
A delete() 0 5 1
A add_subtree() 0 21 3
A get_not_created_course_categories() 0 32 3
A is_certificate_available() 0 21 4
A setStudentList() 0 3 1
A is_movable() 0 3 3
A getUrl() 0 14 3
A deleteAllCertificates() 0 12 4
A getDocumentId() 0 3 1
B apply_visibility_to_children() 0 30 7
B get_links() 0 58 9
A is_course() 0 4 4
A move_to_cat() 0 8 2
B delete_all() 0 44 7
A getDownloadCertificateBlock() 0 25 5
B lockAllItems() 0 23 7
A registerCurrentScore() 0 9 1
A userFinishedCourse() 0 18 2
C get_root_categories_for_student() 0 82 12
A findByCertificate() 0 18 3
A deleteFromCourse() 0 25 4
A get_session_id() 0 3 1
A is_locked() 0 3 2
B getIndependentCategoriesWithStudentResult() 0 25 7
A generateCertificatesInUserList() 0 5 3
A get_root_categories_for_teacher() 0 46 5

How to fix   Complexity   

Complex Class

Complex classes like Category often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Category, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\GradebookCategory;
6
use ChamiloSession as Session;
7
8
/**
9
 * Class Category
10
 * Defines a gradebook Category object.
11
 */
12
class Category implements GradebookItem
13
{
14
    public $studentList;
15
    public $evaluations;
16
    public $links;
17
    public $subCategories;
18
    /** @var GradebookCategory */
19
    public $entity;
20
    private $id;
21
    private $name;
22
    private $description;
23
    private $user_id;
24
    private $course_code;
25
    private $parent;
26
    private $weight;
27
    private $visible;
28
    private $certificate_min_score;
29
    private $session_id;
30
    private $skills = [];
31
    private $grade_model_id;
32
    private $generateCertificates;
33
    private $isRequirement;
34
    private $courseDependency;
35
    private $minimumToValidate;
36
    private $documentId;
37
    /** @var int */
38
    private $gradeBooksToValidateInDependence;
39
40
    /**
41
     * Consctructor.
42
     */
43
    public function __construct()
44
    {
45
        $this->id = 0;
46
        $this->name = null;
47
        $this->description = null;
48
        $this->user_id = 0;
49
        $this->course_code = null;
50
        $this->parent = 0;
51
        $this->weight = 0;
52
        $this->visible = false;
53
        $this->certificate_min_score = 0;
54
        $this->session_id = 0;
55
        $this->grade_model_id = 0;
56
        $this->generateCertificates = false;
57
        $this->isRequirement = false;
58
        $this->courseDependency = [];
59
        $this->documentId = 0;
60
        $this->minimumToValidate = null;
61
    }
62
63
    /**
64
     * @return int
65
     */
66
    public function get_id()
67
    {
68
        return $this->id;
69
    }
70
71
    /**
72
     * @return string
73
     */
74
    public function get_name()
75
    {
76
        return $this->name;
77
    }
78
79
    /**
80
     * @return string
81
     */
82
    public function get_description()
83
    {
84
        return $this->description;
85
    }
86
87
    /**
88
     * @return int
89
     */
90
    public function get_user_id()
91
    {
92
        return $this->user_id;
93
    }
94
95
    /**
96
     * @return int|null
97
     */
98
    public function getCertificateMinScore()
99
    {
100
        if (!empty($this->certificate_min_score)) {
101
            return $this->certificate_min_score;
102
        }
103
104
        return null;
105
    }
106
107
    /**
108
     * @return string
109
     */
110
    public function get_course_code()
111
    {
112
        return $this->course_code;
113
    }
114
115
    /**
116
     * @return int
117
     */
118
    public function get_parent_id()
119
    {
120
        return $this->parent;
121
    }
122
123
    /**
124
     * @return int
125
     */
126
    public function get_weight()
127
    {
128
        return $this->weight;
129
    }
130
131
    public function is_locked(): bool
132
    {
133
        return isset($this->locked) && $this->locked == 1;
134
    }
135
136
    /**
137
     * @return bool
138
     */
139
    public function is_visible()
140
    {
141
        return $this->visible;
142
    }
143
144
    /**
145
     * Get $isRequirement.
146
     *
147
     * @return int
148
     */
149
    public function getIsRequirement()
150
    {
151
        return $this->isRequirement;
152
    }
153
154
    /**
155
     * @param int $id
156
     */
157
    public function set_id($id)
158
    {
159
        $this->id = $id;
160
    }
161
162
    /**
163
     * @param string $name
164
     */
165
    public function set_name($name)
166
    {
167
        $this->name = $name;
168
    }
169
170
    /**
171
     * @param string $description
172
     */
173
    public function set_description($description)
174
    {
175
        $this->description = $description;
176
    }
177
178
    /**
179
     * @param int $user_id
180
     */
181
    public function set_user_id($user_id)
182
    {
183
        $this->user_id = $user_id;
184
    }
185
186
    /**
187
     * @param string $course_code
188
     */
189
    public function set_course_code($course_code)
190
    {
191
        $this->course_code = $course_code;
192
    }
193
194
    /**
195
     * @param float $min_score
196
     */
197
    public function set_certificate_min_score($min_score = null)
198
    {
199
        $this->certificate_min_score = $min_score;
200
    }
201
202
    /**
203
     * @param int $parent
204
     */
205
    public function set_parent_id($parent)
206
    {
207
        $this->parent = (int) $parent;
208
    }
209
210
    /**
211
     * Filters to int and sets the session ID.
212
     *
213
     * @param   int     The session ID from the Dokeos course session
214
     */
215
    public function set_session_id($session_id = 0)
216
    {
217
        $this->session_id = (int) $session_id;
218
    }
219
220
    /**
221
     * @param $weight
222
     */
223
    public function set_weight($weight)
224
    {
225
        $this->weight = $weight;
226
    }
227
228
    /**
229
     * @param $visible
230
     */
231
    public function set_visible($visible)
232
    {
233
        $this->visible = $visible;
234
    }
235
236
    /**
237
     * @param int $id
238
     */
239
    public function set_grade_model_id($id)
240
    {
241
        $this->grade_model_id = $id;
242
    }
243
244
    /**
245
     * @param $locked
246
     */
247
    public function set_locked($locked)
248
    {
249
        $this->locked = $locked;
250
    }
251
252
    /**
253
     * Set $isRequirement.
254
     *
255
     * @param int $isRequirement
256
     */
257
    public function setIsRequirement($isRequirement)
258
    {
259
        $this->isRequirement = $isRequirement;
260
    }
261
262
    /**
263
     * @param $value
264
     */
265
    public function setCourseListDependency($value)
266
    {
267
        $this->courseDependency = [];
268
        $unserialized = false;
269
        if (!empty($value)) {
270
            $unserialized = UnserializeApi::unserialize('not_allowed_classes', $value, true);
271
        }
272
273
        if (false !== $unserialized) {
274
            $this->courseDependency = $unserialized;
275
        }
276
    }
277
278
    /**
279
     * Course id list.
280
     *
281
     * @return array
282
     */
283
    public function getCourseListDependency()
284
    {
285
        return $this->courseDependency;
286
    }
287
288
    /**
289
     * @param int $value
290
     */
291
    public function setMinimumToValidate($value)
292
    {
293
        $this->minimumToValidate = $value;
294
    }
295
296
    public function getMinimumToValidate()
297
    {
298
        return $this->minimumToValidate;
299
    }
300
301
    /**
302
     * @return int|null
303
     */
304
    public function get_grade_model_id()
305
    {
306
        if ($this->grade_model_id < 0) {
307
            return null;
308
        }
309
310
        return $this->grade_model_id;
311
    }
312
313
    /**
314
     * @return string
315
     */
316
    public function get_type()
317
    {
318
        return 'category';
319
    }
320
321
    /**
322
     * @param bool $from_db
323
     *
324
     * @return array|resource
325
     */
326
    public function get_skills($from_db = true)
327
    {
328
        if ($from_db) {
329
            $categoryId = $this->get_id();
330
            $gradebook = new Gradebook();
331
            $skills = $gradebook->getSkillsByGradebook($categoryId);
332
        } else {
333
            $skills = $this->skills;
334
        }
335
336
        return $skills;
337
    }
338
339
    /**
340
     * @return array
341
     */
342
    public function getSkillsForSelect()
343
    {
344
        $skills = $this->get_skills();
345
        $skill_select = [];
346
        if (!empty($skills)) {
347
            foreach ($skills as $skill) {
348
                $skill_select[$skill['id']] = $skill['name'];
349
            }
350
        }
351
352
        return $skill_select;
353
    }
354
355
    /**
356
     * Set the generate_certificates value.
357
     *
358
     * @param int $generateCertificates
359
     */
360
    public function setGenerateCertificates($generateCertificates)
361
    {
362
        $this->generateCertificates = $generateCertificates;
363
    }
364
365
    /**
366
     * Get the generate_certificates value.
367
     *
368
     * @return int
369
     */
370
    public function getGenerateCertificates()
371
    {
372
        return $this->generateCertificates;
373
    }
374
375
    /**
376
     * @param int $id
377
     * @param int $session_id
378
     *
379
     * @return array
380
     */
381
    public static function loadSessionCategories(
382
        $id = null,
383
        $session_id = null
384
    ) {
385
        if (isset($id) && (int) $id === 0) {
386
            $cats = [];
387
            $cats[] = self::create_root_category();
388
389
            return $cats;
390
        }
391
392
        $courseInfo = api_get_course_info_by_id(api_get_course_int_id());
393
        $courseCode = $courseInfo['code'];
394
        $session_id = (int) $session_id;
395
396
        if (!empty($session_id)) {
397
            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
398
            $sql = 'SELECT id, course_code
399
                    FROM '.$table.'
400
                    WHERE session_id = '.$session_id;
401
            $result_session = Database::query($sql);
402
            if (Database::num_rows($result_session) > 0) {
403
                $categoryList = [];
404
                while ($data_session = Database::fetch_array($result_session)) {
405
                    $parent_id = $data_session['id'];
406
                    if ($data_session['course_code'] == $courseCode) {
407
                        $categories = self::load($parent_id);
408
                        $categoryList = array_merge($categoryList, $categories);
409
                    }
410
                }
411
412
                return $categoryList;
413
            }
414
        }
415
    }
416
417
    /**
418
     * Retrieve categories and return them as an array of Category objects.
419
     *
420
     * @param int    $id          category id
421
     * @param int    $user_id     (category owner)
422
     * @param string $course_code
423
     * @param int    $parent_id   parent category
424
     * @param bool   $visible
425
     * @param int    $session_id  (in case we are in a session)
426
     * @param bool   $order_by    Whether to show all "session"
427
     *                            categories (true) or hide them (false) in case there is no session id
428
     *
429
     * @return array<int, Category>
430
     */
431
    public static function load(
432
        $id = null,
433
        $user_id = null,
434
        $course_code = null,
435
        $parent_id = null,
436
        $visible = null,
437
        $session_id = null,
438
        $order_by = null
439
    ): array {
440
        //if the category given is explicitly 0 (not null), then create
441
        // a root category object (in memory)
442
        if (isset($id) && (int) $id === 0) {
443
            $cats = [];
444
            $cats[] = self::create_root_category();
445
446
            return $cats;
447
        }
448
449
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
450
        $sql = 'SELECT * FROM '.$table;
451
        $paramcount = 0;
452
        if (isset($id)) {
453
            $sql .= ' WHERE id = '.intval($id);
454
            $paramcount++;
455
        }
456
457
        if (isset($user_id)) {
458
            $user_id = intval($user_id);
459
            if ($paramcount != 0) {
460
                $sql .= ' AND';
461
            } else {
462
                $sql .= ' WHERE';
463
            }
464
            $sql .= ' user_id = '.intval($user_id);
465
            $paramcount++;
466
        }
467
468
        if (isset($course_code)) {
469
            if ($paramcount != 0) {
470
                $sql .= ' AND';
471
            } else {
472
                $sql .= ' WHERE';
473
            }
474
475
            if ($course_code == '0') {
476
                $sql .= ' course_code is null ';
477
            } else {
478
                $sql .= " course_code = '".Database::escape_string($course_code)."'";
479
            }
480
481
            /*if ($show_session_categories !== true) {
482
                // a query on the course should show all
483
                // the categories inside sessions for this course
484
                // otherwise a special parameter is given to ask explicitely
485
                $sql .= " AND (session_id IS NULL OR session_id = 0) ";
486
            } else {*/
487
            if (empty($session_id)) {
488
                $sql .= ' AND (session_id IS NULL OR session_id = 0) ';
489
            } else {
490
                $sql .= ' AND session_id = '.(int) $session_id.' ';
491
            }
492
            //}
493
            $paramcount++;
494
        }
495
496
        if (isset($parent_id)) {
497
            if (0 != $paramcount) {
498
                $sql .= ' AND ';
499
            } else {
500
                $sql .= ' WHERE ';
501
            }
502
            $sql .= ' parent_id = '.intval($parent_id);
503
            $paramcount++;
504
        }
505
506
        if (isset($visible)) {
507
            if (0 != $paramcount) {
508
                $sql .= ' AND';
509
            } else {
510
                $sql .= ' WHERE';
511
            }
512
            $sql .= ' visible = '.intval($visible);
513
        }
514
515
        if (!empty($order_by)) {
516
            if (!empty($order_by) && $order_by != '') {
517
                $sql .= ' '.$order_by;
518
            }
519
        }
520
521
        $result = Database::query($sql);
522
        $categories = [];
523
        if (Database::num_rows($result) > 0) {
524
            $categories = self::create_category_objects_from_sql_result($result);
525
        }
526
527
        return $categories;
528
    }
529
530
    /**
531
     * Create a category object from a GradebookCategory entity.
532
     *
533
     * @param GradebookCategory $gradebookCategory The entity
534
     *
535
     * @return \Category
536
     */
537
    public static function createCategoryObjectFromEntity(GradebookCategory $gradebookCategory)
538
    {
539
        $category = new Category();
540
        $category->set_id($gradebookCategory->getId());
541
        $category->set_name($gradebookCategory->getName());
542
        $category->set_description($gradebookCategory->getDescription());
543
        $category->set_user_id($gradebookCategory->getUserId());
544
        $category->set_course_code($gradebookCategory->getCourseCode());
545
        $category->set_parent_id($gradebookCategory->getParentId());
546
        $category->set_weight($gradebookCategory->getWeight());
547
        $category->set_visible($gradebookCategory->getVisible());
548
        $category->set_session_id($gradebookCategory->getSessionId());
549
        $category->set_certificate_min_score($gradebookCategory->getCertifMinScore());
550
        $category->set_grade_model_id($gradebookCategory->getGradeModelId());
551
        $category->set_locked($gradebookCategory->getLocked());
552
        $category->setGenerateCertificates($gradebookCategory->getGenerateCertificates());
553
        $category->setIsRequirement($gradebookCategory->getIsRequirement());
554
555
        return $category;
556
    }
557
558
    /**
559
     * Insert this category into the database.
560
     */
561
    public function add()
562
    {
563
        if (isset($this->name) && '-1' == $this->name) {
564
            return false;
565
        }
566
567
        if (isset($this->name) && isset($this->user_id)) {
568
            $em = Database::getManager();
569
570
            $category = new GradebookCategory();
571
            $category->setName($this->name);
572
            $category->setDescription($this->description);
573
            $category->setUserId($this->user_id);
574
            $category->setCourseCode($this->course_code);
575
            $category->setParentId($this->parent);
576
            $category->setWeight($this->weight);
577
            $category->setVisible($this->visible);
578
            $category->setCertifMinScore($this->certificate_min_score);
579
            $category->setSessionId($this->session_id);
580
            $category->setGenerateCertificates($this->generateCertificates);
581
            $category->setGradeModelId($this->grade_model_id);
582
            $category->setIsRequirement($this->isRequirement);
583
            $category->setLocked(false);
584
585
            $em->persist($category);
586
            $em->flush();
587
588
            $id = $category->getId();
589
            $this->set_id($id);
590
591
            if (!empty($id)) {
592
                $parent_id = $this->get_parent_id();
593
                $grade_model_id = $this->get_grade_model_id();
594
                if ($parent_id == 0) {
595
                    //do something
596
                    if (isset($grade_model_id) &&
597
                        !empty($grade_model_id) &&
598
                        $grade_model_id != '-1'
599
                    ) {
600
                        $obj = new GradeModel();
601
                        $components = $obj->get_components($grade_model_id);
602
                        $default_weight_setting = api_get_setting('gradebook_default_weight');
603
                        $default_weight = 100;
604
                        if (isset($default_weight_setting)) {
605
                            $default_weight = $default_weight_setting;
606
                        }
607
                        foreach ($components as $component) {
608
                            $gradebook = new Gradebook();
609
                            $params = [];
610
611
                            $params['name'] = $component['acronym'];
612
                            $params['description'] = $component['title'];
613
                            $params['user_id'] = api_get_user_id();
614
                            $params['parent_id'] = $id;
615
                            $params['weight'] = $component['percentage'] / 100 * $default_weight;
616
                            $params['session_id'] = api_get_session_id();
617
                            $params['course_code'] = $this->get_course_code();
618
619
                            $gradebook->save($params);
620
                        }
621
                    }
622
                }
623
            }
624
625
            $gradebook = new Gradebook();
626
            $gradebook->updateSkillsToGradeBook(
627
                $this->id,
628
                $this->get_skills(false)
629
            );
630
631
            return $id;
632
        }
633
    }
634
635
    /**
636
     * Update the properties of this category in the database.
637
     *
638
     * @todo fix me
639
     */
640
    public function save()
641
    {
642
        $em = Database::getManager();
643
644
        /** @var GradebookCategory $gradebookCategory */
645
        $gradebookCategory = $em
646
            ->getRepository('ChamiloCoreBundle:GradebookCategory')
647
            ->find($this->id);
648
649
        if (empty($gradebookCategory)) {
650
            return false;
651
        }
652
653
        $gradebookCategory->setName($this->name);
654
        $gradebookCategory->setDescription($this->description);
655
        $gradebookCategory->setUserId($this->user_id);
656
        $gradebookCategory->setCourseCode($this->course_code);
657
        $gradebookCategory->setParentId($this->parent);
658
        $gradebookCategory->setWeight($this->weight);
659
        $gradebookCategory->setVisible($this->visible);
660
        $gradebookCategory->setCertifMinScore($this->certificate_min_score);
661
        $gradebookCategory->setGenerateCertificates(
662
            $this->generateCertificates
663
        );
664
        $gradebookCategory->setGradeModelId($this->grade_model_id);
665
        $gradebookCategory->setIsRequirement($this->isRequirement);
666
667
        $em->merge($gradebookCategory);
668
        $em->flush();
669
670
        if (!empty($this->id)) {
671
            $parent_id = $this->get_parent_id();
672
            $grade_model_id = $this->get_grade_model_id();
673
            if ($parent_id == 0) {
674
                if (isset($grade_model_id) &&
675
                    !empty($grade_model_id) &&
676
                    $grade_model_id != '-1'
677
                ) {
678
                    $obj = new GradeModel();
679
                    $components = $obj->get_components($grade_model_id);
680
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
681
                    $default_weight = 100;
682
                    if (isset($default_weight_setting)) {
683
                        $default_weight = $default_weight_setting;
684
                    }
685
                    $final_weight = $this->get_weight();
686
                    if (!empty($final_weight)) {
687
                        $default_weight = $this->get_weight();
688
                    }
689
                    foreach ($components as $component) {
690
                        $gradebook = new Gradebook();
691
                        $params = [];
692
                        $params['name'] = $component['acronym'];
693
                        $params['description'] = $component['title'];
694
                        $params['user_id'] = api_get_user_id();
695
                        $params['parent_id'] = $this->id;
696
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
697
                        $params['session_id'] = api_get_session_id();
698
                        $params['course_code'] = $this->get_course_code();
699
                        $gradebook->save($params);
700
                    }
701
                }
702
            }
703
        }
704
705
        $gradebook = new Gradebook();
706
        $gradebook->updateSkillsToGradeBook(
707
            $this->id,
708
            $this->get_skills(false),
709
            true
710
        );
711
    }
712
713
    /**
714
     * Update value to allow user skills by subcategory passed.
715
     *
716
     * @param $value
717
     */
718
    public function updateAllowSkillBySubCategory($value)
719
    {
720
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
721
        $value = (int) $value;
722
        $upd = 'UPDATE '.$table.' SET allow_skills_by_subcategory = '.$value.' WHERE id = '.intval($this->id);
723
        Database::query($upd);
724
    }
725
726
    /**
727
     * Update the current parent id.
728
     *
729
     * @param $parentId
730
     * @param $catId
731
     *
732
     * @throws Exception
733
     */
734
    public function updateParentId($parentId, $catId)
735
    {
736
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
737
        $parentId = (int) $parentId;
738
        $upd = 'UPDATE '.$table.' SET parent_id = '.$parentId.' WHERE id = '.$catId;
739
        Database::query($upd);
740
    }
741
742
    /**
743
     * Get the value to Allow skill by subcategory.
744
     *
745
     * @return bool
746
     */
747
    public function getAllowSkillBySubCategory($parentId = null)
748
    {
749
        $id = (int) $this->id;
750
        if (isset($parentId)) {
751
            $id = (int) $parentId;
752
        }
753
754
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
755
        $sql = 'SELECT allow_skills_by_subcategory FROM '.$table.' WHERE id = '.$id;
756
        $rs = Database::query($sql);
757
        $value = (bool) Database::result($rs, 0, 0);
758
759
        return $value;
760
    }
761
762
    /**
763
     * Update link weights see #5168.
764
     *
765
     * @param type $new_weight
766
     */
767
    public function updateChildrenWeight($new_weight)
768
    {
769
        $links = $this->get_links();
770
        $old_weight = $this->get_weight();
771
772
        if (!empty($links)) {
773
            foreach ($links as $link_item) {
774
                if (isset($link_item)) {
775
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
776
                    $link_item->set_weight($new_item_weight);
777
                    $link_item->save();
778
                }
779
            }
780
        }
781
    }
782
783
    /**
784
     * Delete this evaluation from the database.
785
     */
786
    public function delete()
787
    {
788
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
789
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
790
        Database::query($sql);
791
    }
792
793
    /**
794
     * Delete the gradebook categories from a course, including course sessions.
795
     *
796
     * @param string $courseCode
797
     */
798
    public static function deleteFromCourse($courseCode)
799
    {
800
        $em = Database::getManager();
801
        $categories = $em
802
            ->createQuery(
803
                'SELECT DISTINCT gc.sessionId
804
                FROM ChamiloCoreBundle:GradebookCategory gc WHERE gc.courseCode = :code'
805
            )
806
            ->setParameter('code', $courseCode)
807
            ->getResult();
808
809
        foreach ($categories as $category) {
810
            $cats = self::load(
811
                null,
812
                null,
813
                $courseCode,
814
                null,
815
                null,
816
                (int) $category['sessionId']
817
            );
818
819
            if (!empty($cats)) {
820
                /** @var self $cat */
821
                foreach ($cats as $cat) {
822
                    $cat->delete_all();
823
                }
824
            }
825
        }
826
    }
827
828
    /**
829
     * Show message about the resource having been deleted.
830
     *
831
     * @return string|bool
832
     */
833
    public function show_message_resource_delete(string $courseCode)
834
    {
835
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
836
        $sql = 'SELECT count(*) AS num
837
                FROM '.$table.'
838
                WHERE
839
                    course_code = "'.Database::escape_string($courseCode).'" AND
840
                    visible = 3';
841
        $res = Database::query($sql);
842
        $option = Database::fetch_array($res, 'ASSOC');
843
        if ($option['num'] >= 1) {
844
            return '&nbsp;&nbsp;<span class="resource-deleted">(&nbsp;'.get_lang('ResourceDeleted').'&nbsp;)</span>';
845
        }
846
847
        return false;
848
    }
849
850
    /**
851
     * Shows all information of an category.
852
     *
853
     * @param int $categoryId
854
     *
855
     * @return array
856
     */
857
    public function showAllCategoryInfo($categoryId)
858
    {
859
        $categoryId = (int) $categoryId;
860
        if (empty($categoryId)) {
861
            return [];
862
        }
863
864
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
865
        $sql = 'SELECT * FROM '.$table.'
866
                WHERE id = '.$categoryId;
867
        $result = Database::query($sql);
868
        $row = Database::fetch_array($result, 'ASSOC');
869
870
        return $row;
871
    }
872
873
    /**
874
     * Checks if the certificate is available for the given user in this category.
875
     *
876
     * @param int $user_id User ID
877
     *
878
     * @return bool True if conditions match, false if fails
879
     */
880
    public function is_certificate_available($user_id)
881
    {
882
        $score = $this->calc_score(
883
            $user_id,
884
            null,
885
            $this->course_code,
886
            $this->session_id,
887
            null
888
        );
889
890
        if (isset($score) && isset($score[0])) {
891
            // Get a percentage score to compare to minimum certificate score
892
            // $certification_score = $score[0] / $score[1] * 100;
893
            // Get real score not a percentage.
894
            $certification_score = $score[0];
895
            if ($certification_score >= $this->certificate_min_score) {
896
                return true;
897
            }
898
        }
899
900
        return false;
901
    }
902
903
    /**
904
     * Is this category a course ?
905
     * A category is a course if it has a course code and no parent category.
906
     */
907
    public function is_course()
908
    {
909
        return isset($this->course_code) && !empty($this->course_code)
910
            && (!isset($this->parent) || $this->parent == 0);
911
    }
912
913
    /**
914
     * Calculate the score of this category.
915
     *
916
     * @param int    $stud_id     student id (default: all students - then the average is returned)
917
     * @param        $type
918
     * @param string $course_code
919
     * @param int    $session_id
920
     *
921
     * @return array (score sum, weight sum)
922
     *               or null if no scores available
923
     */
924
    public function calc_score(
925
        $stud_id = null,
926
        $type = null,
927
        $course_code = '',
928
        $session_id = null,
929
        $forCertificate = 1
930
    ) {
931
        $key = 'category:'.$this->id.'student:'.(int) $stud_id.'type:'.$type.'course:'.$course_code.'session:'.(int) $session_id;
932
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
933
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
934
935
        if ($cacheAvailable) {
936
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
937
            if ($cacheDriver->contains($key)) {
938
                return $cacheDriver->fetch($key);
939
            }
940
        }
941
        // Classic
942
        if (!empty($stud_id) && '' == $type) {
943
            if (!empty($course_code)) {
944
                $cats = $this->get_subcategories(
945
                    $stud_id,
946
                    $course_code,
947
                    $session_id,
948
                    null,
949
                    $forCertificate
950
                );
951
                $evals = $this->get_evaluations($stud_id, false, $course_code, $session_id, $forCertificate);
952
                $links = $this->get_links($stud_id, false, $course_code, $session_id, $forCertificate);
953
            } else {
954
                $cats = $this->get_subcategories($stud_id, '', $session_id, null, $forCertificate);
955
                $evals = $this->get_evaluations($stud_id, false, '', $session_id, $forCertificate);
956
                $links = $this->get_links($stud_id, false, '', $session_id, $forCertificate);
957
            }
958
959
            // Calculate score
960
            $count = 0;
961
            $ressum = 0;
962
            $weightsum = 0;
963
            if (!empty($cats)) {
964
                /** @var Category $cat */
965
                foreach ($cats as $cat) {
966
                    $cat->set_session_id($session_id);
967
                    $cat->set_course_code($course_code);
968
                    $cat->setStudentList($this->getStudentList());
969
                    $score = $cat->calc_score(
970
                        $stud_id,
971
                        null,
972
                        $course_code,
973
                        $session_id
974
                    );
975
976
                    $catweight = 0;
977
                    if (0 != $cat->get_weight()) {
978
                        $catweight = $cat->get_weight();
979
                        $weightsum += $catweight;
980
                    }
981
982
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
983
                        $ressum += $score[0] / $score[1] * $catweight;
984
                    }
985
                }
986
            }
987
988
            if (!empty($evals)) {
989
                /** @var Evaluation $eval */
990
                foreach ($evals as $eval) {
991
                    $eval->setStudentList($this->getStudentList());
992
                    $evalres = $eval->calc_score($stud_id);
993
                    if (isset($evalres) && 0 != $eval->get_weight()) {
994
                        $evalweight = $eval->get_weight();
995
                        $weightsum += $evalweight;
996
                        if (!empty($evalres[1])) {
997
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
998
                        }
999
                    } else {
1000
                        if (0 != $eval->get_weight()) {
1001
                            $evalweight = $eval->get_weight();
1002
                            $weightsum += $evalweight;
1003
                        }
1004
                    }
1005
                }
1006
            }
1007
1008
            if (!empty($links)) {
1009
                /** @var EvalLink|ExerciseLink $link */
1010
                foreach ($links as $link) {
1011
                    $link->setStudentList($this->getStudentList());
1012
1013
                    if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null 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...
1014
                        $link->set_session_id($session_id);
1015
                    }
1016
1017
                    $linkres = $link->calc_score($stud_id, null);
1018
                    if (!empty($linkres) && 0 != $link->get_weight()) {
1019
                        $linkweight = $link->get_weight();
1020
                        $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1021
                        $weightsum += $linkweight;
1022
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1023
                    } else {
1024
                        // Adding if result does not exists
1025
                        if (0 != $link->get_weight()) {
1026
                            $linkweight = $link->get_weight();
1027
                            $weightsum += $linkweight;
1028
                        }
1029
                    }
1030
                }
1031
            }
1032
        } else {
1033
            if (!empty($course_code)) {
1034
                $cats = $this->get_subcategories(
1035
                    null,
1036
                    $course_code,
1037
                    $session_id
1038
                );
1039
                $evals = $this->get_evaluations(null, false, $course_code);
1040
                $links = $this->get_links(null, false, $course_code);
1041
            } else {
1042
                $cats = $this->get_subcategories(null);
1043
                $evals = $this->get_evaluations(null);
1044
                $links = $this->get_links(null);
1045
            }
1046
1047
            // Calculate score
1048
            $ressum = 0;
1049
            $weightsum = 0;
1050
            $bestResult = 0;
1051
            $totalScorePerStudent = [];
1052
1053
            if (!empty($cats)) {
1054
                /** @var Category $cat */
1055
                foreach ($cats as $cat) {
1056
                    $cat->setStudentList($this->getStudentList());
1057
                    $score = $cat->calc_score(
1058
                        null,
1059
                        $type,
1060
                        $course_code,
1061
                        $session_id
1062
                    );
1063
1064
                    $catweight = 0;
1065
                    if (0 != $cat->get_weight()) {
1066
                        $catweight = $cat->get_weight();
1067
                        $weightsum += $catweight;
1068
                    }
1069
1070
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
1071
                        $ressum += $score[0] / $score[1] * $catweight;
1072
1073
                        if ($ressum > $bestResult) {
1074
                            $bestResult = $ressum;
1075
                        }
1076
                    }
1077
                }
1078
            }
1079
1080
            if (!empty($evals)) {
1081
                if ('best' === $type) {
1082
                    $studentList = $this->getStudentList();
1083
                    foreach ($studentList as $student) {
1084
                        $studentId = $student['user_id'];
1085
                        foreach ($evals as $eval) {
1086
                            $linkres = $eval->calc_score($studentId, null);
1087
                            $linkweight = $eval->get_weight();
1088
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1089
                            $ressum = $linkres[0] / $link_res_denom * $linkweight;
1090
1091
                            if (!isset($totalScorePerStudent[$studentId])) {
1092
                                $totalScorePerStudent[$studentId] = 0;
1093
                            }
1094
                            $totalScorePerStudent[$studentId] += $ressum;
1095
                        }
1096
                    }
1097
                } else {
1098
                    /** @var Evaluation $eval */
1099
                    foreach ($evals as $eval) {
1100
                        $evalres = $eval->calc_score(null, $type);
1101
                        $eval->setStudentList($this->getStudentList());
1102
1103
                        if (isset($evalres) && 0 != $eval->get_weight()) {
1104
                            $evalweight = $eval->get_weight();
1105
                            $weightsum += $evalweight;
1106
                            if (!empty($evalres[1])) {
1107
                                $ressum += $evalres[0] / $evalres[1] * $evalweight;
1108
                            }
1109
1110
                            if ($ressum > $bestResult) {
1111
                                $bestResult = $ressum;
1112
                            }
1113
                        } else {
1114
                            if (0 != $eval->get_weight()) {
1115
                                $evalweight = $eval->get_weight();
1116
                                $weightsum += $evalweight;
1117
                            }
1118
                        }
1119
                    }
1120
                }
1121
            }
1122
1123
            if (!empty($links)) {
1124
                $studentList = $this->getStudentList();
1125
                if ('best' === $type) {
1126
                    foreach ($studentList as $student) {
1127
                        $studentId = $student['user_id'];
1128
                        foreach ($links as $link) {
1129
                            $linkres = $link->calc_score($studentId, null);
1130
                            $linkweight = $link->get_weight();
1131
                            if ($linkres) {
1132
                                $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1133
                                $ressum = $linkres[0] / $link_res_denom * $linkweight;
1134
                            }
1135
1136
                            if (!isset($totalScorePerStudent[$studentId])) {
1137
                                $totalScorePerStudent[$studentId] = 0;
1138
                            }
1139
                            $totalScorePerStudent[$studentId] += $ressum;
1140
                        }
1141
                    }
1142
                } else {
1143
                    /** @var EvalLink|ExerciseLink $link */
1144
                    foreach ($links as $link) {
1145
                        $link->setStudentList($this->getStudentList());
1146
1147
                        if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null 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...
1148
                            $link->set_session_id($session_id);
1149
                        }
1150
1151
                        $linkres = $link->calc_score($stud_id, $type);
1152
1153
                        if (!empty($linkres) && 0 != $link->get_weight()) {
1154
                            $linkweight = $link->get_weight();
1155
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1156
1157
                            $weightsum += $linkweight;
1158
                            $ressum += $linkres[0] / $link_res_denom * $linkweight;
1159
                            if ($ressum > $bestResult) {
1160
                                $bestResult = $ressum;
1161
                            }
1162
                        } else {
1163
                            // Adding if result does not exists
1164
                            if (0 != $link->get_weight()) {
1165
                                $linkweight = $link->get_weight();
1166
                                $weightsum += $linkweight;
1167
                            }
1168
                        }
1169
                    }
1170
                }
1171
            }
1172
        }
1173
1174
        switch ($type) {
1175
            case 'best':
1176
                arsort($totalScorePerStudent);
1177
                $maxScore = current($totalScorePerStudent);
1178
1179
                return [$maxScore, $this->get_weight()];
1180
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1181
            case 'average':
1182
                if (empty($ressum)) {
1183
                    if ($cacheAvailable) {
1184
                        $cacheDriver->save($key, null);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cacheDriver does not seem to be defined for all execution paths leading up to this point.
Loading history...
1185
                    }
1186
1187
                    return null;
1188
                }
1189
1190
                if ($cacheAvailable) {
1191
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1192
                }
1193
1194
                return [$ressum, $weightsum];
1195
                break;
1196
            case 'ranking':
1197
                // category ranking is calculated in gradebook_data_generator.class.php
1198
                // function get_data
1199
                return null;
1200
1201
                return AbstractLink::getCurrentUserRanking($stud_id, []);
0 ignored issues
show
Unused Code introduced by
return AbstractLink::get...king($stud_id, array()) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1202
                break;
1203
            default:
1204
                if ($cacheAvailable) {
1205
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1206
                }
1207
1208
                return [$ressum, $weightsum];
1209
                break;
1210
        }
1211
    }
1212
1213
    /**
1214
     * Delete this category and every subcategory, evaluation and result inside.
1215
     */
1216
    public function delete_all()
1217
    {
1218
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1219
        $evals = Evaluation::load(
1220
            null,
1221
            null,
1222
            $this->course_code,
1223
            $this->id,
1224
            null
1225
        );
1226
1227
        $links = LinkFactory::load(
1228
            null,
1229
            null,
1230
            null,
1231
            null,
1232
            $this->course_code,
1233
            $this->id,
1234
            null
1235
        );
1236
1237
        if (!empty($cats)) {
1238
            /** @var Category $cat */
1239
            foreach ($cats as $cat) {
1240
                $cat->delete_all();
1241
                $cat->delete();
1242
            }
1243
        }
1244
1245
        if (!empty($evals)) {
1246
            /** @var Evaluation $eval */
1247
            foreach ($evals as $eval) {
1248
                $eval->delete_with_results();
1249
            }
1250
        }
1251
1252
        if (!empty($links)) {
1253
            /** @var AbstractLink $link */
1254
            foreach ($links as $link) {
1255
                $link->delete();
1256
            }
1257
        }
1258
1259
        $this->delete();
1260
    }
1261
1262
    /**
1263
     * Return array of Category objects where a student is subscribed to.
1264
     *
1265
     * @param int    $stud_id
1266
     * @param string $course_code
1267
     * @param int    $session_id
1268
     *
1269
     * @return array
1270
     */
1271
    public function get_root_categories_for_student(
1272
        $stud_id,
1273
        $course_code = null,
1274
        $session_id = null
1275
    ) {
1276
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1277
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1278
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1279
1280
        $course_code = Database::escape_string($course_code);
1281
        $session_id = (int) $session_id;
1282
1283
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1284
1285
        if (!api_is_allowed_to_edit()) {
1286
            $sql .= ' AND visible = 1';
1287
            //proceed with checks on optional parameters course & session
1288
            if (!empty($course_code)) {
1289
                // TODO: considering it highly improbable that a user would get here
1290
                // if he doesn't have the rights to view this course and this
1291
                // session, we don't check his registration to these, but this
1292
                // could be an improvement
1293
                if (!empty($session_id)) {
1294
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1295
                } else {
1296
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1297
                }
1298
            } else {
1299
                //no optional parameter, proceed as usual
1300
                $sql .= ' AND course_code in
1301
                     (
1302
                        SELECT c.code
1303
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1304
                        ON (cu.c_id = c.id)
1305
                        WHERE cu.user_id = '.intval($stud_id).'
1306
                        AND cu.status = '.STUDENT.'
1307
                    )';
1308
            }
1309
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1310
            //proceed with checks on optional parameters course & session
1311
            if (!empty($course_code)) {
1312
                // TODO: considering it highly improbable that a user would get here
1313
                // if he doesn't have the rights to view this course and this
1314
                // session, we don't check his registration to these, but this
1315
                // could be an improvement
1316
                $sql .= " AND course_code  = '".$course_code."'";
1317
                if (!empty($session_id)) {
1318
                    $sql .= " AND session_id = ".$session_id;
1319
                } else {
1320
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1321
                }
1322
            } else {
1323
                $sql .= ' AND course_code IN
1324
                     (
1325
                        SELECT c.code
1326
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1327
                        ON (cu.c_id = c.id)
1328
                        WHERE
1329
                            cu.user_id = '.api_get_user_id().' AND
1330
                            cu.status = '.COURSEMANAGER.'
1331
                    )';
1332
            }
1333
        } elseif (api_is_platform_admin()) {
1334
            if (isset($session_id) && 0 != $session_id) {
1335
                $sql .= ' AND session_id='.$session_id;
1336
            } else {
1337
                $sql .= ' AND coalesce(session_id,0)=0';
1338
            }
1339
        }
1340
        $result = Database::query($sql);
1341
        $cats = self::create_category_objects_from_sql_result($result);
1342
1343
        // course independent categories
1344
        if (empty($course_code)) {
1345
            $cats = $this->getIndependentCategoriesWithStudentResult(
1346
                0,
1347
                $stud_id,
1348
                $cats
1349
            );
1350
        }
1351
1352
        return $cats;
1353
    }
1354
1355
    /**
1356
     * Return array of Category objects where a teacher is admin for.
1357
     *
1358
     * @param int    $user_id     (to return everything, use 'null' here)
1359
     * @param string $course_code (optional)
1360
     * @param int    $session_id  (optional)
1361
     *
1362
     * @return array
1363
     */
1364
    public function get_root_categories_for_teacher(
1365
        $user_id,
1366
        $course_code = null,
1367
        $session_id = null
1368
    ) {
1369
        if (null == $user_id) {
1370
            return self::load(null, null, $course_code, 0, null, $session_id);
1371
        }
1372
1373
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1374
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1375
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1376
1377
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1378
                WHERE parent_id = 0 ';
1379
        if (!empty($course_code)) {
1380
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1381
            if (!empty($session_id)) {
1382
                $sql .= " AND session_id = ".(int) $session_id;
1383
            }
1384
        } else {
1385
            $sql .= ' AND course_code in
1386
                 (
1387
                    SELECT c.code
1388
                    FROM '.$main_course_user_table.' cu
1389
                    INNER JOIN '.$courseTable.' c
1390
                    ON (cu.c_id = c.id)
1391
                    WHERE user_id = '.intval($user_id).'
1392
                )';
1393
        }
1394
        $result = Database::query($sql);
1395
        $cats = self::create_category_objects_from_sql_result($result);
1396
        // course independent categories
1397
        if (isset($course_code)) {
1398
            $indcats = self::load(
1399
                null,
1400
                $user_id,
1401
                $course_code,
1402
                0,
1403
                null,
1404
                $session_id
1405
            );
1406
            $cats = array_merge($cats, $indcats);
1407
        }
1408
1409
        return $cats;
1410
    }
1411
1412
    /**
1413
     * Can this category be moved to somewhere else ?
1414
     * The root and courses cannot be moved.
1415
     *
1416
     * @return bool
1417
     */
1418
    public function is_movable()
1419
    {
1420
        return !(!isset($this->id) || 0 == $this->id || $this->is_course());
1421
    }
1422
1423
    /**
1424
     * Generate an array of possible categories where this category can be moved to.
1425
     * Notice: its own parent will be included in the list: it's up to the frontend
1426
     * to disable this element.
1427
     *
1428
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1429
     */
1430
    public function get_target_categories()
1431
    {
1432
        // the root or a course -> not movable
1433
        if (!$this->is_movable()) {
1434
            return null;
1435
        } else {
1436
            // otherwise:
1437
            // - course independent category
1438
            //   -> movable to root or other independent categories
1439
            // - category inside a course
1440
            //   -> movable to root, independent categories or categories inside the course
1441
            $user = api_is_platform_admin() ? null : api_get_user_id();
1442
            $targets = [];
1443
            $level = 0;
1444
1445
            $root = [0, get_lang('RootCat'), $level];
1446
            $targets[] = $root;
1447
1448
            if (isset($this->course_code) && !empty($this->course_code)) {
1449
                $crscats = self::load(null, null, $this->course_code, 0);
1450
                foreach ($crscats as $cat) {
1451
                    if ($this->can_be_moved_to_cat($cat)) {
1452
                        $targets[] = [
1453
                            $cat->get_id(),
1454
                            $cat->get_name(),
1455
                            $level + 1,
1456
                        ];
1457
                        $targets = $this->addTargetSubcategories(
1458
                            $targets,
1459
                            $level + 1,
1460
                            $cat->get_id()
1461
                        );
1462
                    }
1463
                }
1464
            }
1465
1466
            $indcats = self::load(null, $user, 0, 0);
1467
            foreach ($indcats as $cat) {
1468
                if ($this->can_be_moved_to_cat($cat)) {
1469
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1470
                    $targets = $this->addTargetSubcategories(
1471
                        $targets,
1472
                        $level + 1,
1473
                        $cat->get_id()
1474
                    );
1475
                }
1476
            }
1477
1478
            return $targets;
1479
        }
1480
    }
1481
1482
    /**
1483
     * Move this category to the given category.
1484
     * If this category moves from inside a course to outside,
1485
     * its course code must be changed, as well as the course code
1486
     * of all underlying categories and evaluations. All links will
1487
     * be deleted as well !
1488
     */
1489
    public function move_to_cat($cat)
1490
    {
1491
        $this->set_parent_id($cat->get_id());
1492
        if ($this->get_course_code() != $cat->get_course_code()) {
1493
            $this->set_course_code($cat->get_course_code());
1494
            $this->applyCourseCodeToChildren();
1495
        }
1496
        $this->save();
1497
    }
1498
1499
    /**
1500
     * Generate an array of all categories the user can navigate to.
1501
     */
1502
    public function get_tree()
1503
    {
1504
        $targets = [];
1505
        $level = 0;
1506
        $root = [0, get_lang('RootCat'), $level];
1507
        $targets[] = $root;
1508
1509
        // course or platform admin
1510
        if (api_is_allowed_to_edit()) {
1511
            $user = api_is_platform_admin() ? null : api_get_user_id();
1512
            $cats = self::get_root_categories_for_teacher($user);
0 ignored issues
show
Bug Best Practice introduced by
The method Category::get_root_categories_for_teacher() 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

1512
            /** @scrutinizer ignore-call */ 
1513
            $cats = self::get_root_categories_for_teacher($user);
Loading history...
1513
            foreach ($cats as $cat) {
1514
                $targets[] = [
1515
                    $cat->get_id(),
1516
                    $cat->get_name(),
1517
                    $level + 1,
1518
                ];
1519
                $targets = $this->add_subtree(
1520
                    $targets,
1521
                    $level + 1,
1522
                    $cat->get_id(),
1523
                    null
1524
                );
1525
            }
1526
        } else {
1527
            // student
1528
            $cats = $this->get_root_categories_for_student(api_get_user_id());
1529
            foreach ($cats as $cat) {
1530
                $targets[] = [
1531
                    $cat->get_id(),
1532
                    $cat->get_name(),
1533
                    $level + 1,
1534
                ];
1535
                $targets = $this->add_subtree(
1536
                    $targets,
1537
                    $level + 1,
1538
                    $cat->get_id(),
1539
                    1
1540
                );
1541
            }
1542
        }
1543
1544
        return $targets;
1545
    }
1546
1547
    /**
1548
     * Generate an array of courses that a teacher hasn't created a category for.
1549
     *
1550
     * @param int $user_id
1551
     *
1552
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1553
     */
1554
    public function get_not_created_course_categories($user_id)
1555
    {
1556
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1557
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1558
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1559
1560
        $user_id = (int) $user_id;
1561
1562
        $sql = 'SELECT DISTINCT(code), title
1563
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1564
                WHERE
1565
                    cc.id = cu.c_id AND
1566
                    cu.status = '.COURSEMANAGER;
1567
1568
        if (!api_is_platform_admin()) {
1569
            $sql .= ' AND cu.user_id = '.$user_id;
1570
        }
1571
        $sql .= ' AND cc.code NOT IN
1572
             (
1573
                SELECT course_code FROM '.$tbl_grade_categories.'
1574
                WHERE
1575
                    parent_id = 0 AND
1576
                    course_code IS NOT NULL
1577
                )';
1578
        $result = Database::query($sql);
1579
1580
        $cats = [];
1581
        while ($data = Database::fetch_array($result)) {
1582
            $cats[] = [$data['code'], $data['title']];
1583
        }
1584
1585
        return $cats;
1586
    }
1587
1588
    /**
1589
     * Generate an array of all courses that a teacher is admin of.
1590
     *
1591
     * @param int $user_id
1592
     *
1593
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1594
     */
1595
    public function get_all_courses($user_id)
1596
    {
1597
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1598
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1599
        $sql = 'SELECT DISTINCT(code), title
1600
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1601
                WHERE cc.id = cu.c_id AND cu.status = '.COURSEMANAGER;
1602
        if (!api_is_platform_admin()) {
1603
            $sql .= ' AND cu.user_id = '.intval($user_id);
1604
        }
1605
1606
        $result = Database::query($sql);
1607
        $cats = [];
1608
        while ($data = Database::fetch_array($result)) {
1609
            $cats[] = [$data['code'], $data['title']];
1610
        }
1611
1612
        return $cats;
1613
    }
1614
1615
    /**
1616
     * Apply the same visibility to every subcategory, evaluation and link.
1617
     */
1618
    public function apply_visibility_to_children()
1619
    {
1620
        $cats = self::load(null, null, null, $this->id, null);
1621
        $evals = Evaluation::load(null, null, null, $this->id, null);
1622
        $links = LinkFactory::load(
1623
            null,
1624
            null,
1625
            null,
1626
            null,
1627
            null,
1628
            $this->id,
1629
            null
1630
        );
1631
        if (!empty($cats)) {
1632
            foreach ($cats as $cat) {
1633
                $cat->set_visible($this->is_visible());
1634
                $cat->save();
1635
                $cat->apply_visibility_to_children();
1636
            }
1637
        }
1638
        if (!empty($evals)) {
1639
            foreach ($evals as $eval) {
1640
                $eval->set_visible($this->is_visible());
1641
                $eval->save();
1642
            }
1643
        }
1644
        if (!empty($links)) {
1645
            foreach ($links as $link) {
1646
                $link->set_visible($this->is_visible());
1647
                $link->save();
1648
            }
1649
        }
1650
    }
1651
1652
    /**
1653
     * Check if a category contains evaluations with a result for a given student.
1654
     *
1655
     * @param int $studentId
1656
     *
1657
     * @return bool
1658
     */
1659
    public function hasEvaluationsWithStudentResults($studentId)
1660
    {
1661
        $evals = Evaluation::get_evaluations_with_result_for_student(
1662
            $this->id,
1663
            $studentId
1664
        );
1665
        if (0 != count($evals)) {
1666
            return true;
1667
        } else {
1668
            $cats = self::load(
1669
                null,
1670
                null,
1671
                null,
1672
                $this->id,
1673
                api_is_allowed_to_edit() ? null : 1
1674
            );
1675
1676
            /** @var Category $cat */
1677
            foreach ($cats as $cat) {
1678
                if ($cat->hasEvaluationsWithStudentResults($studentId)) {
1679
                    return true;
1680
                }
1681
            }
1682
1683
            return false;
1684
        }
1685
    }
1686
1687
    /**
1688
     * Retrieve all categories inside a course independent category
1689
     * that should be visible to a student.
1690
     *
1691
     * @param int   $categoryId parent category
1692
     * @param int   $studentId
1693
     * @param array $cats       optional: if defined, the categories will be added to this array
1694
     *
1695
     * @return array
1696
     */
1697
    public function getIndependentCategoriesWithStudentResult(
1698
        $categoryId,
1699
        $studentId,
1700
        $cats = []
1701
    ) {
1702
        $creator = api_is_allowed_to_edit() && !api_is_platform_admin() ? api_get_user_id() : null;
1703
1704
        $categories = self::load(
1705
            null,
1706
            $creator,
1707
            '0',
1708
            $categoryId,
1709
            api_is_allowed_to_edit() ? null : 1
1710
        );
1711
1712
        if (!empty($categories)) {
1713
            /** @var Category $category */
1714
            foreach ($categories as $category) {
1715
                if ($category->hasEvaluationsWithStudentResults($studentId)) {
1716
                    $cats[] = $category;
1717
                }
1718
            }
1719
        }
1720
1721
        return $cats;
1722
    }
1723
1724
    /**
1725
     * Return the session id (in any case, even if it's null or 0).
1726
     *
1727
     * @return int Session id (can be null)
1728
     */
1729
    public function get_session_id()
1730
    {
1731
        return $this->session_id;
1732
    }
1733
1734
    /**
1735
     * Get appropriate subcategories visible for the user (and optionally the course and session).
1736
     *
1737
     * @param int    $studentId   student id (default: all students)
1738
     * @param string $course_code Course code (optional)
1739
     * @param int    $session_id  Session ID (optional)
1740
     * @param bool   $order
1741
     *
1742
     * @return array Array of subcategories
1743
     */
1744
    public function get_subcategories(
1745
        $studentId = null,
1746
        $course_code = null,
1747
        $session_id = null,
1748
        $order = null,
1749
        $forCertificate = 1
1750
    ) {
1751
        // 1 student
1752
        if (isset($studentId)) {
1753
            // Special case: this is the root
1754
            if (0 == $this->id) {
1755
                return $this->get_root_categories_for_student($studentId, $course_code, $session_id);
1756
            } else {
1757
                return self::load(
1758
                    null,
1759
                    null,
1760
                    $course_code,
1761
                    $this->id,
1762
                    api_is_allowed_to_edit() ? null : $forCertificate,
1763
                    $session_id,
1764
                    $order
1765
                );
1766
            }
1767
        } else {
1768
            // All students
1769
            // Course admin
1770
            if (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1771
                // root
1772
                if (0 == $this->id) {
1773
                    // inside a course
1774
                    return $this->get_root_categories_for_teacher(
1775
                        api_get_user_id(),
1776
                        $course_code,
1777
                        $session_id,
1778
                        false
1779
                    );
1780
                } elseif (!empty($this->course_code)) {
1781
                    return self::load(
1782
                        null,
1783
                        null,
1784
                        $this->course_code,
1785
                        $this->id,
1786
                        null,
1787
                        $session_id,
1788
                        $order
1789
                    );
1790
                } elseif (!empty($course_code)) {
1791
                    // course independent
1792
                    return self::load(
1793
                        null,
1794
                        null,
1795
                        $course_code,
1796
                        $this->id,
1797
                        null,
1798
                        $session_id,
1799
                        $order
1800
                    );
1801
                } else {
1802
                    return self::load(
1803
                        null,
1804
                        api_get_user_id(),
1805
                        0,
1806
                        $this->id,
1807
                        null
1808
                    );
1809
                }
1810
            } elseif (api_is_platform_admin()) {
1811
                // platform admin
1812
                // we explicitly avoid listing subcats from another session
1813
                return self::load(
1814
                    null,
1815
                    null,
1816
                    $course_code,
1817
                    $this->id,
1818
                    null,
1819
                    $session_id,
1820
                    $order
1821
                );
1822
            }
1823
        }
1824
1825
        return [];
1826
    }
1827
1828
    /**
1829
     * Get appropriate evaluations visible for the user.
1830
     *
1831
     * @param int    $studentId   student id (default: all students)
1832
     * @param bool   $recursive   process subcategories (default: no recursion)
1833
     * @param string $course_code
1834
     * @param int    $sessionId
1835
     *
1836
     * @return array
1837
     */
1838
    public function get_evaluations(
1839
        $studentId = null,
1840
        $recursive = false,
1841
        $course_code = '',
1842
        $sessionId = 0,
1843
        $forCertificate = 1
1844
    ) {
1845
        $evals = [];
1846
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1847
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1848
1849
        // 1 student
1850
        if (isset($studentId) && !empty($studentId)) {
1851
            // Special case: this is the root
1852
            if (0 == $this->id) {
1853
                $evals = Evaluation::get_evaluations_with_result_for_student(
1854
                    0,
1855
                    $studentId
1856
                );
1857
            } else {
1858
                $evals = Evaluation::load(
1859
                    null,
1860
                    null,
1861
                    $course_code,
1862
                    $this->id,
1863
                    api_is_allowed_to_edit() ? null : $forCertificate
1864
                );
1865
            }
1866
        } else {
1867
            // All students
1868
            // course admin
1869
            if ((api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) &&
1870
                !api_is_platform_admin()
1871
            ) {
1872
                // root
1873
                if (0 == $this->id) {
1874
                    $evals = Evaluation::load(
1875
                        null,
1876
                        api_get_user_id(),
1877
                        null,
1878
                        $this->id,
1879
                        null
1880
                    );
1881
                } elseif (isset($this->course_code) &&
1882
                    !empty($this->course_code)
1883
                ) {
1884
                    // inside a course
1885
                    $evals = Evaluation::load(
1886
                        null,
1887
                        null,
1888
                        $course_code,
1889
                        $this->id,
1890
                        null
1891
                    );
1892
                } else {
1893
                    // course independent
1894
                    $evals = Evaluation::load(
1895
                        null,
1896
                        api_get_user_id(),
1897
                        null,
1898
                        $this->id,
1899
                        null
1900
                    );
1901
                }
1902
            } else {
1903
                $evals = Evaluation::load(
1904
                    null,
1905
                    null,
1906
                    $course_code,
1907
                    $this->id,
1908
                    null
1909
                );
1910
            }
1911
        }
1912
1913
        if ($recursive) {
1914
            $subcats = $this->get_subcategories(
1915
                $studentId,
1916
                $course_code,
1917
                $sessionId
1918
            );
1919
1920
            if (!empty($subcats)) {
1921
                foreach ($subcats as $subcat) {
1922
                    $subevals = $subcat->get_evaluations(
1923
                        $studentId,
1924
                        true,
1925
                        $course_code
1926
                    );
1927
                    $evals = array_merge($evals, $subevals);
1928
                }
1929
            }
1930
        }
1931
1932
        return $evals;
1933
    }
1934
1935
    /**
1936
     * Get appropriate links visible for the user.
1937
     *
1938
     * @param int    $studentId   student id (default: all students)
1939
     * @param bool   $recursive   process subcategories (default: no recursion)
1940
     * @param string $course_code
1941
     * @param int    $sessionId
1942
     *
1943
     * @return array
1944
     */
1945
    public function get_links(
1946
        $studentId = null,
1947
        $recursive = false,
1948
        $course_code = '',
1949
        $sessionId = 0,
1950
        $forCertificate = 1
1951
    ) {
1952
        $links = [];
1953
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1954
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1955
1956
        // no links in root or course independent categories
1957
        if (0 == $this->id) {
1958
        } elseif (isset($studentId)) {
1959
            // 1 student $studentId
1960
            $links = LinkFactory::load(
1961
                null,
1962
                null,
1963
                null,
1964
                null,
1965
                $course_code,
1966
                $this->id,
1967
                api_is_allowed_to_edit() ? null : $forCertificate
1968
            );
1969
        } else {
1970
            // All students -> only for course/platform admin
1971
            $links = LinkFactory::load(
1972
                null,
1973
                null,
1974
                null,
1975
                null,
1976
                $course_code,
1977
                $this->id,
1978
                null
1979
            );
1980
        }
1981
1982
        if ($recursive) {
1983
            $subcats = $this->get_subcategories(
1984
                $studentId,
1985
                $course_code,
1986
                $sessionId
1987
            );
1988
            if (!empty($subcats)) {
1989
                /** @var Category $subcat */
1990
                foreach ($subcats as $subcat) {
1991
                    $sublinks = $subcat->get_links(
1992
                        $studentId,
1993
                        false,
1994
                        $course_code,
1995
                        $sessionId
1996
                    );
1997
                    $links = array_merge($links, $sublinks);
1998
                }
1999
            }
2000
        }
2001
2002
        return $links;
2003
    }
2004
2005
    /**
2006
     * Get all the categories from with the same given direct parent.
2007
     *
2008
     * @param int $catId Category parent ID
2009
     *
2010
     * @return array Array of Category objects
2011
     */
2012
    public function getCategories($catId)
2013
    {
2014
        $catId = (int) $catId;
2015
        $tblGradeCategories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
2016
        $sql = 'SELECT * FROM '.$tblGradeCategories.'
2017
                WHERE parent_id = '.$catId;
2018
2019
        $result = Database::query($sql);
2020
        $categories = self::create_category_objects_from_sql_result($result);
2021
2022
        return $categories;
2023
    }
2024
2025
    /**
2026
     * Gets the type for the current object.
2027
     *
2028
     * @return string 'C' to represent "Category" object type
2029
     */
2030
    public function get_item_type()
2031
    {
2032
        return 'C';
2033
    }
2034
2035
    /**
2036
     * @param array $skills
2037
     */
2038
    public function set_skills($skills)
2039
    {
2040
        $this->skills = $skills;
2041
    }
2042
2043
    public function get_date()
2044
    {
2045
        return null;
2046
    }
2047
2048
    /**
2049
     * @return string
2050
     */
2051
    public function get_icon_name()
2052
    {
2053
        return 'cat';
2054
    }
2055
2056
    /**
2057
     * Find category by name.
2058
     *
2059
     * @param string $name_mask search string
2060
     *
2061
     * @return array category objects matching the search criterium
2062
     */
2063
    public function find_category($name_mask, $allcat)
2064
    {
2065
        $categories = [];
2066
        foreach ($allcat as $search_cat) {
2067
            if (!(strpos(strtolower($search_cat->get_name()), strtolower($name_mask)) === false)) {
2068
                $categories[] = $search_cat;
2069
            }
2070
        }
2071
2072
        return $categories;
2073
    }
2074
2075
    /**
2076
     * This function, locks a category , only one who can unlock it is
2077
     * the platform administrator.
2078
     *
2079
     * @param int locked 1 or unlocked 0
2080
2081
     *
2082
     * @return bool|null
2083
     * */
2084
    public function lock($locked)
2085
    {
2086
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
2087
        $sql = "UPDATE $table SET locked = '".intval($locked)."'
2088
                WHERE id='".intval($this->id)."'";
2089
        Database::query($sql);
2090
    }
2091
2092
    /**
2093
     * @param $locked
2094
     */
2095
    public function lockAllItems($locked)
2096
    {
2097
        if ('true' == api_get_setting('gradebook_locking_enabled')) {
2098
            $this->lock($locked);
2099
            $evals_to_lock = $this->get_evaluations();
2100
            if (!empty($evals_to_lock)) {
2101
                foreach ($evals_to_lock as $item) {
2102
                    $item->lock($locked);
2103
                }
2104
            }
2105
2106
            $link_to_lock = $this->get_links();
2107
            if (!empty($link_to_lock)) {
2108
                foreach ($link_to_lock as $item) {
2109
                    $item->lock($locked);
2110
                }
2111
            }
2112
2113
            $event_type = LOG_GRADEBOOK_UNLOCKED;
2114
            if (1 == $locked) {
2115
                $event_type = LOG_GRADEBOOK_LOCKED;
2116
            }
2117
            Event::addEvent($event_type, LOG_GRADEBOOK_ID, $this->id);
0 ignored issues
show
Bug introduced by
The method addEvent() does not exist on Event. ( Ignorable by Annotation )

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

2117
            Event::/** @scrutinizer ignore-call */ 
2118
                   addEvent($event_type, LOG_GRADEBOOK_ID, $this->id);

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...
2118
        }
2119
    }
2120
2121
    /**
2122
     * Generates a certificate for this user if everything matches.
2123
     *
2124
     * @param int  $category_id            gradebook id
2125
     * @param int  $user_id
2126
     * @param bool $sendNotification
2127
     * @param bool $skipGenerationIfExists
2128
     *
2129
     * @return array
2130
     */
2131
    public static function generateUserCertificate(
2132
        $category_id,
2133
        $user_id,
2134
        $sendNotification = false,
2135
        $skipGenerationIfExists = false
2136
    ) {
2137
        $user_id = (int) $user_id;
2138
        $category_id = (int) $category_id;
2139
2140
        // Generating the total score for a course
2141
        $category = self::load(
2142
            $category_id,
2143
            null,
2144
            null,
2145
            null,
2146
            null,
2147
            null,
2148
            false
2149
        );
2150
2151
        /** @var Category $category */
2152
        $category = $category[0];
2153
2154
        if (empty($category)) {
2155
            return false;
2156
        }
2157
2158
        $sessionId = $category->get_session_id();
2159
        $courseCode = $category->get_course_code();
2160
        $courseInfo = api_get_course_info($courseCode);
2161
        $courseId = $courseInfo['real_id'];
2162
2163
        $userFinishedCourse = self::userFinishedCourse(
2164
            $user_id,
2165
            $category,
2166
            true
2167
        );
2168
2169
        $enableGradeSubCategorySkills = (true === api_get_configuration_value('gradebook_enable_subcategory_skills_independant_assignement'));
2170
        // it continues if is enabled skills independant of assignment
2171
        if (!$userFinishedCourse && !$enableGradeSubCategorySkills) {
2172
            return false;
2173
        }
2174
2175
        $skillToolEnabled = Skill::hasAccessToUserSkill(
2176
            api_get_user_id(),
2177
            $user_id
2178
        );
2179
2180
        $userHasSkills = false;
2181
        if ($skillToolEnabled) {
2182
            $skill = new Skill();
2183
            $objSkillRelUser = new SkillRelUser();
2184
            $skill->addSkillToUser(
2185
                $user_id,
2186
                $category,
2187
                $courseId,
2188
                $sessionId
2189
            );
2190
2191
            $userSkills = $objSkillRelUser->getUserSkills(
2192
                $user_id,
2193
                $courseId,
2194
                $sessionId
2195
            );
2196
            $userHasSkills = !empty($userSkills);
2197
        }
2198
2199
        // certificate is not generated if course is not finished
2200
        if (!$userFinishedCourse) {
2201
            return false;
2202
        }
2203
2204
        // Block certification links depending gradebook configuration (generate certifications)
2205
        if (empty($category->getGenerateCertificates())) {
2206
            if ($userHasSkills) {
2207
                return [
2208
                    'badge_link' => Display::toolbarButton(
2209
                        get_lang('ExportBadges'),
2210
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2211
                        'external-link'
2212
                    ),
2213
                ];
2214
            }
2215
2216
            return false;
2217
        }
2218
2219
        $scoretotal = $category->calc_score($user_id);
2220
2221
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2222
        // file load this variable as a global
2223
        $scoredisplay = ScoreDisplay::instance();
2224
        $my_score_in_gradebook = $scoredisplay->display_score(
2225
            $scoretotal,
2226
            SCORE_SIMPLE
2227
        );
2228
2229
        $my_certificate = GradebookUtils::get_certificate_by_user_id(
2230
            $category_id,
2231
            $user_id
2232
        );
2233
2234
        if ($skipGenerationIfExists && !empty($my_certificate)) {
2235
            return false;
2236
        }
2237
2238
        if (empty($my_certificate)) {
2239
            GradebookUtils::registerUserInfoAboutCertificate(
2240
                $category_id,
2241
                $user_id,
2242
                $my_score_in_gradebook,
2243
                api_get_utc_datetime()
2244
            );
2245
            $my_certificate = GradebookUtils::get_certificate_by_user_id(
2246
                $category_id,
2247
                $user_id
2248
            );
2249
        }
2250
2251
        $html = [];
2252
        if (!empty($my_certificate)) {
2253
            $certificate_obj = new Certificate(
2254
                $my_certificate['id'],
2255
                0,
2256
                $sendNotification
2257
            );
2258
2259
            $fileWasGenerated = $certificate_obj->isHtmlFileGenerated();
2260
2261
            // Fix when using custom certificate BT#15937
2262
            if (api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate') === 'true') {
2263
                $infoCertificate = CustomCertificatePlugin::getCertificateData($my_certificate['id'], $user_id);
2264
                if (!empty($infoCertificate)) {
2265
                    $fileWasGenerated = true;
2266
                }
2267
            }
2268
2269
            if (!empty($fileWasGenerated)) {
2270
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'].'&user_id='.$user_id;
2271
                $certificates = Display::toolbarButton(
2272
                    get_lang('DisplayCertificate'),
2273
                    $url,
2274
                    'eye',
2275
                    'primary',
2276
                    ['target' => '_blank']
2277
                );
2278
2279
                $exportToPDF = Display::url(
2280
                    Display::return_icon(
2281
                        'pdf.png',
2282
                        get_lang('ExportToPDF'),
2283
                        [],
2284
                        ICON_SIZE_MEDIUM
2285
                    ),
2286
                    "$url&action=export"
2287
                );
2288
2289
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2290
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2291
                if ($hideExportLink === 'true' || (api_is_student() && $hideExportLinkStudent === 'true')) {
2292
                    $exportToPDF = null;
2293
                }
2294
2295
                $html = [
2296
                    'certificate_link' => $certificates,
2297
                    'pdf_link' => $exportToPDF,
2298
                    'pdf_url' => "$url&action=export",
2299
                ];
2300
            }
2301
2302
            if ($skillToolEnabled && $userHasSkills) {
2303
                $html['badge_link'] = Display::toolbarButton(
2304
                    get_lang('ExportBadges'),
2305
                    api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2306
                    'external-link'
2307
                );
2308
            }
2309
2310
            return $html;
2311
        }
2312
    }
2313
2314
    /**
2315
     * @param int   $catId
2316
     * @param array $userList
2317
     */
2318
    public static function generateCertificatesInUserList($catId, $userList)
2319
    {
2320
        if (!empty($userList)) {
2321
            foreach ($userList as $userInfo) {
2322
                self::generateUserCertificate($catId, $userInfo['user_id']);
2323
            }
2324
        }
2325
    }
2326
2327
    /**
2328
     * @param int         $catId
2329
     * @param array       $userList
2330
     * @param string|null $courseCode
2331
     * @param bool        $generateToFile
2332
     * @param string      $pdfName
2333
     *
2334
     * @throws \MpdfException
2335
     */
2336
    public static function exportAllCertificates(
2337
        $catId,
2338
        $userList = [],
2339
        $courseCode = null,
2340
        $generateToFile = false,
2341
        $pdfName = ''
2342
    ) {
2343
        $orientation = api_get_configuration_value('certificate_pdf_orientation');
2344
2345
        $params['orientation'] = 'landscape';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
2346
        if (!empty($orientation)) {
2347
            $params['orientation'] = $orientation;
2348
        }
2349
2350
        $params['left'] = 0;
2351
        $params['right'] = 0;
2352
        $params['top'] = 0;
2353
        $params['bottom'] = 0;
2354
        $pageFormat = $params['orientation'] === 'landscape' ? 'A4-L' : 'A4';
2355
        $pdf = new PDF($pageFormat, $params['orientation'], $params);
2356
        if (api_get_configuration_value('add_certificate_pdf_footer')) {
2357
            $pdf->setCertificateFooter();
2358
        }
2359
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2360
        $certificate_path_list = [];
2361
2362
        if (!empty($certificate_list)) {
2363
            foreach ($certificate_list as $index => $value) {
2364
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2365
                    $value['user_id'],
2366
                    $catId
2367
                );
2368
                foreach ($list_certificate as $value_certificate) {
2369
                    $certificate_obj = new Certificate($value_certificate['id']);
2370
                    $certificate_obj->generate(['hide_print_button' => true]);
2371
                    if ($certificate_obj->isHtmlFileGenerated()) {
2372
                        $certificate_path_list[] = $certificate_obj->html_file;
2373
                    }
2374
                }
2375
            }
2376
        }
2377
2378
        if (!empty($certificate_path_list)) {
2379
            // Print certificates (without the common header/footer/watermark
2380
            //  stuff) and return as one multiple-pages PDF
2381
            $pdf->html_to_pdf(
2382
                $certificate_path_list,
2383
                empty($pdfName) ? get_lang('Certificates') : $pdfName,
2384
                $courseCode,
2385
                false,
2386
                false,
2387
                true,
2388
                '',
2389
                $generateToFile
2390
            );
2391
        }
2392
    }
2393
2394
    /**
2395
     * @param int $catId
2396
     */
2397
    public static function deleteAllCertificates($catId)
2398
    {
2399
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2400
        if (!empty($certificate_list)) {
2401
            foreach ($certificate_list as $index => $value) {
2402
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2403
                    $value['user_id'],
2404
                    $catId
2405
                );
2406
                foreach ($list_certificate as $value_certificate) {
2407
                    $certificate_obj = new Certificate($value_certificate['id']);
2408
                    $certificate_obj->delete(true);
2409
                }
2410
            }
2411
        }
2412
    }
2413
2414
    /**
2415
     * Check whether a user has finished a course by its gradebook.
2416
     *
2417
     * @param int       $userId           The user ID
2418
     * @param \Category $category         Optional. The gradebook category.
2419
     *                                    To check by the gradebook category
2420
     * @param bool      $recalculateScore Whether recalculate the score
2421
     *
2422
     * @return bool
2423
     */
2424
    public static function userFinishedCourse(
2425
        $userId,
2426
        Category $category,
2427
        $recalculateScore = false
2428
    ) {
2429
        if (empty($category)) {
2430
            return false;
2431
        }
2432
2433
        $currentScore = self::getCurrentScore(
2434
            $userId,
2435
            $category,
2436
            $recalculateScore
2437
        );
2438
2439
        $minCertificateScore = $category->getCertificateMinScore();
2440
2441
        return $currentScore >= $minCertificateScore;
2442
    }
2443
2444
    /**
2445
     * Get the current score (as percentage) on a gradebook category for a user.
2446
     *
2447
     * @param int      $userId      The user id
2448
     * @param Category $category    The gradebook category
2449
     * @param bool     $recalculate
2450
     *
2451
     * @return float The score
2452
     */
2453
    public static function getCurrentScore(
2454
        $userId,
2455
        $category,
2456
        $recalculate = false
2457
    ) {
2458
        if (empty($category)) {
2459
            return 0;
2460
        }
2461
2462
        if ($recalculate) {
2463
            return self::calculateCurrentScore(
2464
                $userId,
2465
                $category
2466
            );
2467
        }
2468
2469
        $resultData = Database::select(
2470
            '*',
2471
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2472
            [
2473
                'where' => [
2474
                    'category_id = ? AND user_id = ?' => [$category->get_id(), $userId],
2475
                ],
2476
                'order' => 'registered_at DESC',
2477
                'limit' => '1',
2478
            ],
2479
            'first'
2480
        );
2481
2482
        if (empty($resultData)) {
2483
            return 0;
2484
        }
2485
2486
        return $resultData['score'];
2487
    }
2488
2489
    /**
2490
     * Register the current score for a user on a category gradebook.
2491
     *
2492
     * @param float $score      The achieved score
2493
     * @param int   $userId     The user id
2494
     * @param int   $categoryId The gradebook category
2495
     *
2496
     * @return int The insert id
2497
     */
2498
    public static function registerCurrentScore($score, $userId, $categoryId)
2499
    {
2500
        return Database::insert(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::insert(...pi_get_utc_datetime())) could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
2501
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2502
            [
2503
                'category_id' => intval($categoryId),
2504
                'user_id' => intval($userId),
2505
                'score' => api_float_val($score),
2506
                'registered_at' => api_get_utc_datetime(),
2507
            ]
2508
        );
2509
    }
2510
2511
    /**
2512
     * @return array
2513
     */
2514
    public function getStudentList()
2515
    {
2516
        return $this->studentList;
2517
    }
2518
2519
    /**
2520
     * @param array $list
2521
     */
2522
    public function setStudentList($list)
2523
    {
2524
        $this->studentList = $list;
2525
    }
2526
2527
    /**
2528
     * @return string
2529
     */
2530
    public static function getUrl()
2531
    {
2532
        $url = Session::read('gradebook_dest');
2533
        if (empty($url)) {
2534
            // We guess the link
2535
            $courseInfo = api_get_course_info();
2536
            if (!empty($courseInfo)) {
2537
                return api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2538
            } else {
2539
                return api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2540
            }
2541
        }
2542
2543
        return $url;
2544
    }
2545
2546
    /**
2547
     * Destination is index.php or gradebook.php.
2548
     *
2549
     * @param string $url
2550
     */
2551
    public static function setUrl($url)
2552
    {
2553
        switch ($url) {
2554
            case 'gradebook.php':
2555
                $url = api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2556
                break;
2557
            case 'index.php':
2558
                $url = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2559
                break;
2560
        }
2561
        Session::write('gradebook_dest', $url);
2562
    }
2563
2564
    /**
2565
     * @return int
2566
     */
2567
    public function getGradeBooksToValidateInDependence()
2568
    {
2569
        return $this->gradeBooksToValidateInDependence;
2570
    }
2571
2572
    /**
2573
     * @param int $value
2574
     *
2575
     * @return Category
2576
     */
2577
    public function setGradeBooksToValidateInDependence($value)
2578
    {
2579
        $this->gradeBooksToValidateInDependence = $value;
2580
2581
        return $this;
2582
    }
2583
2584
    /**
2585
     * Return HTML code with links to download and view certificate.
2586
     */
2587
    public static function getDownloadCertificateBlock(array $certificate): string
2588
    {
2589
        if (!isset($certificate['pdf_url'])) {
2590
            return '';
2591
        }
2592
2593
        $hideExportLink = api_get_setting('hide_certificate_export_link');
2594
        $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2595
        if ($hideExportLink === 'true' || (api_is_student() && $hideExportLinkStudent === 'true')) {
2596
            $downloadLink = '';
2597
        } else {
2598
            $downloadLink = Display::toolbarButton(
2599
                get_lang('DownloadCertificatePdf'),
2600
                $certificate['pdf_url'],
2601
                'file-pdf-o'
2602
            );
2603
        }
2604
2605
        $viewLink = $certificate['certificate_link'];
2606
2607
        return "
2608
            <div class='panel panel-default'>
2609
                <div class='panel-body'>
2610
                    <h3 class='text-center'>".get_lang('NowDownloadYourCertificateClickHere')."</h3>
2611
                    <div class='text-center'>$downloadLink $viewLink</div>
2612
                </div>
2613
            </div>
2614
        ";
2615
    }
2616
2617
    /**
2618
     * Find a gradebook category by the certificate ID.
2619
     *
2620
     * @param int $id certificate id
2621
     *
2622
     * @throws \Doctrine\ORM\NonUniqueResultException
2623
     *
2624
     * @return Category|null
2625
     */
2626
    public static function findByCertificate($id)
2627
    {
2628
        $category = Database::getManager()
2629
            ->createQuery('SELECT c.catId FROM ChamiloCoreBundle:GradebookCertificate c WHERE c.id = :id')
2630
            ->setParameters(['id' => $id])
2631
            ->getOneOrNullResult();
2632
2633
        if (empty($category)) {
2634
            return null;
2635
        }
2636
2637
        $category = self::load($category['catId']);
2638
2639
        if (empty($category)) {
2640
            return null;
2641
        }
2642
2643
        return $category[0];
2644
    }
2645
2646
    /**
2647
     * @param int $value
2648
     */
2649
    public function setDocumentId($value)
2650
    {
2651
        $this->documentId = (int) $value;
2652
    }
2653
2654
    /**
2655
     * @return int
2656
     */
2657
    public function getDocumentId()
2658
    {
2659
        return $this->documentId;
2660
    }
2661
2662
    /**
2663
     * Get the remaining weight in root category.
2664
     *
2665
     * @return int
2666
     */
2667
    public function getRemainingWeight()
2668
    {
2669
        $subCategories = $this->get_subcategories();
2670
2671
        $subWeight = 0;
2672
2673
        /** @var Category $subCategory */
2674
        foreach ($subCategories as $subCategory) {
2675
            $subWeight += $subCategory->get_weight();
2676
        }
2677
2678
        return $this->weight - $subWeight;
2679
    }
2680
2681
    private static function create_root_category(): Category
2682
    {
2683
        $cat = new Category();
2684
        $cat->set_id(0);
2685
        $cat->set_name(get_lang('RootCat'));
2686
        $cat->set_description(null);
2687
        $cat->set_user_id(0);
2688
        $cat->set_course_code(null);
2689
        $cat->set_parent_id(null);
2690
        $cat->set_weight(0);
2691
        $cat->set_visible(1);
2692
        $cat->setGenerateCertificates(0);
2693
        $cat->setIsRequirement(false);
2694
2695
        return $cat;
2696
    }
2697
2698
    /**
2699
     * @param Doctrine\DBAL\Driver\Statement|null $result
2700
     *
2701
     * @return array<int, Category>
2702
     */
2703
    private static function create_category_objects_from_sql_result($result): array
2704
    {
2705
        $categories = [];
2706
        $allow = api_get_configuration_value('allow_gradebook_stats');
2707
        if ($allow) {
2708
            $em = Database::getManager();
2709
            $repo = $em->getRepository('ChamiloCoreBundle:GradebookCategory');
2710
        }
2711
2712
        while ($data = Database::fetch_array($result)) {
2713
            $cat = new Category();
2714
            $cat->set_id($data['id']);
2715
            $cat->set_name($data['name']);
2716
            $cat->set_description($data['description']);
2717
            $cat->set_user_id($data['user_id']);
2718
            $cat->set_course_code($data['course_code']);
2719
            $cat->set_parent_id($data['parent_id']);
2720
            $cat->set_weight($data['weight']);
2721
            $cat->set_visible($data['visible']);
2722
            $cat->set_session_id($data['session_id']);
2723
            $cat->set_certificate_min_score($data['certif_min_score']);
2724
            $cat->set_grade_model_id($data['grade_model_id']);
2725
            $cat->set_locked($data['locked']);
2726
            $cat->setGenerateCertificates($data['generate_certificates']);
2727
            $cat->setIsRequirement($data['is_requirement']);
2728
            $cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2729
            $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2730
            $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2731
            $cat->setDocumentId($data['document_id']);
2732
            if ($allow) {
2733
                $cat->entity = $repo->find($data['id']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $repo does not seem to be defined for all execution paths leading up to this point.
Loading history...
2734
            }
2735
2736
            $categories[] = $cat;
2737
        }
2738
2739
        return $categories;
2740
    }
2741
2742
    /**
2743
     * Internal function used by get_target_categories().
2744
     *
2745
     * @param array $targets
2746
     * @param int   $level
2747
     * @param int   $catid
2748
     *
2749
     * @return array
2750
     */
2751
    private function addTargetSubcategories($targets, $level, $catid)
2752
    {
2753
        $subcats = self::load(null, null, null, $catid);
2754
        foreach ($subcats as $cat) {
2755
            if ($this->can_be_moved_to_cat($cat)) {
2756
                $targets[] = [
2757
                    $cat->get_id(),
2758
                    $cat->get_name(),
2759
                    $level + 1,
2760
                ];
2761
                $targets = $this->addTargetSubcategories(
2762
                    $targets,
2763
                    $level + 1,
2764
                    $cat->get_id()
2765
                );
2766
            }
2767
        }
2768
2769
        return $targets;
2770
    }
2771
2772
    /**
2773
     * Internal function used by get_target_categories() and addTargetSubcategories()
2774
     * Can this category be moved to the given category ?
2775
     * Impossible when origin and target are the same... children won't be processed
2776
     * either. (a category can't be moved to one of its own children).
2777
     */
2778
    private function can_be_moved_to_cat($cat)
2779
    {
2780
        return $cat->get_id() != $this->get_id();
2781
    }
2782
2783
    /**
2784
     * Internal function used by move_to_cat().
2785
     */
2786
    private function applyCourseCodeToChildren()
2787
    {
2788
        $cats = self::load(null, null, null, $this->id, null);
2789
        $evals = Evaluation::load(null, null, null, $this->id, null);
2790
        $links = LinkFactory::load(
2791
            null,
2792
            null,
2793
            null,
2794
            null,
2795
            null,
2796
            $this->id,
2797
            null
2798
        );
2799
        /** @var Category $cat */
2800
        foreach ($cats as $cat) {
2801
            $cat->set_course_code($this->get_course_code());
2802
            $cat->save();
2803
            $cat->applyCourseCodeToChildren();
2804
        }
2805
2806
        foreach ($evals as $eval) {
2807
            $eval->set_course_code($this->get_course_code());
2808
            $eval->save();
2809
        }
2810
2811
        foreach ($links as $link) {
2812
            $link->delete();
2813
        }
2814
    }
2815
2816
    /**
2817
     * Internal function used by get_tree().
2818
     *
2819
     * @param int      $level
2820
     * @param int|null $visible
2821
     *
2822
     * @return array
2823
     */
2824
    private function add_subtree($targets, $level, $catid, $visible)
2825
    {
2826
        $subcats = self::load(null, null, null, $catid, $visible);
2827
2828
        if (!empty($subcats)) {
2829
            foreach ($subcats as $cat) {
2830
                $targets[] = [
2831
                    $cat->get_id(),
2832
                    $cat->get_name(),
2833
                    $level + 1,
2834
                ];
2835
                $targets = self::add_subtree(
0 ignored issues
show
Bug Best Practice introduced by
The method Category::add_subtree() 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

2835
                /** @scrutinizer ignore-call */ 
2836
                $targets = self::add_subtree(
Loading history...
2836
                    $targets,
2837
                    $level + 1,
2838
                    $cat->get_id(),
2839
                    $visible
2840
                );
2841
            }
2842
        }
2843
2844
        return $targets;
2845
    }
2846
2847
    /**
2848
     * Calculate the current score on a gradebook category for a user.
2849
     *
2850
     * @param int      $userId   The user id
2851
     * @param Category $category The gradebook category
2852
     *
2853
     * @return float The score
2854
     */
2855
    private static function calculateCurrentScore($userId, $category)
2856
    {
2857
        if (empty($category)) {
2858
            return 0;
2859
        }
2860
2861
        $courseEvaluations = $category->get_evaluations($userId, true);
2862
        $courseLinks = $category->get_links($userId, true);
2863
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2864
2865
        $categoryScore = 0;
2866
        for ($i = 0; $i < count($evaluationsAndLinks); $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...
2867
            /** @var AbstractLink $item */
2868
            $item = $evaluationsAndLinks[$i];
2869
            // Set session id from category
2870
            $item->set_session_id($category->get_session_id());
2871
            $score = $item->calc_score($userId);
2872
            $itemValue = 0;
2873
            if (!empty($score)) {
2874
                $divider = $score[1] == 0 ? 1 : $score[1];
2875
                $itemValue = $score[0] / $divider * $item->get_weight();
2876
            }
2877
2878
            $categoryScore += $itemValue;
2879
        }
2880
2881
        return api_float_val($categoryScore);
2882
    }
2883
}
2884