Issues (2037)

main/gradebook/lib/be/category.class.php (8 issues)

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
        );
891
892
        if (isset($score) && isset($score[0])) {
893
            // Get a percentage score to compare to minimum certificate score
894
            // $certification_score = $score[0] / $score[1] * 100;
895
            // Get real score not a percentage.
896
            $certification_score = $score[0];
897
            if ($certification_score >= $this->certificate_min_score) {
898
                return true;
899
            }
900
        }
901
902
        return false;
903
    }
904
905
    /**
906
     * Is this category a course ?
907
     * A category is a course if it has a course code and no parent category.
908
     */
909
    public function is_course()
910
    {
911
        return isset($this->course_code) && !empty($this->course_code)
912
            && (!isset($this->parent) || $this->parent == 0);
913
    }
914
915
    /**
916
     * Calculate the score of this category.
917
     *
918
     * @param int    $stud_id     student id (default: all students - then the average is returned)
919
     * @param        $type
920
     * @param string $course_code
921
     * @param int    $session_id
922
     *
923
     * @return array (score sum, weight sum)
924
     *               or null if no scores available
925
     */
926
    public function calc_score(
927
        $stud_id = null,
928
        $type = null,
929
        $course_code = '',
930
        $session_id = null
931
    ) {
932
        $key = 'category:'.$this->id.'student:'.(int) $stud_id.'type:'.$type.'course:'.$course_code.'session:'.(int) $session_id;
933
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
934
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
935
936
        if ($cacheAvailable) {
937
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
938
            if ($cacheDriver->contains($key)) {
939
                return $cacheDriver->fetch($key);
940
            }
941
        }
942
        // Classic
943
        if (!empty($stud_id) && '' == $type) {
944
            if (!empty($course_code)) {
945
                $cats = $this->get_subcategories(
946
                    $stud_id,
947
                    $course_code,
948
                    $session_id
949
                );
950
                $evals = $this->get_evaluations($stud_id, false, $course_code);
951
                $links = $this->get_links($stud_id, false, $course_code);
952
            } else {
953
                $cats = $this->get_subcategories($stud_id);
954
                $evals = $this->get_evaluations($stud_id);
955
                $links = $this->get_links($stud_id);
956
            }
957
958
            // Calculate score
959
            $count = 0;
960
            $ressum = 0;
961
            $weightsum = 0;
962
            if (!empty($cats)) {
963
                /** @var Category $cat */
964
                foreach ($cats as $cat) {
965
                    $cat->set_session_id($session_id);
966
                    $cat->set_course_code($course_code);
967
                    $cat->setStudentList($this->getStudentList());
968
                    $score = $cat->calc_score(
969
                        $stud_id,
970
                        null,
971
                        $course_code,
972
                        $session_id
973
                    );
974
975
                    $catweight = 0;
976
                    if (0 != $cat->get_weight()) {
977
                        $catweight = $cat->get_weight();
978
                        $weightsum += $catweight;
979
                    }
980
981
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
982
                        $ressum += $score[0] / $score[1] * $catweight;
983
                    }
984
                }
985
            }
986
987
            if (!empty($evals)) {
988
                /** @var Evaluation $eval */
989
                foreach ($evals as $eval) {
990
                    $eval->setStudentList($this->getStudentList());
991
                    $evalres = $eval->calc_score($stud_id);
992
                    if (isset($evalres) && 0 != $eval->get_weight()) {
993
                        $evalweight = $eval->get_weight();
994
                        $weightsum += $evalweight;
995
                        if (!empty($evalres[1])) {
996
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
997
                        }
998
                    } else {
999
                        if (0 != $eval->get_weight()) {
1000
                            $evalweight = $eval->get_weight();
1001
                            $weightsum += $evalweight;
1002
                        }
1003
                    }
1004
                }
1005
            }
1006
1007
            if (!empty($links)) {
1008
                /** @var EvalLink|ExerciseLink $link */
1009
                foreach ($links as $link) {
1010
                    $link->setStudentList($this->getStudentList());
1011
1012
                    if ($session_id) {
1013
                        $link->set_session_id($session_id);
1014
                    }
1015
1016
                    $linkres = $link->calc_score($stud_id, null);
1017
                    if (!empty($linkres) && 0 != $link->get_weight()) {
1018
                        $linkweight = $link->get_weight();
1019
                        $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1020
                        $weightsum += $linkweight;
1021
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1022
                    } else {
1023
                        // Adding if result does not exists
1024
                        if (0 != $link->get_weight()) {
1025
                            $linkweight = $link->get_weight();
1026
                            $weightsum += $linkweight;
1027
                        }
1028
                    }
1029
                }
1030
            }
1031
        } else {
1032
            if (!empty($course_code)) {
1033
                $cats = $this->get_subcategories(
1034
                    null,
1035
                    $course_code,
1036
                    $session_id
1037
                );
1038
                $evals = $this->get_evaluations(null, false, $course_code);
1039
                $links = $this->get_links(null, false, $course_code);
1040
            } else {
1041
                $cats = $this->get_subcategories(null);
1042
                $evals = $this->get_evaluations(null);
1043
                $links = $this->get_links(null);
1044
            }
1045
1046
            // Calculate score
1047
            $ressum = 0;
1048
            $weightsum = 0;
1049
            $bestResult = 0;
1050
            $totalScorePerStudent = [];
1051
1052
            if (!empty($cats)) {
1053
                /** @var Category $cat */
1054
                foreach ($cats as $cat) {
1055
                    $cat->setStudentList($this->getStudentList());
1056
                    $score = $cat->calc_score(
1057
                        null,
1058
                        $type,
1059
                        $course_code,
1060
                        $session_id
1061
                    );
1062
1063
                    $catweight = 0;
1064
                    if (0 != $cat->get_weight()) {
1065
                        $catweight = $cat->get_weight();
1066
                        $weightsum += $catweight;
1067
                    }
1068
1069
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
1070
                        $ressum += $score[0] / $score[1] * $catweight;
1071
1072
                        if ($ressum > $bestResult) {
1073
                            $bestResult = $ressum;
1074
                        }
1075
                    }
1076
                }
1077
            }
