Category::setStudentList()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
    /**
132
     * @return bool
133
     */
134
    public function is_locked()
135
    {
136
        return isset($this->locked) && $this->locked == 1 ? true : false;
137
    }
138
139
    /**
140
     * @return bool
141
     */
142
    public function is_visible()
143
    {
144
        return $this->visible;
145
    }
146
147
    /**
148
     * Get $isRequirement.
149
     *
150
     * @return int
151
     */
152
    public function getIsRequirement()
153
    {
154
        return $this->isRequirement;
155
    }
156
157
    /**
158
     * @param int $id
159
     */
160
    public function set_id($id)
161
    {
162
        $this->id = $id;
163
    }
164
165
    /**
166
     * @param string $name
167
     */
168
    public function set_name($name)
169
    {
170
        $this->name = $name;
171
    }
172
173
    /**
174
     * @param string $description
175
     */
176
    public function set_description($description)
177
    {
178
        $this->description = $description;
179
    }
180
181
    /**
182
     * @param int $user_id
183
     */
184
    public function set_user_id($user_id)
185
    {
186
        $this->user_id = $user_id;
187
    }
188
189
    /**
190
     * @param string $course_code
191
     */
192
    public function set_course_code($course_code)
193
    {
194
        $this->course_code = $course_code;
195
    }
196
197
    /**
198
     * @param float $min_score
199
     */
200
    public function set_certificate_min_score($min_score = null)
201
    {
202
        $this->certificate_min_score = $min_score;
203
    }
204
205
    /**
206
     * @param int $parent
207
     */
208
    public function set_parent_id($parent)
209
    {
210
        $this->parent = (int) $parent;
211
    }
212
213
    /**
214
     * Filters to int and sets the session ID.
215
     *
216
     * @param   int     The session ID from the Dokeos course session
217
     */
218
    public function set_session_id($session_id = 0)
219
    {
220
        $this->session_id = (int) $session_id;
221
    }
222
223
    /**
224
     * @param $weight
225
     */
226
    public function set_weight($weight)
227
    {
228
        $this->weight = $weight;
229
    }
230
231
    /**
232
     * @param $visible
233
     */
234
    public function set_visible($visible)
235
    {
236
        $this->visible = $visible;
237
    }
238
239
    /**
240
     * @param int $id
241
     */
242
    public function set_grade_model_id($id)
243
    {
244
        $this->grade_model_id = $id;
245
    }
246
247
    /**
248
     * @param $locked
249
     */
250
    public function set_locked($locked)
251
    {
252
        $this->locked = $locked;
253
    }
254
255
    /**
256
     * Set $isRequirement.
257
     *
258
     * @param int $isRequirement
259
     */
260
    public function setIsRequirement($isRequirement)
261
    {
262
        $this->isRequirement = $isRequirement;
263
    }
264
265
    /**
266
     * @param $value
267
     */
268
    public function setCourseListDependency($value)
269
    {
270
        $this->courseDependency = [];
271
        $unserialized = false;
272
        if (!empty($value)) {
273
            $unserialized = UnserializeApi::unserialize('not_allowed_classes', $value, true);
274
        }
275
276
        if (false !== $unserialized) {
277
            $this->courseDependency = $unserialized;
278
        }
279
    }
280
281
    /**
282
     * Course id list.
283
     *
284
     * @return array
285
     */
286
    public function getCourseListDependency()
287
    {
288
        return $this->courseDependency;
289
    }
290
291
    /**
292
     * @param int $value
293
     */
294
    public function setMinimumToValidate($value)
295
    {
296
        $this->minimumToValidate = $value;
297
    }
298
299
    public function getMinimumToValidate()
300
    {
301
        return $this->minimumToValidate;
302
    }
303
304
    /**
305
     * @return int|null
306
     */
307
    public function get_grade_model_id()
308
    {
309
        if ($this->grade_model_id < 0) {
310
            return null;
311
        }
312
313
        return $this->grade_model_id;
314
    }
315
316
    /**
317
     * @return string
318
     */
319
    public function get_type()
320
    {
321
        return 'category';
322
    }
323
324
    /**
325
     * @param bool $from_db
326
     *
327
     * @return array|resource
328
     */
329
    public function get_skills($from_db = true)
330
    {
331
        if ($from_db) {
332
            $categoryId = $this->get_id();
333
            $gradebook = new Gradebook();
334
            $skills = $gradebook->getSkillsByGradebook($categoryId);
335
        } else {
336
            $skills = $this->skills;
337
        }
338
339
        return $skills;
340
    }
341
342
    /**
343
     * @return array
344
     */
345
    public function getSkillsForSelect()
346
    {
347
        $skills = $this->get_skills();
348
        $skill_select = [];
349
        if (!empty($skills)) {
350
            foreach ($skills as $skill) {
351
                $skill_select[$skill['id']] = $skill['name'];
352
            }
353
        }
354
355
        return $skill_select;
356
    }
357
358
    /**
359
     * Set the generate_certificates value.
360
     *
361
     * @param int $generateCertificates
362
     */
363
    public function setGenerateCertificates($generateCertificates)
364
    {
365
        $this->generateCertificates = $generateCertificates;
366
    }
367
368
    /**
369
     * Get the generate_certificates value.
370
     *
371
     * @return int
372
     */
373
    public function getGenerateCertificates()
374
    {
375
        return $this->generateCertificates;
376
    }
377
378
    /**
379
     * @param int $id
380
     * @param int $session_id
381
     *
382
     * @return array
383
     */
384
    public static function loadSessionCategories(
385
        $id = null,
386
        $session_id = null
387
    ) {
388
        if (isset($id) && (int) $id === 0) {
389
            $cats = [];
390
            $cats[] = self::create_root_category();
391
392
            return $cats;
393
        }
394
395
        $courseInfo = api_get_course_info_by_id(api_get_course_int_id());
396
        $courseCode = $courseInfo['code'];
397
        $session_id = (int) $session_id;
398
399
        if (!empty($session_id)) {
400
            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
401
            $sql = 'SELECT id, course_code
402
                    FROM '.$table.'
403
                    WHERE session_id = '.$session_id;
404
            $result_session = Database::query($sql);
405
            if (Database::num_rows($result_session) > 0) {
406
                $categoryList = [];
407
                while ($data_session = Database::fetch_array($result_session)) {
408
                    $parent_id = $data_session['id'];
409
                    if ($data_session['course_code'] == $courseCode) {
410
                        $categories = self::load($parent_id);
411
                        $categoryList = array_merge($categoryList, $categories);
412
                    }
413
                }
414
415
                return $categoryList;
416
            }
417
        }
418
    }
419
420
    /**
421
     * Retrieve categories and return them as an array of Category objects.
422
     *
423
     * @param int    $id          category id
424
     * @param int    $user_id     (category owner)
425
     * @param string $course_code
426
     * @param int    $parent_id   parent category
427
     * @param bool   $visible
428
     * @param int    $session_id  (in case we are in a session)
429
     * @param bool   $order_by    Whether to show all "session"
430
     *                            categories (true) or hide them (false) in case there is no session id
431
     *
432
     * @return array
433
     */
