Issues (2029)

main/gradebook/lib/be/category.class.php (1 issue)

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;
1180
            case 'average':
1181
                if (empty($ressum)) {
1182
                    if ($cacheAvailable) {
1183
                        $cacheDriver->save($key, null);
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, []);
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);
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';
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']);
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(
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