1078
1079
            if (!empty($evals)) {
1080
                if ('best' === $type) {
1081
                    $studentList = $this->getStudentList();
1082
                    foreach ($studentList as $student) {
1083
                        $studentId = $student['user_id'];
1084
                        foreach ($evals as $eval) {
1085
                            $linkres = $eval->calc_score($studentId, null);
1086
                            $linkweight = $eval->get_weight();
1087
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1088
                            $ressum = $linkres[0] / $link_res_denom * $linkweight;
1089
1090
                            if (!isset($totalScorePerStudent[$studentId])) {
1091
                                $totalScorePerStudent[$studentId] = 0;
1092
                            }
1093
                            $totalScorePerStudent[$studentId] += $ressum;
1094
                        }
1095
                    }
1096
                } else {
1097
                    /** @var Evaluation $eval */
1098
                    foreach ($evals as $eval) {
1099
                        $evalres = $eval->calc_score(null, $type);
1100
                        $eval->setStudentList($this->getStudentList());
1101
1102
                        if (isset($evalres) && 0 != $eval->get_weight()) {
1103
                            $evalweight = $eval->get_weight();
1104
                            $weightsum += $evalweight;
1105
                            if (!empty($evalres[1])) {
1106
                                $ressum += $evalres[0] / $evalres[1] * $evalweight;
1107
                            }
1108
1109
                            if ($ressum > $bestResult) {
1110
                                $bestResult = $ressum;
1111
                            }
1112
                        } else {
1113
                            if (0 != $eval->get_weight()) {
1114
                                $evalweight = $eval->get_weight();
1115
                                $weightsum += $evalweight;
1116
                            }
1117
                        }
1118
                    }
1119
                }
1120
            }