434
    public static function load(
435
        $id = null,
436
        $user_id = null,
437
        $course_code = null,
438
        $parent_id = null,
439
        $visible = null,
440
        $session_id = null,
441
        $order_by = null
442
    ) {
443
        //if the category given is explicitly 0 (not null), then create
444
        // a root category object (in memory)
445
        if (isset($id) && (int) $id === 0) {
446
            $cats = [];
447
            $cats[] = self::create_root_category();
448
449
            return $cats;
450
        }
451
452
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
453
        $sql = 'SELECT * FROM '.$table;
454
        $paramcount = 0;
455
        if (isset($id)) {
456
            $sql .= ' WHERE id = '.intval($id);
457
            $paramcount++;
458
        }
459
460
        if (isset($user_id)) {
461
            $user_id = intval($user_id);
462
            if ($paramcount != 0) {
463
                $sql .= ' AND';
464
            } else {
465
                $sql .= ' WHERE';
466
            }
467
            $sql .= ' user_id = '.intval($user_id);
468
            $paramcount++;
469
        }
470
471
        if (isset($course_code)) {
472
            if ($paramcount != 0) {
473
                $sql .= ' AND';
474
            } else {
475
                $sql .= ' WHERE';
476
            }
477
478
            if ($course_code == '0') {
479
                $sql .= ' course_code is null ';
480
            } else {
481
                $sql .= " course_code = '".Database::escape_string($course_code)."'";
482
            }
483
484
            /*if ($show_session_categories !== true) {
485
                // a query on the course should show all
486
                // the categories inside sessions for this course
487
                // otherwise a special parameter is given to ask explicitely
488
                $sql .= " AND (session_id IS NULL OR session_id = 0) ";
489
            } else {*/
490
            if (empty($session_id)) {
491
                $sql .= ' AND (session_id IS NULL OR session_id = 0) ';
492
            } else {
493
                $sql .= ' AND session_id = '.(int) $session_id.' ';
494
            }
495
            //}
496
            $paramcount++;
497
        }
498
499
        if (isset($parent_id)) {
500
            if (0 != $paramcount) {
501
                $sql .= ' AND ';
502
            } else {
503
                $sql .= ' WHERE ';
504
            }
505
            $sql .= ' parent_id = '.intval($parent_id);
506
            $paramcount++;
507
        }
508
509
        if (isset($visible)) {
510
            if (0 != $paramcount) {
511
                $sql .= ' AND';
512
            } else {
513
                $sql .= ' WHERE';
514
            }
515
            $sql .= ' visible = '.intval($visible);
516
        }
517
518
        if (!empty($order_by)) {
519
            if (!empty($order_by) && $order_by != '') {
520
                $sql .= ' '.$order_by;
521
            }
522
        }
523
524
        $result = Database::query($sql);
525
        $categories = [];
526
        if (Database::num_rows($result) > 0) {
527
            $categories = self::create_category_objects_from_sql_result($result);
528
        }
529
530
        return $categories;
531
    }
532
533
    /**
534
     * Create a category object from a GradebookCategory entity.
535
     *
536
     * @param GradebookCategory $gradebookCategory The entity
537
     *
538
     * @return \Category
539
     */
540
    public static function createCategoryObjectFromEntity(GradebookCategory $gradebookCategory)
541
    {
542
        $category = new Category();
543
        $category->set_id($gradebookCategory->getId());
544
        $category->set_name($gradebookCategory->getName());
545
        $category->set_description($gradebookCategory->getDescription());
546
        $category->set_user_id($gradebookCategory->getUserId());
547
        $category->set_course_code($gradebookCategory->getCourseCode());
548
        $category->set_parent_id($gradebookCategory->getParentId());
549
        $category->set_weight($gradebookCategory->getWeight());
550
        $category->set_visible($gradebookCategory->getVisible());
551
        $category->set_session_id($gradebookCategory->getSessionId());
552
        $category->set_certificate_min_score($gradebookCategory->getCertifMinScore());
553
        $category->set_grade_model_id($gradebookCategory->getGradeModelId());
554
        $category->set_locked($gradebookCategory->getLocked());
555
        $category->setGenerateCertificates($gradebookCategory->getGenerateCertificates());
556
        $category->setIsRequirement($gradebookCategory->getIsRequirement());
557
558
        return $category;
559
    }
560
561
    /**
562
     * Insert this category into the database.
563
     */
564
    public function add()
565
    {
566
        if (isset($this->name) && '-1' == $this->name) {
567
            return false;
568
        }
569
570
        if (isset($this->name) && isset($this->user_id)) {
571
            $em = Database::getManager();
572
573
            $category = new GradebookCategory();
574
            $category->setName($this->name);
575
            $category->setDescription($this->description);
576
            $category->setUserId($this->user_id);
577
            $category->setCourseCode($this->course_code);
578
            $category->setParentId($this->parent);
579
            $category->setWeight($this->weight);
580
            $category->setVisible($this->visible);
581
            $category->setCertifMinScore($this->certificate_min_score);
582
            $category->setSessionId($this->session_id);
583
            $category->setGenerateCertificates($this->generateCertificates);
584
            $category->setGradeModelId($this->grade_model_id);
585
            $category->setIsRequirement($this->isRequirement);
586
            $category->setLocked(false);
587
588
            $em->persist($category);
589
            $em->flush();
590
591
            $id = $category->getId();
592
            $this->set_id($id);
593
594
            if (!empty($id)) {
595
                $parent_id = $this->get_parent_id();
596
                $grade_model_id = $this->get_grade_model_id();
597
                if ($parent_id == 0) {
598
                    //do something
599
                    if (isset($grade_model_id) &&
600
                        !empty($grade_model_id) &&
601
                        $grade_model_id != '-1'
602
                    ) {
603
                        $obj = new GradeModel();
604
                        $components = $obj->get_components($grade_model_id);
605
                        $default_weight_setting = api_get_setting('gradebook_default_weight');
606
                        $default_weight = 100;
607
                        if (isset($default_weight_setting)) {
608
                            $default_weight = $default_weight_setting;
609
                        }
610
                        foreach ($components as $component) {
611
                            $gradebook = new Gradebook();
612
                            $params = [];
613
614
                            $params['name'] = $component['acronym'];
615
                            $params['description'] = $component['title'];
616
                            $params['user_id'] = api_get_user_id();
617
                            $params['parent_id'] = $id;
618
                            $params['weight'] = $component['percentage'] / 100 * $default_weight;
619
                            $params['session_id'] = api_get_session_id();
620
                            $params['course_code'] = $this->get_course_code();
621
622
                            $gradebook->save($params);
623
                        }
624
                    }
625
                }
626
            }
627
628
            $gradebook = new Gradebook();
629
            $gradebook->updateSkillsToGradeBook(
630
                $this->id,
631
                $this->get_skills(false)
632
            );
633
634
            return $id;
635
        }
636
    }
637
638
    /**
639
     * Update the properties of this category in the database.
640
     *
641
     * @todo fix me
642
     */
643
    public function save()
644
    {
645
        $em = Database::getManager();
646
647
        /** @var GradebookCategory $gradebookCategory */
648
        $gradebookCategory = $em
649
            ->getRepository('ChamiloCoreBundle:GradebookCategory')
650
            ->find($this->id);
651
652
        if (empty($gradebookCategory)) {
653
            return false;
654
        }
655
656
        $gradebookCategory->setName($this->name);
657
        $gradebookCategory->setDescription($this->description);
658
        $gradebookCategory->setUserId($this->user_id);
659
        $gradebookCategory->setCourseCode($this->course_code);
660
        $gradebookCategory->setParentId($this->parent);
661
        $gradebookCategory->setWeight($this->weight);
662
        $gradebookCategory->setVisible($this->visible);
663
        $gradebookCategory->setCertifMinScore($this->certificate_min_score);
664
        $gradebookCategory->setGenerateCertificates(
665
            $this->generateCertificates
666
        );
667
        $gradebookCategory->setGradeModelId($this->grade_model_id);
668
        $gradebookCategory->setIsRequirement($this->isRequirement);
669
670
        $em->merge($gradebookCategory);
671
        $em->flush();
672
673
        if (!empty($this->id)) {
674
            $parent_id = $this->get_parent_id();
675
            $grade_model_id = $this->get_grade_model_id();
676
            if ($parent_id == 0) {
677
                if (isset($grade_model_id) &&
678
                    !empty($grade_model_id) &&
679
                    $grade_model_id != '-1'
680
                ) {
681
                    $obj = new GradeModel();
682
                    $components = $obj->get_components($grade_model_id);
683
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
684
                    $default_weight = 100;
685
                    if (isset($default_weight_setting)) {
686
                        $default_weight = $default_weight_setting;
687
                    }
688
                    $final_weight = $this->get_weight();
689
                    if (!empty($final_weight)) {
690
                        $default_weight = $this->get_weight();
691
                    }
692
                    foreach ($components as $component) {
693
                        $gradebook = new Gradebook();
694
                        $params = [];
695
                        $params['name'] = $component['acronym'];
696
                        $params['description'] = $component['title'];
697
                        $params['user_id'] = api_get_user_id();
698
                        $params['parent_id'] = $this->id;
699
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
700
                        $params['session_id'] = api_get_session_id();
701
                        $params['course_code'] = $this->get_course_code();
702
                        $gradebook->save($params);
703
                    }
704
                }
705
            }
706
        }
707
708
        $gradebook = new Gradebook();
709
        $gradebook->updateSkillsToGradeBook(
710
            $this->id,
711
            $this->get_skills(false),
712
            true
713
        );
714
    }