1121
1122
            if (!empty($links)) {
1123
                $studentList = $this->getStudentList();
1124
                if ('best' === $type) {
1125
                    foreach ($studentList as $student) {
1126
                        $studentId = $student['user_id'];
1127
                        foreach ($links as $link) {
1128
                            $linkres = $link->calc_score($studentId, null);
1129
                            $linkweight = $link->get_weight();
1130
                            if ($linkres) {
1131
                                $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1132
                                $ressum = $linkres[0] / $link_res_denom * $linkweight;
1133
                            }
1134
1135
                            if (!isset($totalScorePerStudent[$studentId])) {
1136
                                $totalScorePerStudent[$studentId] = 0;
1137
                            }
1138
                            $totalScorePerStudent[$studentId] += $ressum;
1139
                        }
1140
                    }
1141
                } else {
1142
                    /** @var EvalLink|ExerciseLink $link */
1143
                    foreach ($links as $link) {
1144
                        $link->setStudentList($this->getStudentList());
1145
1146
                        if ($session_id) {
1147
                            $link->set_session_id($session_id);
1148
                        }
1149
1150
                        $linkres = $link->calc_score($stud_id, $type);
1151
1152
                        if (!empty($linkres) && 0 != $link->get_weight()) {
1153
                            $linkweight = $link->get_weight();
1154
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1155
1156
                            $weightsum += $linkweight;
1157
                            $ressum += $linkres[0] / $link_res_denom * $linkweight;
1158
                            if ($ressum > $bestResult) {
1159
                                $bestResult = $ressum;
1160
                            }
1161
                        } else {
1162
                            // Adding if result does not exists
1163
                            if (0 != $link->get_weight()) {
1164
                                $linkweight = $link->get_weight();
1165
                                $weightsum += $linkweight;
1166
                            }
1167
                        }
1168
                    }
1169
                }
1170
            }
1171
        }
1172
1173
        switch ($type) {
1174
            case 'best':
1175
                arsort($totalScorePerStudent);
1176
                $maxScore = current($totalScorePerStudent);
1177
1178
                return [$maxScore, $this->get_weight()];
1179
                break;
0 ignored issues
show
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...
1180
            case 'average':
1181
                if (empty($ressum)) {
1182
                    if ($cacheAvailable) {
1183
                        $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...
1184
                    }
1185
1186
                    return null;
1187
                }
1188
1189
                if ($cacheAvailable) {
1190
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1191
                }
1192
1193
                return [$ressum, $weightsum];
1194
                break;
1195
            case 'ranking':
1196
                // category ranking is calculated in gradebook_data_generator.class.php
1197
                // function get_data
1198
                return null;
1199
1200
                return AbstractLink::getCurrentUserRanking($stud_id, []);
0 ignored issues
show
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...
1201
                break;
1202
            default:
1203
                if ($cacheAvailable) {
1204
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1205
                }
1206
1207
                return [$ressum, $weightsum];
1208
                break;
1209
        }
1210
    }
1211
1212
    /**
1213
     * Delete this category and every subcategory, evaluation and result inside.
1214
     */
1215
    public function delete_all()
1216
    {
1217
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1218
        $evals = Evaluation::load(
1219
            null,
1220
            null,
1221
            $this->course_code,
1222
            $this->id,
1223
            null
1224
        );
1225
1226
        $links = LinkFactory::load(
1227
            null,
1228
            null,
1229
            null,
1230
            null,
1231
            $this->course_code,
1232
            $this->id,
1233
            null
1234
        );
1235
1236
        if (!empty($cats)) {
1237
            /** @var Category $cat */
1238
            foreach ($cats as $cat) {
1239
                $cat->delete_all();
1240
                $cat->delete();
1241
            }
1242
        }
1243
1244
        if (!empty($evals)) {
1245
            /** @var Evaluation $eval */
1246
            foreach ($evals as $eval) {
1247
                $eval->delete_with_results();
1248
            }
1249
        }
1250
1251
        if (!empty($links)) {
1252
            /** @var AbstractLink $link */
1253
            foreach ($links as $link) {
1254
                $link->delete();
1255
            }
1256
        }
1257
1258
        $this->delete();
1259
    }