715
716
    /**
717
     * Update value to allow user skills by subcategory passed.
718
     *
719
     * @param $value
720
     */
721
    public function updateAllowSkillBySubCategory($value)
722
    {
723
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
724
        $value = (int) $value;
725
        $upd = 'UPDATE '.$table.' SET allow_skills_by_subcategory = '.$value.' WHERE id = '.intval($this->id);
726
        Database::query($upd);
727
    }
728
729
    /**
730
     * Update the current parent id.
731
     *
732
     * @param $parentId
733
     * @param $catId
734
     *
735
     * @throws Exception
736
     */
737
    public function updateParentId($parentId, $catId)
738
    {
739
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
740
        $parentId = (int) $parentId;
741
        $upd = 'UPDATE '.$table.' SET parent_id = '.$parentId.' WHERE id = '.$catId;
742
        Database::query($upd);
743
    }
744
745
    /**
746
     * Get the value to Allow skill by subcategory.
747
     *
748
     * @return bool
749
     */
750
    public function getAllowSkillBySubCategory($parentId = null)
751
    {
752
        $id = (int) $this->id;
753
        if (isset($parentId)) {
754
            $id = (int) $parentId;
755
        }
756
757
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
758
        $sql = 'SELECT allow_skills_by_subcategory FROM '.$table.' WHERE id = '.$id;
759
        $rs = Database::query($sql);
760
        $value = (bool) Database::result($rs, 0, 0);
761
762
        return $value;
763
    }
764
765
    /**
766
     * Update link weights see #5168.
767
     *
768
     * @param type $new_weight
769
     */
770
    public function updateChildrenWeight($new_weight)
771
    {
772
        $links = $this->get_links();
773
        $old_weight = $this->get_weight();
774
775
        if (!empty($links)) {
776
            foreach ($links as $link_item) {
777
                if (isset($link_item)) {
778
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
779
                    $link_item->set_weight($new_item_weight);
780
                    $link_item->save();
781
                }
782
            }
783
        }
784
    }
785
786
    /**
787
     * Delete this evaluation from the database.
788
     */
789
    public function delete()
790
    {
791
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
792
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
793
        Database::query($sql);
794
    }
795
796
    /**
797
     * Delete the gradebook categories from a course, including course sessions.
798
     *
799
     * @param string $courseCode
800
     */
801
    public static function deleteFromCourse($courseCode)
802
    {
803
        $em = Database::getManager();
804
        $categories = $em
805
            ->createQuery(
806
                'SELECT DISTINCT gc.sessionId
807
                FROM ChamiloCoreBundle:GradebookCategory gc WHERE gc.courseCode = :code'
808
            )
809
            ->setParameter('code', $courseCode)
810
            ->getResult();
811
812
        foreach ($categories as $category) {
813
            $cats = self::load(
814
                null,
815
                null,
816
                $courseCode,
817
                null,
818
                null,
819
                (int) $category['sessionId']
820
            );
821
822
            if (!empty($cats)) {
823
                /** @var self $cat */
824
                foreach ($cats as $cat) {
825
                    $cat->delete_all();
826
                }
827
            }
828
        }
829
    }
830
831
    /**
832
     * Show message about the resource having been deleted.
833
     *
834
     * @return string|bool
835
     */
836
    public function show_message_resource_delete(string $courseCode)
837
    {
838
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
839
        $sql = 'SELECT count(*) AS num
840
                FROM '.$table.'
841
                WHERE
842
                    course_code = "'.Database::escape_string($courseCode).'" AND
843
                    visible = 3';
844
        $res = Database::query($sql);
845
        $option = Database::fetch_array($res, 'ASSOC');
846
        if ($option['num'] >= 1) {
847
            return '&nbsp;&nbsp;<span class="resource-deleted">(&nbsp;'.get_lang('ResourceDeleted').'&nbsp;)</span>';
848
        }
849
850
        return false;
851
    }
852
853
    /**
854
     * Shows all information of an category.
855
     *
856
     * @param int $categoryId
857
     *
858
     * @return array
859
     */
860
    public function showAllCategoryInfo($categoryId)
861
    {
862
        $categoryId = (int) $categoryId;
863
        if (empty($categoryId)) {
864
            return [];
865
        }
866
867
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
868
        $sql = 'SELECT * FROM '.$table.'
869
                WHERE id = '.$categoryId;
870
        $result = Database::query($sql);
871
        $row = Database::fetch_array($result, 'ASSOC');
872
873
        return $row;
874
    }
875
876
    /**
877
     * Checks if the certificate is available for the given user in this category.
878
     *
879
     * @param int $user_id User ID
880
     *
881
     * @return bool True if conditions match, false if fails
882
     */
883
    public function is_certificate_available($user_id)
884
    {
885
        $score = $this->calc_score(
886
            $user_id,
887
            null,
888
            $this->course_code,
889
            $this->session_id,
890
            null
891
        );
892
893
        if (isset($score) && isset($score[0])) {
894
            // Get a percentage score to compare to minimum certificate score
895
            // $certification_score = $score[0] / $score[1] * 100;
896
            // Get real score not a percentage.
897
            $certification_score = $score[0];
898
            if ($certification_score >= $this->certificate_min_score) {
899
                return true;
900
            }
901
        }
902
903
        return false;
904
    }
905
906
    /**
907
     * Is this category a course ?
908
     * A category is a course if it has a course code and no parent category.
909
     */
910
    public function is_course()
911
    {
912
        return isset($this->course_code) && !empty($this->course_code)
913
            && (!isset($this->parent) || $this->parent == 0);
914
    }
915
916
    /**
917
     * Calculate the score of this category.
918
     *
919
     * @param int    $stud_id     student id (default: all students - then the average is returned)
920
     * @param        $type
921
     * @param string $course_code
922
     * @param int    $session_id
923
     *
924
     * @return array (score sum, weight sum)
925
     *               or null if no scores available
926
     */