1260
1261
    /**
1262
     * Return array of Category objects where a student is subscribed to.
1263
     *
1264
     * @param int    $stud_id
1265
     * @param string $course_code
1266
     * @param int    $session_id
1267
     *
1268
     * @return array
1269
     */
1270
    public function get_root_categories_for_student(
1271
        $stud_id,
1272
        $course_code = null,
1273
        $session_id = null
1274
    ) {
1275
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1276
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1277
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1278
1279
        $course_code = Database::escape_string($course_code);
1280
        $session_id = (int) $session_id;
1281
1282
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1283
1284
        if (!api_is_allowed_to_edit()) {
1285
            $sql .= ' AND visible = 1';
1286
            //proceed with checks on optional parameters course & session
1287
            if (!empty($course_code)) {
1288
                // TODO: considering it highly improbable that a user would get here
1289
                // if he doesn't have the rights to view this course and this
1290
                // session, we don't check his registration to these, but this
1291
                // could be an improvement
1292
                if (!empty($session_id)) {
1293
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1294
                } else {
1295
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1296
                }
1297
            } else {
1298
                //no optional parameter, proceed as usual
1299
                $sql .= ' AND course_code in
1300
                     (
1301
                        SELECT c.code
1302
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1303
                        ON (cu.c_id = c.id)
1304
                        WHERE cu.user_id = '.intval($stud_id).'
1305
                        AND cu.status = '.STUDENT.'
1306
                    )';
1307
            }
1308
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1309
            //proceed with checks on optional parameters course & session
1310
            if (!empty($course_code)) {
1311
                // TODO: considering it highly improbable that a user would get here
1312
                // if he doesn't have the rights to view this course and this
1313
                // session, we don't check his registration to these, but this
1314
                // could be an improvement
1315
                $sql .= " AND course_code  = '".$course_code."'";
1316
                if (!empty($session_id)) {
1317
                    $sql .= " AND session_id = ".$session_id;
1318
                } else {
1319
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1320
                }
1321
            } else {
1322
                $sql .= ' AND course_code IN
1323
                     (
1324
                        SELECT c.code
1325
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1326
                        ON (cu.c_id = c.id)
1327
                        WHERE
1328
                            cu.user_id = '.api_get_user_id().' AND
1329
                            cu.status = '.COURSEMANAGER.'
1330
                    )';
1331
            }
1332
        } elseif (api_is_platform_admin()) {
1333
            if (isset($session_id) && 0 != $session_id) {
1334
                $sql .= ' AND session_id='.$session_id;
1335
            } else {
1336
                $sql .= ' AND coalesce(session_id,0)=0';
1337
            }
1338
        }
1339
        $result = Database::query($sql);
1340
        $cats = self::create_category_objects_from_sql_result($result);
1341
1342
        // course independent categories
1343
        if (empty($course_code)) {
1344
            $cats = $this->getIndependentCategoriesWithStudentResult(
1345
                0,
1346
                $stud_id,
1347
                $cats
1348
            );
1349
        }
1350
1351
        return $cats;
1352
    }
1353
1354
    /**
1355
     * Return array of Category objects where a teacher is admin for.
1356
     *
1357
     * @param int    $user_id     (to return everything, use 'null' here)
1358
     * @param string $course_code (optional)
1359
     * @param int    $session_id  (optional)
1360
     *
1361
     * @return array
1362
     */
1363
    public function get_root_categories_for_teacher(
1364
        $user_id,
1365
        $course_code = null,
1366
        $session_id = null
1367
    ) {
1368
        if (null == $user_id) {
1369
            return self::load(null, null, $course_code, 0, null, $session_id);
1370
        }
1371
1372
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1373
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1374
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1375
1376
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1377
                WHERE parent_id = 0 ';
1378
        if (!empty($course_code)) {
1379
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1380
            if (!empty($session_id)) {
1381
                $sql .= " AND session_id = ".(int) $session_id;
1382
            }
1383
        } else {
1384
            $sql .= ' AND course_code in
1385
                 (
1386
                    SELECT c.code
1387
                    FROM '.$main_course_user_table.' cu
1388
                    INNER JOIN '.$courseTable.' c
1389
                    ON (cu.c_id = c.id)
1390
                    WHERE user_id = '.intval($user_id).'
1391
                )';
1392
        }
1393
        $result = Database::query($sql);
1394
        $cats = self::create_category_objects_from_sql_result($result);
1395
        // course independent categories
1396
        if (isset($course_code)) {
1397
            $indcats = self::load(
1398
                null,
1399
                $user_id,
1400
                $course_code,
1401
                0,
1402
                null,
1403
                $session_id
1404
            );
1405
            $cats = array_merge($cats, $indcats);
1406
        }
1407
1408
        return $cats;
1409
    }
1410
1411
    /**
1412
     * Can this category be moved to somewhere else ?
1413
     * The root and courses cannot be moved.
1414
     *
1415
     * @return bool
1416
     */
1417
    public function is_movable()
1418
    {
1419
        return !(!isset($this->id) || 0 == $this->id || $this->is_course());
1420
    }
1421
1422
    /**
1423
     * Generate an array of possible categories where this category can be moved to.
1424
     * Notice: its own parent will be included in the list: it's up to the frontend
1425
     * to disable this element.
1426
     *
1427
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1428
     */
1429
    public function get_target_categories()
1430
    {
1431
        // the root or a course -> not movable
1432
        if (!$this->is_movable()) {
1433
            return null;
1434
        } else {
1435
            // otherwise:
1436
            // - course independent category
1437
            //   -> movable to root or other independent categories
1438
            // - category inside a course
1439
            //   -> movable to root, independent categories or categories inside the course
1440
            $user = api_is_platform_admin() ? null : api_get_user_id();
1441
            $targets = [];
1442
            $level = 0;
1443
1444
            $root = [0, get_lang('RootCat'), $level];
1445
            $targets[] = $root;
1446
1447
            if (isset($this->course_code) && !empty($this->course_code)) {
1448
                $crscats = self::load(null, null, $this->course_code, 0);
1449
                foreach ($crscats as $cat) {
1450
                    if ($this->can_be_moved_to_cat($cat)) {
1451
                        $targets[] = [
1452
                            $cat->get_id(),
1453
                            $cat->get_name(),
1454
                            $level + 1,
1455
                        ];
1456
                        $targets = $this->addTargetSubcategories(
1457
                            $targets,
1458
                            $level + 1,
1459
                            $cat->get_id()
1460
                        );
1461
                    }
1462
                }
1463
            }
1464
1465
            $indcats = self::load(null, $user, 0, 0);
1466
            foreach ($indcats as $cat) {
1467
                if ($this->can_be_moved_to_cat($cat)) {
1468
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1469
                    $targets = $this->addTargetSubcategories(
1470
                        $targets,
1471
                        $level + 1,
1472
                        $cat->get_id()
1473
                    );
1474
                }
1475
            }
1476
1477
            return $targets;
1478
        }
1479
    }
1480
1481
    /**
1482
     * Move this category to the given category.
1483
     * If this category moves from inside a course to outside,
1484
     * its course code must be changed, as well as the course code
1485
     * of all underlying categories and evaluations. All links will
1486
     * be deleted as well !
1487
     */
1488
    public function move_to_cat($cat)
1489
    {
1490
        $this->set_parent_id($cat->get_id());
1491
        if ($this->get_course_code() != $cat->get_course_code()) {
1492
            $this->set_course_code($cat->get_course_code());
1493
            $this->applyCourseCodeToChildren();
1494
        }
1495
        $this->save();
1496
    }
1497
1498
    /**
1499
     * Generate an array of all categories the user can navigate to.
1500
     */
1501
    public function get_tree()
1502
    {
1503
        $targets = [];
1504
        $level = 0;
1505
        $root = [0, get_lang('RootCat'), $level];
1506
        $targets[] = $root;
1507
1508
        // course or platform admin
1509
        if (api_is_allowed_to_edit()) {
1510
            $user = api_is_platform_admin() ? null : api_get_user_id();
1511
            $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

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

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

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