927
    public function calc_score(
928
        $stud_id = null,
929
        $type = null,
930
        $course_code = '',
931
        $session_id = null,
932
        $forCertificate = 1
933
    ) {
934
        $key = 'category:'.$this->id.'student:'.(int) $stud_id.'type:'.$type.'course:'.$course_code.'session:'.(int) $session_id;
935
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
936
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
937
938
        if ($cacheAvailable) {
939
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
940
            if ($cacheDriver->contains($key)) {
941
                return $cacheDriver->fetch($key);
942
            }
943
        }
944
        // Classic
945
        if (!empty($stud_id) && '' == $type) {
946
            if (!empty($course_code)) {
947
                $cats = $this->get_subcategories(
948
                    $stud_id,
949
                    $course_code,
950
                    $session_id,
951
                    null,
952
                    $forCertificate
953
                );
954
                $evals = $this->get_evaluations($stud_id, false, $course_code, $session_id, $forCertificate);
955
                $links = $this->get_links($stud_id, false, $course_code, $session_id, $forCertificate);
956
            } else {
957
                $cats = $this->get_subcategories($stud_id, '', $session_id, null, $forCertificate);
958
                $evals = $this->get_evaluations($stud_id, false, '', $session_id, $forCertificate);
959
                $links = $this->get_links($stud_id, false, '', $session_id, $forCertificate);
960
            }
961
962
            // Calculate score
963
            $count = 0;
964
            $ressum = 0;
965
            $weightsum = 0;
966
            if (!empty($cats)) {
967
                /** @var Category $cat */
968
                foreach ($cats as $cat) {
969
                    $cat->set_session_id($session_id);
970
                    $cat->set_course_code($course_code);
971
                    $cat->setStudentList($this->getStudentList());
972
                    $score = $cat->calc_score(
973
                        $stud_id,
974
                        null,
975
                        $course_code,
976
                        $session_id
977
                    );
978
979
                    $catweight = 0;
980
                    if (0 != $cat->get_weight()) {
981
                        $catweight = $cat->get_weight();
982
                        $weightsum += $catweight;
983
                    }
984
985
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
986
                        $ressum += $score[0] / $score[1] * $catweight;
987
                    }
988
                }
989
            }
990
991
            if (!empty($evals)) {
992
                /** @var Evaluation $eval */
993
                foreach ($evals as $eval) {
994
                    $eval->setStudentList($this->getStudentList());
995
                    $evalres = $eval->calc_score($stud_id);
996
                    if (isset($evalres) && 0 != $eval->get_weight()) {
997
                        $evalweight = $eval->get_weight();
998
                        $weightsum += $evalweight;
999
                        if (!empty($evalres[1])) {
1000
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
1001
                        }
1002
                    } else {
1003
                        if (0 != $eval->get_weight()) {
1004
                            $evalweight = $eval->get_weight();
1005
                            $weightsum += $evalweight;
1006
                        }
1007
                    }
1008
                }
1009
            }
1010
1011
            if (!empty($links)) {
1012
                /** @var EvalLink|ExerciseLink $link */
1013
                foreach ($links as $link) {
1014
                    $link->setStudentList($this->getStudentList());
1015
1016
                    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...
1017
                        $link->set_session_id($session_id);
1018
                    }
1019
1020
                    $linkres = $link->calc_score($stud_id, null);
1021
                    if (!empty($linkres) && 0 != $link->get_weight()) {
1022
                        $linkweight = $link->get_weight();
1023
                        $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1024
                        $weightsum += $linkweight;
1025
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1026
                    } else {
1027
                        // Adding if result does not exists
1028
                        if (0 != $link->get_weight()) {
1029
                            $linkweight = $link->get_weight();
1030
                            $weightsum += $linkweight;
1031
                        }
1032
                    }
1033
                }
1034
            }
1035
        } else {
1036
            if (!empty($course_code)) {
1037
                $cats = $this->get_subcategories(
1038
                    null,
1039
                    $course_code,
1040
                    $session_id
1041
                );
1042
                $evals = $this->get_evaluations(null, false, $course_code);
1043
                $links = $this->get_links(null, false, $course_code);
1044
            } else {
1045
                $cats = $this->get_subcategories(null);
1046
                $evals = $this->get_evaluations(null);
1047
                $links = $this->get_links(null);
1048
            }
1049
1050
            // Calculate score
1051
            $ressum = 0;
1052
            $weightsum = 0;
1053
            $bestResult = 0;
1054
            $totalScorePerStudent = [];
1055
1056
            if (!empty($cats)) {
1057
                /** @var Category $cat */
1058
                foreach ($cats as $cat) {
1059
                    $cat->setStudentList($this->getStudentList());
1060
                    $score = $cat->calc_score(
1061
                        null,
1062
                        $type,
1063
                        $course_code,
1064
                        $session_id
1065
                    );
1066
1067
                    $catweight = 0;
1068
                    if (0 != $cat->get_weight()) {
1069
                        $catweight = $cat->get_weight();
1070
                        $weightsum += $catweight;
1071
                    }
1072
1073
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
1074
                        $ressum += $score[0] / $score[1] * $catweight;
1075
1076
                        if ($ressum > $bestResult) {
1077
                            $bestResult = $ressum;
1078
                        }
1079
                    }
1080
                }
1081
            }
1082
1083
            if (!empty($evals)) {
1084
                if ('best' === $type) {
1085
                    $studentList = $this->getStudentList();
1086
                    foreach ($studentList as $student) {
1087
                        $studentId = $student['user_id'];
1088
                        foreach ($evals as $eval) {
1089
                            $linkres = $eval->calc_score($studentId, null);
1090
                            $linkweight = $eval->get_weight();
1091
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1092
                            $ressum = $linkres[0] / $link_res_denom * $linkweight;
1093
1094
                            if (!isset($totalScorePerStudent[$studentId])) {
1095
                                $totalScorePerStudent[$studentId] = 0;
1096
                            }
1097
                            $totalScorePerStudent[$studentId] += $ressum;
1098
                        }
1099
                    }
1100
                } else {
1101
                    /** @var Evaluation $eval */
1102
                    foreach ($evals as $eval) {
1103
                        $evalres = $eval->calc_score(null, $type);
1104
                        $eval->setStudentList($this->getStudentList());
1105
1106
                        if (isset($evalres) && 0 != $eval->get_weight()) {
1107
                            $evalweight = $eval->get_weight();
1108
                            $weightsum += $evalweight;
1109
                            if (!empty($evalres[1])) {
1110
                                $ressum += $evalres[0] / $evalres[1] * $evalweight;
1111
                            }
1112
1113
                            if ($ressum > $bestResult) {
1114
                                $bestResult = $ressum;
1115
                            }
1116
                        } else {
1117
                            if (0 != $eval->get_weight()) {
1118
                                $evalweight = $eval->get_weight();
1119
                                $weightsum += $evalweight;
1120
                            }
1121
                        }
1122
                    }
1123
                }
1124
            }
1125
1126
            if (!empty($links)) {
1127
                $studentList = $this->getStudentList();
1128
                if ('best' === $type) {
1129
                    foreach ($studentList as $student) {
1130
                        $studentId = $student['user_id'];
1131
                        foreach ($links as $link) {
1132
                            $linkres = $link->calc_score($studentId, null);
1133
                            $linkweight = $link->get_weight();
1134
                            if ($linkres) {
1135
                                $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1136
                                $ressum = $linkres[0] / $link_res_denom * $linkweight;
1137
                            }
1138
1139
                            if (!isset($totalScorePerStudent[$studentId])) {
1140
                                $totalScorePerStudent[$studentId] = 0;
1141
                            }
1142
                            $totalScorePerStudent[$studentId] += $ressum;
1143
                        }
1144
                    }
1145
                } else {
1146
                    /** @var EvalLink|ExerciseLink $link */
1147
                    foreach ($links as $link) {
1148
                        $link->setStudentList($this->getStudentList());
1149
1150
                        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...
1151
                            $link->set_session_id($session_id);
1152
                        }
1153
1154
                        $linkres = $link->calc_score($stud_id, $type);
1155
1156
                        if (!empty($linkres) && 0 != $link->get_weight()) {
1157
                            $linkweight = $link->get_weight();
1158
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1159
1160
                            $weightsum += $linkweight;
1161
                            $ressum += $linkres[0] / $link_res_denom * $linkweight;
1162
                            if ($ressum > $bestResult) {
1163
                                $bestResult = $ressum;
1164
                            }
1165
                        } else {
1166
                            // Adding if result does not exists
1167
                            if (0 != $link->get_weight()) {
1168
                                $linkweight = $link->get_weight();
1169
                                $weightsum += $linkweight;
1170
                            }
1171
                        }
1172
                    }
1173
                }
1174
            }
1175
        }
1176
1177
        switch ($type) {
1178
            case 'best':
1179
                arsort($totalScorePerStudent);
1180
                $maxScore = current($totalScorePerStudent);
1181
1182
                return [$maxScore, $this->get_weight()];
1183
                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...
1184
            case 'average':
1185
                if (empty($ressum)) {
1186
                    if ($cacheAvailable) {
1187
                        $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...
1188
                    }
1189
1190
                    return null;
1191
                }
1192
1193
                if ($cacheAvailable) {
1194
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1195
                }
1196
1197
                return [$ressum, $weightsum];
1198
                break;
1199
            case 'ranking':
1200
                // category ranking is calculated in gradebook_data_generator.class.php
1201
                // function get_data
1202
                return null;
1203
1204
                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...
1205
                break;
1206
            default:
1207
                if ($cacheAvailable) {
1208
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1209
                }
1210
1211
                return [$ressum, $weightsum];
1212
                break;
1213
        }
1214
    }
1215
1216
    /**
1217
     * Delete this category and every subcategory, evaluation and result inside.
1218
     */
1219
    public function delete_all()
1220
    {
1221
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1222
        $evals = Evaluation::load(
1223
            null,
1224
            null,
1225
            $this->course_code,
1226
            $this->id,
1227
            null
1228
        );
1229
1230
        $links = LinkFactory::load(
1231
            null,
1232
            null,
1233
            null,
1234
            null,
1235
            $this->course_code,
1236
            $this->id,
1237
            null
1238
        );
1239
1240
        if (!empty($cats)) {
1241
            /** @var Category $cat */
1242
            foreach ($cats as $cat) {
1243
                $cat->delete_all();
1244
                $cat->delete();
1245
            }
1246
        }
1247
1248
        if (!empty($evals)) {
1249
            /** @var Evaluation $eval */
1250
            foreach ($evals as $eval) {
1251
                $eval->delete_with_results();
1252
            }
1253
        }
1254
1255
        if (!empty($links)) {
1256
            /** @var AbstractLink $link */
1257
            foreach ($links as $link) {
1258
                $link->delete();
1259
            }
1260
        }
1261
1262
        $this->delete();
1263
    }
1264
1265
    /**
1266
     * Return array of Category objects where a student is subscribed to.
1267
     *
1268
     * @param int    $stud_id
1269
     * @param string $course_code
1270
     * @param int    $session_id
1271
     *
1272
     * @return array
1273
     */
1274
    public function get_root_categories_for_student(
1275
        $stud_id,
1276
        $course_code = null,
1277
        $session_id = null
1278
    ) {
1279
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1280
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1281
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1282
1283
        $course_code = Database::escape_string($course_code);
1284
        $session_id = (int) $session_id;
1285
1286
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1287
1288
        if (!api_is_allowed_to_edit()) {
1289
            $sql .= ' AND visible = 1';
1290
            //proceed with checks on optional parameters course & session
1291
            if (!empty($course_code)) {
1292
                // TODO: considering it highly improbable that a user would get here
1293
                // if he doesn't have the rights to view this course and this
1294
                // session, we don't check his registration to these, but this
1295
                // could be an improvement
1296
                if (!empty($session_id)) {
1297
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1298
                } else {
1299
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1300
                }
1301
            } else {
1302
                //no optional parameter, proceed as usual
1303
                $sql .= ' AND course_code in
1304
                     (
1305
                        SELECT c.code
1306
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1307
                        ON (cu.c_id = c.id)
1308
                        WHERE cu.user_id = '.intval($stud_id).'
1309
                        AND cu.status = '.STUDENT.'
1310
                    )';
1311
            }
1312
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1313
            //proceed with checks on optional parameters course & session
1314
            if (!empty($course_code)) {
1315
                // TODO: considering it highly improbable that a user would get here
1316
                // if he doesn't have the rights to view this course and this
1317
                // session, we don't check his registration to these, but this
1318
                // could be an improvement
1319
                $sql .= " AND course_code  = '".$course_code."'";
1320
                if (!empty($session_id)) {
1321
                    $sql .= " AND session_id = ".$session_id;
1322
                } else {
1323
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1324
                }
1325
            } else {
1326
                $sql .= ' AND course_code IN
1327
                     (
1328
                        SELECT c.code
1329
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1330
                        ON (cu.c_id = c.id)
1331
                        WHERE
1332
                            cu.user_id = '.api_get_user_id().' AND
1333
                            cu.status = '.COURSEMANAGER.'
1334
                    )';
1335
            }
1336
        } elseif (api_is_platform_admin()) {
1337
            if (isset($session_id) && 0 != $session_id) {
1338
                $sql .= ' AND session_id='.$session_id;
1339
            } else {
1340
                $sql .= ' AND coalesce(session_id,0)=0';
1341
            }
1342
        }
1343
        $result = Database::query($sql);
1344
        $cats = self::create_category_objects_from_sql_result($result);
1345
1346
        // course independent categories
1347
        if (empty($course_code)) {
1348
            $cats = $this->getIndependentCategoriesWithStudentResult(
1349
                0,
1350
                $stud_id,
1351
                $cats
1352
            );
1353
        }
1354
1355
        return $cats;
1356
    }
1357
1358
    /**
1359
     * Return array of Category objects where a teacher is admin for.
1360
     *
1361
     * @param int    $user_id     (to return everything, use 'null' here)
1362
     * @param string $course_code (optional)
1363
     * @param int    $session_id  (optional)
1364
     *
1365
     * @return array
1366
     */
1367
    public function get_root_categories_for_teacher(
1368
        $user_id,
1369
        $course_code = null,
1370
        $session_id = null
1371
    ) {
1372
        if (null == $user_id) {
1373
            return self::load(null, null, $course_code, 0, null, $session_id);
1374
        }
1375
1376
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1377
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1378
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1379
1380
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1381
                WHERE parent_id = 0 ';
1382
        if (!empty($course_code)) {
1383
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1384
            if (!empty($session_id)) {
1385
                $sql .= " AND session_id = ".(int) $session_id;
1386
            }
1387
        } else {
1388
            $sql .= ' AND course_code in
1389
                 (
1390
                    SELECT c.code
1391
                    FROM '.$main_course_user_table.' cu
1392
                    INNER JOIN '.$courseTable.' c
1393
                    ON (cu.c_id = c.id)
1394
                    WHERE user_id = '.intval($user_id).'
1395
                )';
1396
        }
1397
        $result = Database::query($sql);
1398
        $cats = self::create_category_objects_from_sql_result($result);
1399
        // course independent categories
1400
        if (isset($course_code)) {
1401
            $indcats = self::load(
1402
                null,
1403
                $user_id,
1404
                $course_code,
1405
                0,
1406
                null,
1407
                $session_id
1408
            );
1409
            $cats = array_merge($cats, $indcats);
1410
        }
1411
1412
        return $cats;
1413
    }
1414
1415
    /**
1416
     * Can this category be moved to somewhere else ?
1417
     * The root and courses cannot be moved.
1418
     *
1419
     * @return bool
1420
     */
1421
    public function is_movable()
1422
    {
1423
        return !(!isset($this->id) || 0 == $this->id || $this->is_course());
1424
    }
1425
1426
    /**
1427
     * Generate an array of possible categories where this category can be moved to.
1428
     * Notice: its own parent will be included in the list: it's up to the frontend
1429
     * to disable this element.
1430
     *
1431
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1432
     */
1433
    public function get_target_categories()
1434
    {
1435
        // the root or a course -> not movable
1436
        if (!$this->is_movable()) {
1437
            return null;
1438
        } else {
1439
            // otherwise:
1440
            // - course independent category
1441
            //   -> movable to root or other independent categories
1442
            // - category inside a course
1443
            //   -> movable to root, independent categories or categories inside the course
1444
            $user = api_is_platform_admin() ? null : api_get_user_id();
1445
            $targets = [];
1446
            $level = 0;
1447
1448
            $root = [0, get_lang('RootCat'), $level];
1449
            $targets[] = $root;
1450
1451
            if (isset($this->course_code) && !empty($this->course_code)) {
1452
                $crscats = self::load(null, null, $this->course_code, 0);
1453
                foreach ($crscats as $cat) {
1454
                    if ($this->can_be_moved_to_cat($cat)) {
1455
                        $targets[] = [
1456
                            $cat->get_id(),
1457
                            $cat->get_name(),
1458
                            $level + 1,
1459
                        ];
1460
                        $targets = $this->addTargetSubcategories(
1461
                            $targets,
1462
                            $level + 1,
1463
                            $cat->get_id()
1464
                        );
1465
                    }
1466
                }
1467
            }
1468
1469
            $indcats = self::load(null, $user, 0, 0);
1470
            foreach ($indcats as $cat) {
1471
                if ($this->can_be_moved_to_cat($cat)) {
1472
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1473
                    $targets = $this->addTargetSubcategories(
1474
                        $targets,
1475
                        $level + 1,
1476
                        $cat->get_id()
1477
                    );
1478
                }
1479
            }
1480
1481
            return $targets;
1482
        }
1483
    }
1484
1485
    /**
1486
     * Move this category to the given category.
1487
     * If this category moves from inside a course to outside,
1488
     * its course code must be changed, as well as the course code
1489
     * of all underlying categories and evaluations. All links will
1490
     * be deleted as well !
1491
     */
1492
    public function move_to_cat($cat)
1493
    {
1494
        $this->set_parent_id($cat->get_id());
1495
        if ($this->get_course_code() != $cat->get_course_code()) {
1496
            $this->set_course_code($cat->get_course_code());
1497
            $this->applyCourseCodeToChildren();
1498
        }
1499
        $this->save();
1500
    }
1501
1502
    /**
1503
     * Generate an array of all categories the user can navigate to.
1504
     */
1505
    public function get_tree()
1506
    {
1507
        $targets = [];
1508
        $level = 0;
1509
        $root = [0, get_lang('RootCat'), $level];
1510
        $targets[] = $root;
1511
1512
        // course or platform admin
1513
        if (api_is_allowed_to_edit()) {
1514
            $user = api_is_platform_admin() ? null : api_get_user_id();
1515
            $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

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

2120
            Event::/** @scrutinizer ignore-call */ 
2121
                   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...
2121
        }
2122
    }
2123
2124
    /**
2125
     * Generates a certificate for this user if everything matches.
2126
     *
2127
     * @param int  $category_id            gradebook id
2128
     * @param int  $user_id
2129
     * @param bool $sendNotification
2130
     * @param bool $skipGenerationIfExists
2131
     *
2132
     * @return array
2133
     */
2134
    public static function generateUserCertificate(
2135
        $category_id,
2136
        $user_id,
2137
        $sendNotification = false,
2138
        $skipGenerationIfExists = false
2139
    ) {
2140
        $user_id = (int) $user_id;
2141
        $category_id = (int) $category_id;
2142
2143
        // Generating the total score for a course
2144
        $category = self::load(
2145
            $category_id,
2146
            null,
2147
            null,
2148
            null,
2149
            null,
2150
            null,
2151
            false
2152
        );
2153
2154
        /** @var Category $category */
2155
        $category = $category[0];
2156
2157
        if (empty($category)) {
2158
            return false;
2159
        }
2160
2161
        $sessionId = $category->get_session_id();
2162
        $courseCode = $category->get_course_code();
2163
        $courseInfo = api_get_course_info($courseCode);
2164
        $courseId = $courseInfo['real_id'];
2165
2166
        $userFinishedCourse = self::userFinishedCourse(
2167
            $user_id,
2168
            $category,
2169
            true
2170
        );
2171
2172
        $enableGradeSubCategorySkills = (true === api_get_configuration_value('gradebook_enable_subcategory_skills_independant_assignement'));
2173
        // it continues if is enabled skills independant of assignment
2174
        if (!$userFinishedCourse && !$enableGradeSubCategorySkills) {
2175
            return false;
2176
        }
2177
2178
        $skillToolEnabled = Skill::hasAccessToUserSkill(
2179
            api_get_user_id(),
2180
            $user_id
2181
        );
2182
2183
        $userHasSkills = false;
2184
        if ($skillToolEnabled) {
2185
            $skill = new Skill();
2186
            $objSkillRelUser = new SkillRelUser();
2187
            $skill->addSkillToUser(
2188
                $user_id,
2189
                $category,
2190
                $courseId,
2191
                $sessionId
2192
            );
2193
2194
            $userSkills = $objSkillRelUser->getUserSkills(
2195
                $user_id,
2196
                $courseId,
2197
                $sessionId
2198
            );
2199
            $userHasSkills = !empty($userSkills);
2200
        }
2201
2202
        // certificate is not generated if course is not finished
2203
        if (!$userFinishedCourse) {
2204
            return false;
2205
        }
2206
2207
        // Block certification links depending gradebook configuration (generate certifications)
2208
        if (empty($category->getGenerateCertificates())) {
2209
            if ($userHasSkills) {
2210
                return [
2211
                    'badge_link' => Display::toolbarButton(
2212
                        get_lang('ExportBadges'),
2213
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2214
                        'external-link'
2215
                    ),
2216
                ];
2217
            }
2218
2219
            return false;
2220
        }
2221
2222
        $scoretotal = $category->calc_score($user_id);
2223
2224
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2225
        // file load this variable as a global
2226
        $scoredisplay = ScoreDisplay::instance();
2227
        $my_score_in_gradebook = $scoredisplay->display_score(
2228
            $scoretotal,
2229
            SCORE_SIMPLE
2230
        );
2231
2232
        $my_certificate = GradebookUtils::get_certificate_by_user_id(
2233
            $category_id,
2234
            $user_id
2235
        );
2236
2237
        if ($skipGenerationIfExists && !empty($my_certificate)) {
2238
            return false;
2239
        }
2240
2241
        if (empty($my_certificate)) {
2242
            GradebookUtils::registerUserInfoAboutCertificate(
2243
                $category_id,
2244
                $user_id,
2245
                $my_score_in_gradebook,
2246
                api_get_utc_datetime()
2247
            );
2248
            $my_certificate = GradebookUtils::get_certificate_by_user_id(
2249
                $category_id,
2250
                $user_id
2251
            );
2252
        }
2253
2254
        $html = [];
2255
        if (!empty($my_certificate)) {
2256
            $certificate_obj = new Certificate(
2257
                $my_certificate['id'],
2258
                0,
2259
                $sendNotification
2260
            );
2261
2262
            $fileWasGenerated = $certificate_obj->isHtmlFileGenerated();
2263
2264
            // Fix when using custom certificate BT#15937
2265
            if (api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate') === 'true') {
2266
                $infoCertificate = CustomCertificatePlugin::getCertificateData($my_certificate['id'], $user_id);
2267
                if (!empty($infoCertificate)) {
2268
                    $fileWasGenerated = true;
2269
                }
2270
            }
2271
2272
            if (!empty($fileWasGenerated)) {
2273
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'].'&user_id='.$user_id;
2274
                $certificates = Display::toolbarButton(
2275
                    get_lang('DisplayCertificate'),
2276
                    $url,
2277
                    'eye',
2278
                    'primary',
2279
                    ['target' => '_blank']
2280
                );
2281
2282
                $exportToPDF = Display::url(
2283
                    Display::return_icon(
2284
                        'pdf.png',
2285
                        get_lang('ExportToPDF'),
2286
                        [],
2287
                        ICON_SIZE_MEDIUM
2288
                    ),
2289
                    "$url&action=export"
2290
                );
2291
2292
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2293
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2294
                if ($hideExportLink === 'true' || (api_is_student() && $hideExportLinkStudent === 'true')) {
2295
                    $exportToPDF = null;
2296
                }
2297
2298
                $html = [
2299
                    'certificate_link' => $certificates,
2300
                    'pdf_link' => $exportToPDF,
2301
                    'pdf_url' => "$url&action=export",
2302
                ];
2303
            }
2304
2305
            if ($skillToolEnabled && $userHasSkills) {
2306
                $html['badge_link'] = Display::toolbarButton(
2307
                    get_lang('ExportBadges'),
2308
                    api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2309
                    'external-link'
2310
                );
2311
            }
2312
2313
            return $html;
2314
        }
2315
    }
2316
2317
    /**
2318
     * @param int   $catId
2319
     * @param array $userList
2320
     */
2321
    public static function generateCertificatesInUserList($catId, $userList)
2322
    {
2323
        if (!empty($userList)) {
2324
            foreach ($userList as $userInfo) {
2325
                self::generateUserCertificate($catId, $userInfo['user_id']);
2326
            }
2327
        }
2328
    }
2329
2330
    /**
2331
     * @param int         $catId
2332
     * @param array       $userList
2333
     * @param string|null $courseCode
2334
     * @param bool        $generateToFile
2335
     * @param string      $pdfName
2336
     *
2337
     * @throws \MpdfException
2338
     */
2339
    public static function exportAllCertificates(
2340
        $catId,
2341
        $userList = [],
2342
        $courseCode = null,
2343
        $generateToFile = false,
2344
        $pdfName = ''
2345
    ) {
2346
        $orientation = api_get_configuration_value('certificate_pdf_orientation');
2347
2348
        $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...
2349
        if (!empty($orientation)) {
2350
            $params['orientation'] = $orientation;
2351
        }
2352
2353
        $params['left'] = 0;
2354
        $params['right'] = 0;
2355
        $params['top'] = 0;
2356
        $params['bottom'] = 0;
2357
        $pageFormat = $params['orientation'] === 'landscape' ? 'A4-L' : 'A4';
2358
        $pdf = new PDF($pageFormat, $params['orientation'], $params);
2359
        if (api_get_configuration_value('add_certificate_pdf_footer')) {
2360
            $pdf->setCertificateFooter();
2361
        }
2362
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2363
        $certificate_path_list = [];
2364
2365
        if (!empty($certificate_list)) {
2366
            foreach ($certificate_list as $index => $value) {
2367
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2368
                    $value['user_id'],
2369
                    $catId
2370
                );
2371
                foreach ($list_certificate as $value_certificate) {
2372
                    $certificate_obj = new Certificate($value_certificate['id']);
2373
                    $certificate_obj->generate(['hide_print_button' => true]);
2374
                    if ($certificate_obj->isHtmlFileGenerated()) {
2375
                        $certificate_path_list[] = $certificate_obj->html_file;
2376
                    }
2377
                }
2378
            }
2379
        }
2380
2381
        if (!empty($certificate_path_list)) {
2382
            // Print certificates (without the common header/footer/watermark
2383
            //  stuff) and return as one multiple-pages PDF
2384
            $pdf->html_to_pdf(
2385
                $certificate_path_list,
2386
                empty($pdfName) ? get_lang('Certificates') : $pdfName,
2387
                $courseCode,
2388
                false,
2389
                false,
2390
                true,
2391
                '',
2392
                $generateToFile
2393
            );
2394
        }
2395
    }
2396
2397
    /**
2398
     * @param int $catId
2399
     */
2400
    public static function deleteAllCertificates($catId)
2401
    {
2402
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2403
        if (!empty($certificate_list)) {
2404
            foreach ($certificate_list as $index => $value) {
2405
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2406
                    $value['user_id'],
2407
                    $catId
2408
                );
2409
                foreach ($list_certificate as $value_certificate) {
2410
                    $certificate_obj = new Certificate($value_certificate['id']);
2411
                    $certificate_obj->delete(true);
2412
                }
2413
            }
2414
        }
2415
    }
2416
2417
    /**
2418
     * Check whether a user has finished a course by its gradebook.
2419
     *
2420
     * @param int       $userId           The user ID
2421
     * @param \Category $category         Optional. The gradebook category.
2422
     *                                    To check by the gradebook category
2423
     * @param bool      $recalculateScore Whether recalculate the score
2424
     *
2425
     * @return bool
2426
     */
2427
    public static function userFinishedCourse(
2428
        $userId,
2429
        Category $category,
2430
        $recalculateScore = false
2431
    ) {
2432
        if (empty($category)) {
2433
            return false;
2434
        }
2435
2436
        $currentScore = self::getCurrentScore(
2437
            $userId,
2438
            $category,
2439
            $recalculateScore
2440
        );
2441
2442
        $minCertificateScore = $category->getCertificateMinScore();
2443
2444
        return $currentScore >= $minCertificateScore;
2445
    }
2446
2447
    /**
2448
     * Get the current score (as percentage) on a gradebook category for a user.
2449
     *
2450
     * @param int      $userId      The user id
2451
     * @param Category $category    The gradebook category
2452
     * @param bool     $recalculate
2453
     *
2454
     * @return float The score
2455
     */
2456
    public static function getCurrentScore(
2457
        $userId,
2458
        $category,
2459
        $recalculate = false
2460
    ) {
2461
        if (empty($category)) {
2462
            return 0;
2463
        }
2464
2465
        if ($recalculate) {
2466
            return self::calculateCurrentScore(
2467
                $userId,
2468
                $category
2469
            );
2470
        }
2471
2472
        $resultData = Database::select(
2473
            '*',
2474
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2475
            [
2476
                'where' => [
2477
                    'category_id = ? AND user_id = ?' => [$category->get_id(), $userId],
2478
                ],
2479
                'order' => 'registered_at DESC',
2480
                'limit' => '1',
2481
            ],
2482
            'first'
2483
        );
2484
2485
        if (empty($resultData)) {
2486
            return 0;
2487
        }
2488
2489
        return $resultData['score'];
2490
    }
2491
2492
    /**
2493
     * Register the current score for a user on a category gradebook.
2494
     *
2495
     * @param float $score      The achieved score
2496
     * @param int   $userId     The user id
2497
     * @param int   $categoryId The gradebook category
2498
     *
2499
     * @return int The insert id
2500
     */
2501
    public static function registerCurrentScore($score, $userId, $categoryId)
2502
    {
2503
        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...
2504
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2505
            [
2506
                'category_id' => intval($categoryId),
2507
                'user_id' => intval($userId),
2508
                'score' => api_float_val($score),
2509
                'registered_at' => api_get_utc_datetime(),
2510
            ]
2511
        );
2512
    }
2513
2514
    /**
2515
     * @return array
2516
     */
2517
    public function getStudentList()
2518
    {
2519
        return $this->studentList;
2520
    }
2521
2522
    /**
2523
     * @param array $list
2524
     */
2525
    public function setStudentList($list)
2526
    {
2527
        $this->studentList = $list;
2528
    }
2529
2530
    /**
2531
     * @return string
2532
     */
2533
    public static function getUrl()
2534
    {
2535
        $url = Session::read('gradebook_dest');
2536
        if (empty($url)) {
2537
            // We guess the link
2538
            $courseInfo = api_get_course_info();
2539
            if (!empty($courseInfo)) {
2540
                return api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2541
            } else {
2542
                return api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2543
            }
2544
        }
2545
2546
        return $url;
2547
    }
2548
2549
    /**
2550
     * Destination is index.php or gradebook.php.
2551
     *
2552
     * @param string $url
2553
     */
2554
    public static function setUrl($url)
2555
    {
2556
        switch ($url) {
2557
            case 'gradebook.php':
2558
                $url = api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2559
                break;
2560
            case 'index.php':
2561
                $url = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2562
                break;
2563
        }
2564
        Session::write('gradebook_dest', $url);
2565
    }
2566
2567
    /**
2568
     * @return int
2569
     */
2570
    public function getGradeBooksToValidateInDependence()
2571
    {
2572
        return $this->gradeBooksToValidateInDependence;
2573
    }
2574
2575
    /**
2576
     * @param int $value
2577
     *
2578
     * @return Category
2579
     */
2580
    public function setGradeBooksToValidateInDependence($value)
2581
    {
2582
        $this->gradeBooksToValidateInDependence = $value;
2583
2584
        return $this;
2585
    }
2586
2587
    /**
2588
     * Return HTML code with links to download and view certificate.
2589
     */
2590
    public static function getDownloadCertificateBlock(array $certificate): string
2591
    {
2592
        if (!isset($certificate['pdf_url'])) {
2593
            return '';
2594
        }
2595
2596
        $hideExportLink = api_get_setting('hide_certificate_export_link');
2597
        $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2598
        if ($hideExportLink === 'true' || (api_is_student() && $hideExportLinkStudent === 'true')) {
2599
            $downloadLink = '';
2600
        } else {
2601
            $downloadLink = Display::toolbarButton(
2602
                get_lang('DownloadCertificatePdf'),
2603
                $certificate['pdf_url'],
2604
                'file-pdf-o'
2605
            );
2606
        }
2607
2608
        $viewLink = $certificate['certificate_link'];
2609
2610
        return "
2611
            <div class='panel panel-default'>
2612
                <div class='panel-body'>
2613
                    <h3 class='text-center'>".get_lang('NowDownloadYourCertificateClickHere')."</h3>
2614
                    <div class='text-center'>$downloadLink $viewLink</div>
2615
                </div>
2616
            </div>
2617
        ";
2618
    }
2619
2620
    /**
2621
     * Find a gradebook category by the certificate ID.
2622
     *
2623
     * @param int $id certificate id
2624
     *
2625
     * @throws \Doctrine\ORM\NonUniqueResultException
2626
     *
2627
     * @return Category|null
2628
     */
2629
    public static function findByCertificate($id)
2630
    {
2631
        $category = Database::getManager()
2632
            ->createQuery('SELECT c.catId FROM ChamiloCoreBundle:GradebookCertificate c WHERE c.id = :id')
2633
            ->setParameters(['id' => $id])
2634
            ->getOneOrNullResult();
2635
2636
        if (empty($category)) {
2637
            return null;
2638
        }
2639
2640
        $category = self::load($category['catId']);
2641
2642
        if (empty($category)) {
2643
            return null;
2644
        }
2645
2646
        return $category[0];
2647
    }
2648
2649
    /**
2650
     * @param int $value
2651
     */
2652
    public function setDocumentId($value)
2653
    {
2654
        $this->documentId = (int) $value;
2655
    }
2656
2657
    /**
2658
     * @return int
2659
     */
2660
    public function getDocumentId()
2661
    {
2662
        return $this->documentId;
2663
    }
2664
2665
    /**
2666
     * Get the remaining weight in root category.
2667
     *
2668
     * @return int
2669
     */
2670
    public function getRemainingWeight()
2671
    {
2672
        $subCategories = $this->get_subcategories();
2673
2674
        $subWeight = 0;
2675
2676
        /** @var Category $subCategory */
2677
        foreach ($subCategories as $subCategory) {
2678
            $subWeight += $subCategory->get_weight();
2679
        }
2680
2681
        return $this->weight - $subWeight;
2682
    }
2683
2684
    /**
2685
     * @return Category
2686
     */
2687
    private static function create_root_category()
2688
    {
2689
        $cat = new Category();
2690
        $cat->set_id(0);
2691
        $cat->set_name(get_lang('RootCat'));
2692
        $cat->set_description(null);
2693
        $cat->set_user_id(0);
2694
        $cat->set_course_code(null);
2695
        $cat->set_parent_id(null);
2696
        $cat->set_weight(0);
2697
        $cat->set_visible(1);
2698
        $cat->setGenerateCertificates(0);
2699
        $cat->setIsRequirement(false);
2700
2701
        return $cat;
2702
    }
2703
2704
    /**
2705
     * @param Doctrine\DBAL\Driver\Statement|null $result
2706
     *
2707
     * @return array
2708
     */
2709
    private static function create_category_objects_from_sql_result($result)
2710
    {
2711
        $categories = [];
2712
        $allow = api_get_configuration_value('allow_gradebook_stats');
2713
        if ($allow) {
2714
            $em = Database::getManager();
2715
            $repo = $em->getRepository('ChamiloCoreBundle:GradebookCategory');
2716
        }
2717
2718
        while ($data = Database::fetch_array($result)) {
2719
            $cat = new Category();
2720
            $cat->set_id($data['id']);
2721
            $cat->set_name($data['name']);
2722
            $cat->set_description($data['description']);
2723
            $cat->set_user_id($data['user_id']);
2724
            $cat->set_course_code($data['course_code']);
2725
            $cat->set_parent_id($data['parent_id']);
2726
            $cat->set_weight($data['weight']);
2727
            $cat->set_visible($data['visible']);
2728
            $cat->set_session_id($data['session_id']);
2729
            $cat->set_certificate_min_score($data['certif_min_score']);
2730
            $cat->set_grade_model_id($data['grade_model_id']);
2731
            $cat->set_locked($data['locked']);
2732
            $cat->setGenerateCertificates($data['generate_certificates']);
2733
            $cat->setIsRequirement($data['is_requirement']);
2734
            $cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2735
            $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2736
            $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2737
            $cat->setDocumentId($data['document_id']);
2738
            if ($allow) {
2739
                $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...
2740
            }
2741
2742
            $categories[] = $cat;
2743
        }
2744
2745
        return $categories;
2746
    }
2747
2748
    /**
2749
     * Internal function used by get_target_categories().
2750
     *
2751
     * @param array $targets
2752
     * @param int   $level
2753
     * @param int   $catid
2754
     *
2755
     * @return array
2756
     */
2757
    private function addTargetSubcategories($targets, $level, $catid)
2758
    {
2759
        $subcats = self::load(null, null, null, $catid);
2760
        foreach ($subcats as $cat) {
2761
            if ($this->can_be_moved_to_cat($cat)) {
2762
                $targets[] = [
2763
                    $cat->get_id(),
2764
                    $cat->get_name(),
2765
                    $level + 1,
2766
                ];
2767
                $targets = $this->addTargetSubcategories(
2768
                    $targets,
2769
                    $level + 1,
2770
                    $cat->get_id()
2771
                );
2772
            }
2773
        }
2774
2775
        return $targets;
2776
    }
2777
2778
    /**
2779
     * Internal function used by get_target_categories() and addTargetSubcategories()
2780
     * Can this category be moved to the given category ?
2781
     * Impossible when origin and target are the same... children won't be processed
2782
     * either. (a category can't be moved to one of its own children).
2783
     */
2784
    private function can_be_moved_to_cat($cat)
2785
    {
2786
        return $cat->get_id() != $this->get_id();
2787
    }
2788
2789
    /**
2790
     * Internal function used by move_to_cat().
2791
     */
2792
    private function applyCourseCodeToChildren()
2793
    {
2794
        $cats = self::load(null, null, null, $this->id, null);
2795
        $evals = Evaluation::load(null, null, null, $this->id, null);
2796
        $links = LinkFactory::load(
2797
            null,
2798
            null,
2799
            null,
2800
            null,
2801
            null,
2802
            $this->id,
2803
            null
2804
        );
2805
        /** @var Category $cat */
2806
        foreach ($cats as $cat) {
2807
            $cat->set_course_code($this->get_course_code());
2808
            $cat->save();
2809
            $cat->applyCourseCodeToChildren();
2810
        }
2811
2812
        foreach ($evals as $eval) {
2813
            $eval->set_course_code($this->get_course_code());
2814
            $eval->save();
2815
        }
2816
2817
        foreach ($links as $link) {
2818
            $link->delete();
2819
        }
2820
    }
2821
2822
    /**
2823
     * Internal function used by get_tree().
2824
     *
2825
     * @param int      $level
2826
     * @param int|null $visible
2827
     *
2828
     * @return array
2829
     */
2830
    private function add_subtree($targets, $level, $catid, $visible)
2831
    {
2832
        $subcats = self::load(null, null, null, $catid, $visible);
2833
2834
        if (!empty($subcats)) {
2835
            foreach ($subcats as $cat) {
2836
                $targets[] = [
2837
                    $cat->get_id(),
2838
                    $cat->get_name(),
2839
                    $level + 1,
2840
                ];
2841
                $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

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