Category::set_description()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\GradebookCategory;
7
use Chamilo\CoreBundle\Framework\Container;
8
use ChamiloSession as Session;
9
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
10
11
/**
12
 * Class Category
13
 * Defines a gradebook Category object.
14
 */
15
class Category implements GradebookItem
16
{
17
    public $studentList;
18
    public $evaluations;
19
    public $links;
20
    public $subCategories;
21
    /** @var GradebookCategory */
22
    public $entity;
23
    private int $id;
24
    private $name;
25
    private $description;
26
    private $user_id;
27
    private $course_code;
28
    private $courseId;
29
    private $parent;
30
    private $weight;
31
    private $visible;
32
    private $certificate_min_score;
33
    private $session_id;
34
    private $skills = [];
35
    private $grade_model_id;
36
    private $generateCertificates;
37
    private $isRequirement;
38
    private $courseDependency;
39
    private $minimumToValidate;
40
    private $documentId;
41
    /** @var int */
42
    private $gradeBooksToValidateInDependence;
43
44
    /**
45
     * Consctructor.
46
     */
47
    public function __construct()
48
    {
49
        $this->id = 0;
50
        $this->name = null;
51
        $this->description = '';
52
        $this->user_id = 0;
53
        $this->courseId = 0;
54
        $this->parent = 0;
55
        $this->weight = 0;
56
        $this->visible = false;
57
        $this->certificate_min_score = 0;
58
        $this->session_id = 0;
59
        $this->grade_model_id = 0;
60
        $this->generateCertificates = false;
61
        $this->isRequirement = false;
62
        $this->courseDependency = [];
63
        $this->documentId = 0;
64
        $this->minimumToValidate = null;
65
    }
66
67
    /**
68
     * @return int
69
     */
70
    public function get_id()
71
    {
72
        return $this->id;
73
    }
74
75
    /**
76
     * @return string
77
     */
78
    public function get_name()
79
    {
80
        return $this->name;
81
    }
82
83
    /**
84
     * @return string
85
     */
86
    public function get_description()
87
    {
88
        return $this->description;
89
    }
90
91
    /**
92
     * @return int
93
     */
94
    public function get_user_id()
95
    {
96
        return $this->user_id;
97
    }
98
99
    /**
100
     * @return int|null
101
     */
102
    public function getCertificateMinScore()
103
    {
104
        if (!empty($this->certificate_min_score)) {
105
            return $this->certificate_min_score;
106
        }
107
108
        return null;
109
    }
110
111
    /**
112
     * @return string
113
     */
114
    public function get_course_code()
115
    {
116
        return $this->course_code;
117
    }
118
119
    /**
120
     * @return int
121
     */
122
    public function get_parent_id()
123
    {
124
        return $this->parent;
125
    }
126
127
    /**
128
     * @return int
129
     */
130
    public function get_weight()
131
    {
132
        return $this->weight;
133
    }
134
135
    /**
136
     * @return bool
137
     */
138
    public function is_locked()
139
    {
140
        return isset($this->locked) && 1 == $this->locked ? true : false;
141
    }
142
143
    /**
144
     * @return bool
145
     */
146
    public function is_visible()
147
    {
148
        return $this->visible;
149
    }
150
151
    /**
152
     * Get $isRequirement.
153
     *
154
     * @return int
155
     */
156
    public function getIsRequirement()
157
    {
158
        return $this->isRequirement;
159
    }
160
161
    /**
162
     * @param int $id
163
     */
164
    public function set_id($id)
165
    {
166
        $this->id = $id;
167
    }
168
169
    /**
170
     * @param string $name
171
     */
172
    public function set_name($name)
173
    {
174
        $this->name = $name;
175
    }
176
177
    /**
178
     * @param string $description
179
     */
180
    public function set_description($description)
181
    {
182
        $this->description = $description;
183
    }
184
185
    /**
186
     * @param int $user_id
187
     */
188
    public function set_user_id($user_id)
189
    {
190
        $this->user_id = $user_id;
191
    }
192
193
    /**
194
     * @param float $min_score
195
     */
196
    public function set_certificate_min_score($min_score = null)
197
    {
198
        $this->certificate_min_score = $min_score;
199
    }
200
201
    /**
202
     * @param int $parent
203
     */
204
    public function set_parent_id($parent)
205
    {
206
        $this->parent = (int) $parent;
207
    }
208
209
    /**
210
     * Filters to int and sets the session ID.
211
     *
212
     * @param   int     The session ID from the Dokeos course session
213
     */
214
    public function set_session_id($session_id = 0)
215
    {
216
        $this->session_id = (int) $session_id;
217
    }
218
219
    /**
220
     * @param $weight
221
     */
222
    public function set_weight($weight)
223
    {
224
        $this->weight = $weight;
225
    }
226
227
    /**
228
     * @param $visible
229
     */
230
    public function set_visible($visible)
231
    {
232
        $this->visible = $visible;
233
    }
234
235
    /**
236
     * @param int $id
237
     */
238
    public function set_grade_model_id($id)
239
    {
240
        $this->grade_model_id = $id;
241
    }
242
243
    /**
244
     * @param $locked
245
     */
246
    public function set_locked($locked)
247
    {
248
        $this->locked = $locked;
249
    }
250
251
    /**
252
     * Set $isRequirement.
253
     *
254
     * @param int $isRequirement
255
     */
256
    public function setIsRequirement($isRequirement)
257
    {
258
        $this->isRequirement = $isRequirement;
259
    }
260
261
    /**
262
     * @param $value
263
     */
264
    public function setCourseListDependency($value)
265
    {
266
        $this->courseDependency = [];
267
268
        $unserialized = UnserializeApi::unserialize('not_allowed_classes', $value, true);
269
270
        if (false !== $unserialized) {
271
            $this->courseDependency = $unserialized;
272
        }
273
    }
274
275
    /**
276
     * Course id list.
277
     *
278
     * @return array
279
     */
280
    public function getCourseListDependency()
281
    {
282
        return $this->courseDependency;
283
    }
284
285
    /**
286
     * @param int $value
287
     */
288
    public function setMinimumToValidate($value)
289
    {
290
        $this->minimumToValidate = $value;
291
    }
292
293
    public function getMinimumToValidate()
294
    {
295
        return $this->minimumToValidate;
296
    }
297
298
    /**
299
     * @return int|null
300
     */
301
    public function get_grade_model_id()
302
    {
303
        if ($this->grade_model_id < 0) {
304
            return null;
305
        }
306
307
        return $this->grade_model_id;
308
    }
309
310
    /**
311
     * @return string
312
     */
313
    public function get_type()
314
    {
315
        return 'category';
316
    }
317
318
    /**
319
     * @param bool $from_db
320
     *
321
     * @return array|resource
322
     */
323
    public function get_skills($from_db = true)
324
    {
325
        if ($from_db) {
326
            $categoryId = $this->get_id();
327
            $gradebook = new Gradebook();
328
            $skills = $gradebook->getSkillsByGradebook($categoryId);
329
        } else {
330
            $skills = $this->skills;
331
        }
332
333
        return $skills;
334
    }
335
336
    /**
337
     * @return array
338
     */
339
    public function getSkillsForSelect()
340
    {
341
        $skills = $this->get_skills();
342
        $skill_select = [];
343
        if (!empty($skills)) {
344
            foreach ($skills as $skill) {
345
                $skill_select[$skill['id']] = $skill['name'];
346
            }
347
        }
348
349
        return $skill_select;
350
    }
351
352
    /**
353
     * Set the generate_certificates value.
354
     *
355
     * @param int $generateCertificates
356
     */
357
    public function setGenerateCertificates($generateCertificates)
358
    {
359
        $this->generateCertificates = $generateCertificates;
360
    }
361
362
    /**
363
     * Get the generate_certificates value.
364
     *
365
     * @return int
366
     */
367
    public function getGenerateCertificates()
368
    {
369
        return $this->generateCertificates;
370
    }
371
372
    /**
373
     * @param int $id
374
     * @param int $session_id
375
     *
376
     * @return array
377
     */
378
    public static function loadSessionCategories(
379
        $id = null,
380
        $session_id = null
381
    ) {
382
        if (isset($id) && 0 === (int) $id) {
383
            $cats = [];
384
            $cats[] = self::create_root_category();
385
386
            return $cats;
387
        }
388
        $courseId = api_get_course_int_id();
389
        $session_id = (int) $session_id;
390
391
        if (!empty($session_id)) {
392
            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
393
            $sql = 'SELECT id, c_id
394
                    FROM '.$table.'
395
                    WHERE session_id = '.$session_id;
396
            $result_session = Database::query($sql);
397
            if (Database::num_rows($result_session) > 0) {
398
                $categoryList = [];
399
                while ($data_session = Database::fetch_array($result_session)) {
400
                    $parent_id = $data_session['id'];
401
                    if ($data_session['c_id'] == $courseId) {
402
                        $categories = self::load($parent_id);
403
                        $categoryList = array_merge($categoryList, $categories);
404
                    }
405
                }
406
407
                return $categoryList;
408
            }
409
        }
410
    }
411
412
    /**
413
     * Retrieve categories and return them as an array of Category objects.
414
     *
415
     * @param ?int  $id category id
416
     * @param ?int  $user_id (category owner)
417
     * @param ?int  $courseId course id (int)
418
     * @param ?int  $parent_id parent category
419
     * @param ?int  $visible 0 or 1
420
     * @param ?int  $session_id (in case we are in a session)
421
     * @param ?bool $order_by Whether to show all "session"
422
     *                            categories (true) or hide them (false) in case there is no session id
423
     *
424
     * @return array<static>
425
     * @throws \Doctrine\DBAL\Exception
426
     * @throws Exception
427
     */
428
    public static function load(
429
        ?int $id = null,
430
        ?int $user_id = null,
431
        ?int $courseId = 0,
432
        ?int $parent_id = null,
433
        ?int $visible = null,
434
        ?int $session_id = null,
435
        ?string $order_by = null
436
    ): array {
437
        //if the category given is explicitly 0 (not null), then create
438
        // a root category object (in memory)
439
        if (isset($id) && 0 === $id) {
440
            $cats = [];
441
            $cats[] = self::create_root_category();
442
443
            return $cats;
444
        }
445
446
        $bond = ' WHERE';
447
448
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
449
        $sql = 'SELECT * FROM '.$table;
450
        if (!empty($id)) {
451
            $sql .= ' WHERE id = '.$id;
452
            $bond = ' AND';
453
        }
454
455
        if (isset($user_id)) {
456
            $sql .= $bond.' user_id = '.$user_id;
457
            $bond = ' AND';
458
        }
459
460
461
462
        if (!empty($courseId)) {
463
            $sql .= $bond." c_id = $courseId";
464
            $bond = ' AND';
465
        }
466
467
        if (!isset($session_id)) {
468
            $session_id = api_get_session_id();
469
        }
470
471
        if (!empty($session_id)) {
472
            $sql .= $bond.' session_id = '.$session_id;
473
        } else {
474
            if (empty($id)) {
475
                $sql .= $bond.' (session_id IS NULL OR session_id = 0) ';
476
            }
477
        }
478
        $bond = ' AND';
479
480
        if (!empty($parent_id)) {
481
            $sql .= $bond.' parent_id = '.$parent_id;
482
        }
483
484
        if (isset($visible)) {
485
            $sql .= $bond.' visible = '.$visible;
486
        }
487
488
        if (isset($order_by)) {
489
            $sql .= ' '.Database::escape_string($order_by);
490
        }
491
492
        $result = Database::query($sql);
493
        $categories = [];
494
        if (Database::num_rows($result) > 0) {
495
            $categories = self::create_category_objects_from_sql_result($result);
496
        }
497
498
        return $categories;
499
    }
500
501
    /**
502
     * Insert this category into the database.
503
     * @throws \Doctrine\ORM\Exception\NotSupported
504
     */
505
    public function add()
506
    {
507
        if (isset($this->name) && '-1' == $this->name) {
508
            return false;
509
        }
510
511
        if (isset($this->name) && isset($this->user_id)) {
512
            $em = Database::getManager();
513
514
            $course = api_get_course_entity($this->courseId);
515
            $parent = null;
516
            if (!empty($this->parent)) {
517
                $parent = $em->getRepository(GradebookCategory::class)->find($this->parent);
518
            }
519
520
            $category = new GradebookCategory();
521
            $category->setTitle($this->name);
522
            $category->setDescription($this->description);
523
            $userId = is_numeric($this->user_id) ? (int) $this->user_id : api_get_user_id();
524
            $category->setUser(api_get_user_entity($userId));
525
            $category->setUser(api_get_user_entity($this->user_id));
526
            $category->setCourse($course);
527
            $category->setParent($parent);
528
            $category->setWeight(api_float_val($this->weight));
529
            $category->setVisible($this->visible ? true : false);
530
            $category->setCertifMinScore($this->certificate_min_score);
531
            $category->setSession(api_get_session_entity($this->session_id));
532
            $category->setGenerateCertificates($this->generateCertificates);
533
            if (!empty($this->grade_model_id)) {
534
                $model = $em->getRepository(\Chamilo\CoreBundle\Entity\GradeModel::class)->find($this->grade_model_id);
535
                $category->setGradeModel($model);
536
            }
537
538
            $category->setIsRequirement($this->isRequirement);
539
            $category->setLocked(0);
540
541
            $em->persist($category);
542
            $em->flush();
543
544
            $id = $category->getId();
545
            $this->set_id($id);
546
547
            if (!empty($id)) {
548
                $parent_id = $this->get_parent_id();
549
                $grade_model_id = $this->get_grade_model_id();
550
                if (0 == $parent_id) {
551
                    //do something
552
                    if (isset($grade_model_id) &&
553
                        !empty($grade_model_id) &&
554
                        '-1' != $grade_model_id
555
                    ) {
556
                        $obj = new GradeModel();
557
                        $components = $obj->get_components($grade_model_id);
558
                        $default_weight_setting = api_get_setting('gradebook_default_weight');
559
                        $default_weight = 100;
560
                        if (isset($default_weight_setting)) {
561
                            $default_weight = $default_weight_setting;
562
                        }
563
                        foreach ($components as $component) {
564
                            $gradebook = new Gradebook();
565
                            $params = [];
566
567
                            $params['name'] = $component['acronym'];
568
                            $params['description'] = $component['title'];
569
                            $params['user_id'] = api_get_user_id();
570
                            $params['parent_id'] = $id;
571
                            $params['weight'] = $component['percentage'] / 100 * $default_weight;
572
                            $params['session_id'] = api_get_session_id();
573
                            $params['c_id'] = $this->getCourseId();
574
575
                            $gradebook->save($params);
576
                        }
577
                    }
578
                }
579
            }
580
581
            $gradebook = new Gradebook();
582
            $gradebook->updateSkillsToGradeBook(
583
                $this->id,
584
                $this->get_skills(false)
585
            );
586
587
            return $id;
588
        }
589
    }
590
591
    /**
592
     * Update the properties of this category in the database.
593
     *
594
     * @throws \Doctrine\ORM\Exception\NotSupported
595
     * @throws \Doctrine\ORM\Exception\ORMException
596
     * @todo fix me
597
     */
598
    public function save()
599
    {
600
        $em = Database::getManager();
601
        $repo = $em->getRepository(GradebookCategory::class);
602
603
        /** @var GradebookCategory $category */
604
        $category = $repo->find($this->id);
605
606
        if (null === $category) {
607
            return false;
608
        }
609
610
        $parent = null;
611
        if (!empty($this->parent)) {
612
            $parent = $repo->find($this->parent);
613
        }
614
        $course = api_get_course_entity();
615
616
        $category->setTitle($this->name);
617
        $category->setDescription($this->description);
618
        $userId = is_numeric($this->user_id) ? (int) $this->user_id : api_get_user_id();
619
        $category->setUser(api_get_user_entity($userId));
620
        $category->setCourse($course);
621
        $category->setParent($parent);
622
        $category->setWeight($this->weight);
623
        $category->setVisible($this->visible);
624
        $category->setCertifMinScore($this->certificate_min_score);
625
        $category->setGenerateCertificates($this->generateCertificates);
626
        if (!empty($this->grade_model_id)) {
627
            $model = $em->getRepository(\Chamilo\CoreBundle\Entity\GradeModel::class)->find($this->grade_model_id);
628
            $category->setGradeModel($model);
629
        }
630
631
        $category->setIsRequirement($this->isRequirement);
632
633
        $em->persist($category);
634
        $em->flush();
635
636
        if (!empty($this->id)) {
637
            $parent_id = $this->get_parent_id();
638
            $grade_model_id = $this->get_grade_model_id();
639
            if (0 == $parent_id) {
640
                if (!empty($grade_model_id) &&
641
                    '-1' != $grade_model_id
642
                ) {
643
                    $obj = new GradeModel();
644
                    $components = $obj->get_components($grade_model_id);
645
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
646
                    $default_weight = 100;
647
                    if (isset($default_weight_setting)) {
648
                        $default_weight = $default_weight_setting;
649
                    }
650
                    $final_weight = $this->get_weight();
651
                    if (!empty($final_weight)) {
652
                        $default_weight = $this->get_weight();
653
                    }
654
                    foreach ($components as $component) {
655
                        $gradebook = new Gradebook();
656
                        $params = [];
657
                        $params['name'] = $component['acronym'];
658
                        $params['description'] = $component['title'];
659
                        $params['user_id'] = api_get_user_id();
660
                        $params['parent_id'] = $this->id;
661
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
662
                        $params['session_id'] = api_get_session_id();
663
                        $params['c_id'] = $this->getCourseId();
664
                        $gradebook->save($params);
665
                    }
666
                }
667
            }
668
        }
669
670
        $gradebook = new Gradebook();
671
        $gradebook->updateSkillsToGradeBook(
672
            $this->id,
673
            $this->get_skills(false),
674
            true
675
        );
676
    }
677
678
    /**
679
     * Update link weights see #5168.
680
     *
681
     * @param int $new_weight
682
     */
683
    public function updateChildrenWeight($new_weight)
684
    {
685
        $links = $this->get_links();
686
        $old_weight = $this->get_weight();
687
688
        if (!empty($links)) {
689
            foreach ($links as $link_item) {
690
                if (isset($link_item)) {
691
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
692
                    $link_item->set_weight($new_item_weight);
693
                    $link_item->save();
694
                }
695
            }
696
        }
697
    }
698
699
    /**
700
     * Delete this evaluation from the database.
701
     * @throws Exception
702
     */
703
    public function delete(): void
704
    {
705
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
706
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
707
        Database::query($sql);
708
    }
709
710
    /**
711
     * Return an HTML span block if the given resource has been deleted
712
     * @param ?int $courseId
713
     *
714
     * @return string
715
     * @throws \Doctrine\DBAL\Exception
716
     * @throws Exception
717
     */
718
    public static function show_message_resource_delete(?int $courseId): string
719
    {
720
        if (empty($courseId)) {
721
            return '';
722
        }
723
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
724
        $sql = "SELECT count(*) AS num
725
                FROM $table
726
                WHERE
727
                    c_id = $courseId AND
728
                    visible = 3";
729
        $res = Database::query($sql);
730
        $option = Database::fetch_array($res);
731
        if ($option['num'] >= 1) {
732
            return '&nbsp;&nbsp;<span class="resource-deleted">
733
                (&nbsp;'.get_lang('The resource has been deleted').'&nbsp;)
734
                </span>';
735
        }
736
737
        return '';
738
    }
739
740
    /**
741
     * Shows all information of a category.
742
     *
743
     * @param int $categoryId
744
     *
745
     * @return array
746
     * @throws \Doctrine\DBAL\Exception
747
     * @throws Exception
748
     */
749
    public function showAllCategoryInfo(int $categoryId): array
750
    {
751
        if (empty($categoryId)) {
752
            return [];
753
        }
754
755
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
756
        $sql = "SELECT * FROM $table
757
                WHERE id = $categoryId";
758
        $result = Database::query($sql);
759
760
        return Database::fetch_array($result);
761
    }
762
763
    /**
764
     * Checks if the certificate is available for the given user in this category.
765
     *
766
     * @param int $user_id User ID
767
     *
768
     * @return bool True if conditions match, false if fails
769
     */
770
    public function is_certificate_available($user_id)
771
    {
772
        $score = $this->calc_score(
773
            $user_id,
774
            null,
775
            $this->courseId,
776
            $this->session_id
777
        );
778
779
        if (isset($score) && isset($score[0])) {
780
            // Get a percentage score to compare to minimum certificate score
781
            // $certification_score = $score[0] / $score[1] * 100;
782
            // Get real score not a percentage.
783
            $certification_score = $score[0];
784
            if ($certification_score >= $this->certificate_min_score) {
785
                return true;
786
            }
787
        }
788
789
        return false;
790
    }
791
792
    /**
793
     * Is this category the main one in a course ?
794
     * A category is a course if it has a course code and no parent category.
795
     */
796
    public function is_course(): bool
797
    {
798
        return !empty($this->getCourseId())
799
            && (!isset($this->parent) || 0 == $this->parent);
800
    }
801
802
    /**
803
     * Calculate the score of this category.
804
     */
805
    public function calc_score(
806
        ?int $studentId = null,
807
        ?string $type = null,
808
        ?int $courseId = 0,
809
        ?int $session_id = null
810
    ): ?array {
811
        $key = 'category:'.$this->id.'student:'.(int) $studentId.'type:'.$type.'course:'.$courseId.'session:'.(int) $session_id;
812
        $useCache = ('true' === api_get_setting('gradebook.gradebook_use_apcu_cache'));
813
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
814
815
        if ($cacheAvailable) {
816
            $cache = new \Symfony\Component\Cache\Adapter\ApcuAdapter();
817
            if ($cache->hasItem($key)) {
818
                return $cache->getItem($key)->get();
819
            }
820
        }
821
        // Classic
822
        if (!empty($studentId) && '' == $type) {
823
            if (!empty($courseId)) {
824
                $cats = $this->get_subcategories(
825
                    $studentId,
826
                    $courseId,
827
                    $session_id
828
                );
829
                $evals = $this->get_evaluations($studentId, false, $courseId);
830
                $links = $this->get_links($studentId, false, $courseId);
831
            } else {
832
                $cats = $this->get_subcategories($studentId);
833
                $evals = $this->get_evaluations($studentId);
834
                $links = $this->get_links($studentId);
835
            }
836
837
            // Calculate score
838
            $count = 0;
839
            $ressum = 0;
840
            $weightsum = 0;
841
            if (!empty($cats)) {
842
                /** @var Category $cat */
843
                foreach ($cats as $cat) {
844
                    $cat->set_session_id($session_id);
845
                    $cat->setCourseId($courseId);
846
                    $cat->setStudentList($this->getStudentList());
847
                    $score = $cat->calc_score(
848
                        $studentId,
849
                        null,
850
                        $courseId,
851
                        $session_id
852
                    );
853
854
                    $catweight = 0;
855
                    if (0 != $cat->get_weight()) {
856
                        $catweight = $cat->get_weight();
857
                        $weightsum += $catweight;
858
                    }
859
860
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
861
                        $ressum += $score[0] / $score[1] * $catweight;
862
                    }
863
                }
864
            }
865
866
            if (!empty($evals)) {
867
                /** @var Evaluation $eval */
868
                foreach ($evals as $eval) {
869
                    $eval->setStudentList($this->getStudentList());
870
                    $evalres = $eval->calc_score($studentId);
871
                    if (isset($evalres) && 0 != $eval->get_weight()) {
872
                        $evalweight = $eval->get_weight();
873
                        $weightsum += $evalweight;
874
                        if (!empty($evalres[1])) {
875
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
876
                        }
877
                    } else {
878
                        if (0 != $eval->get_weight()) {
879
                            $evalweight = $eval->get_weight();
880
                            $weightsum += $evalweight;
881
                        }
882
                    }
883
                }
884
            }
885
886
            if (!empty($links)) {
887
                /** @var EvalLink|ExerciseLink $link */
888
                foreach ($links as $link) {
889
                    $link->setStudentList($this->getStudentList());
890
891
                    if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
892
                        $link->set_session_id($session_id);
893
                    }
894
895
                    $linkres = $link->calc_score($studentId, null);
896
                    if (!empty($linkres) && 0 != $link->get_weight()) {
897
                        $linkweight = $link->get_weight();
898
                        $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
899
                        $weightsum += $linkweight;
900
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
901
                    } else {
902
                        // Adding if result does not exists
903
                        if (0 != $link->get_weight()) {
904
                            $linkweight = $link->get_weight();
905
                            $weightsum += $linkweight;
906
                        }
907
                    }
908
                }
909
            }
910
        } else {
911
            if (!empty($courseId)) {
912
                $cats = $this->get_subcategories(
913
                    null,
914
                    $courseId,
915
                    $session_id
916
                );
917
                $evals = $this->get_evaluations(null, false, $courseId);
918
                $links = $this->get_links(null, false, $courseId);
919
            } else {
920
                $cats = $this->get_subcategories(null);
921
                $evals = $this->get_evaluations(null);
922
                $links = $this->get_links(null);
923
            }
924
925
            // Calculate score
926
            $ressum = 0;
927
            $weightsum = 0;
928
            $bestResult = 0;
929
            $totalScorePerStudent = [];
930
931
            if (!empty($cats)) {
932
                /** @var Category $cat */
933
                foreach ($cats as $cat) {
934
                    $cat->setStudentList($this->getStudentList());
935
                    $score = $cat->calc_score(
936
                        null,
937
                        $type,
938
                        $courseId,
939
                        $session_id
940
                    );
941
942
                    $catweight = 0;
943
                    if (0 != $cat->get_weight()) {
944
                        $catweight = $cat->get_weight();
945
                        $weightsum += $catweight;
946
                    }
947
948
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
949
                        $ressum += $score[0] / $score[1] * $catweight;
950
951
                        if ($ressum > $bestResult) {
952
                            $bestResult = $ressum;
953
                        }
954
                    }
955
                }
956
            }
957
958
            if (!empty($evals)) {
959
                if ('best' === $type) {
960
                    $studentList = $this->getStudentList();
961
                    foreach ($studentList as $student) {
962
                        $studentId = $student['user_id'];
963
                        foreach ($evals as $eval) {
964
                            $linkres = $eval->calc_score($studentId, null);
965
                            $linkweight = $eval->get_weight();
966
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
967
                            $ressum = $linkres[0] / $link_res_denom * $linkweight;
968
969
                            if (!isset($totalScorePerStudent[$studentId])) {
970
                                $totalScorePerStudent[$studentId] = 0;
971
                            }
972
                            $totalScorePerStudent[$studentId] += $ressum;
973
                        }
974
                    }
975
                } else {
976
                    /** @var Evaluation $eval */
977
                    foreach ($evals as $eval) {
978
                        $evalres = $eval->calc_score(null, $type);
979
                        $eval->setStudentList($this->getStudentList());
980
981
                        if (isset($evalres) && 0 != $eval->get_weight()) {
982
                            $evalweight = $eval->get_weight();
983
                            $weightsum += $evalweight;
984
                            if (!empty($evalres[1])) {
985
                                $ressum += $evalres[0] / $evalres[1] * $evalweight;
986
                            }
987
988
                            if ($ressum > $bestResult) {
989
                                $bestResult = $ressum;
990
                            }
991
                        } else {
992
                            if (0 != $eval->get_weight()) {
993
                                $evalweight = $eval->get_weight();
994
                                $weightsum += $evalweight;
995
                            }
996
                        }
997
                    }
998
                }
999
            }
1000
1001
            if (!empty($links)) {
1002
                $studentList = $this->getStudentList();
1003
                if ('best' === $type) {
1004
                    foreach ($studentList as $student) {
1005
                        $studentId = $student['user_id'];
1006
                        foreach ($links as $link) {
1007
                            $linkres = $link->calc_score($studentId, null);
1008
                            $linkweight = $link->get_weight();
1009
                            if ($linkres) {
1010
                                $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1011
                                $ressum = $linkres[0] / $link_res_denom * $linkweight;
1012
                            }
1013
1014
                            if (!isset($totalScorePerStudent[$studentId])) {
1015
                                $totalScorePerStudent[$studentId] = 0;
1016
                            }
1017
                            $totalScorePerStudent[$studentId] += $ressum;
1018
                        }
1019
                    }
1020
                } else {
1021
                    /** @var EvalLink|ExerciseLink $link */
1022
                    foreach ($links as $link) {
1023
                        $link->setStudentList($this->getStudentList());
1024
1025
                        if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1026
                            $link->set_session_id($session_id);
1027
                        }
1028
1029
                        $linkres = $link->calc_score($studentId, $type);
1030
1031
                        if (!empty($linkres) && 0 != $link->get_weight()) {
1032
                            $linkweight = $link->get_weight();
1033
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1034
1035
                            $weightsum += $linkweight;
1036
                            $ressum += $linkres[0] / $link_res_denom * $linkweight;
1037
                            if ($ressum > $bestResult) {
1038
                                $bestResult = $ressum;
1039
                            }
1040
                        } else {
1041
                            // Adding if result does not exist
1042
                            if (0 != $link->get_weight()) {
1043
                                $linkweight = $link->get_weight();
1044
                                $weightsum += $linkweight;
1045
                            }
1046
                        }
1047
                    }
1048
                }
1049
            }
1050
        }
1051
1052
        switch ($type) {
1053
            case 'best':
1054
                arsort($totalScorePerStudent);
1055
                $maxScore = current($totalScorePerStudent);
1056
1057
                return [$maxScore, $this->get_weight()];
1058
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
1059
            case 'average':
1060
                if (empty($ressum)) {
1061
                    if ($cacheAvailable) {
1062
                        $cacheItem = $cache->getItem($key);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cache does not seem to be defined for all execution paths leading up to this point.
Loading history...
1063
                        $cacheItem->set(null);
1064
1065
                        $cache->save($cacheItem);
1066
                    }
1067
1068
                    return null;
1069
                }
1070
1071
                if ($cacheAvailable) {
1072
                    $cacheItem = $cache->getItem($key);
1073
                    $cacheItem->set([$ressum, $weightsum]);
1074
1075
                    $cache->save($cacheItem);
1076
                }
1077
1078
                return [$ressum, $weightsum];
1079
                //break;
1080
            case 'ranking':
1081
                // category ranking is calculated in gradebook_data_generator.class.php
1082
                // function get_data
1083
                return null;
1084
1085
                //return AbstractLink::getCurrentUserRanking($studentId, []);
1086
                //break;
1087
            default:
1088
                if ($cacheAvailable) {
1089
                    $cacheItem = $cache->getItem($key);
1090
                    $cacheItem->set([$ressum, $weightsum]);
1091
1092
                    $cache->save($cacheItem);
1093
                }
1094
1095
                return [$ressum, $weightsum];
1096
        }
1097
    }
1098
1099
    /**
1100
     * Delete this category and every subcategory, evaluation and result inside.
1101
     */
1102
    public function delete_all()
1103
    {
1104
        $cats = self::load(null, null, $this->getCourseId(), $this->id, null);
1105
        $evals = Evaluation::load(
1106
            null,
1107
            null,
1108
            $this->getCourseId(),
1109
            $this->id,
1110
            null
1111
        );
1112
1113
        $links = LinkFactory::load(
1114
            null,
1115
            null,
1116
            null,
1117
            null,
1118
            $this->getCourseId(),
1119
            $this->id,
1120
            null
1121
        );
1122
1123
        if (!empty($cats)) {
1124
            /** @var Category $cat */
1125
            foreach ($cats as $cat) {
1126
                $cat->delete_all();
1127
                $cat->delete();
1128
            }
1129
        }
1130
1131
        if (!empty($evals)) {
1132
            /** @var Evaluation $eval */
1133
            foreach ($evals as $eval) {
1134
                $eval->delete_with_results();
1135
            }
1136
        }
1137
1138
        if (!empty($links)) {
1139
            /** @var AbstractLink $link */
1140
            foreach ($links as $link) {
1141
                $link->delete();
1142
            }
1143
        }
1144
1145
        $this->delete();
1146
    }
1147
1148
    /**
1149
     * Return array of Category objects where a student is subscribed to.
1150
     *
1151
     * @param int $stud_id
1152
     * @param int $courseId
1153
     * @param int $session_id
1154
     *
1155
     * @return array
1156
     * @throws \Doctrine\DBAL\Exception
1157
     */
1158
    public function get_root_categories_for_student(
1159
        int $stud_id,
1160
        int $courseId = 0,
1161
        int $session_id = 0
1162
    ) {
1163
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1164
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1165
1166
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1167
1168
        if (!api_is_allowed_to_edit()) {
1169
            $sql .= ' AND visible = 1';
1170
            //proceed with checks on optional parameters course & session
1171
            if (!empty($courseId)) {
1172
                // TODO: considering it highly improbable that a user would get here
1173
                // if he doesn't have the rights to view this course and this
1174
                // session, we don't check his registration to these, but this
1175
                // could be an improvement
1176
                if (!empty($session_id)) {
1177
                    $sql .= " AND c_id = '".$courseId."' AND session_id = ".$session_id;
1178
                } else {
1179
                    $sql .= " AND c_id = '".$courseId."' AND session_id is null OR session_id=0";
1180
                }
1181
            } else {
1182
                //no optional parameter, proceed as usual
1183
                $sql .= ' AND c_id in
1184
                     (
1185
                        SELECT c.id
1186
                        FROM '.$main_course_user_table.' cu
1187
                        WHERE cu.user_id = '.intval($stud_id).'
1188
                        AND cu.status = '.STUDENT.'
1189
                    )';
1190
            }
1191
        } elseif (!api_is_platform_admin()) {
1192
            //proceed with checks on optional parameters course & session
1193
            if (!empty($courseId)) {
1194
                // TODO: considering it highly improbable that a user would get here
1195
                // if he doesn't have the rights to view this course and this
1196
                // session, we don't check his registration to these, but this
1197
                // could be an improvement
1198
                $sql .= " AND c_id  = $courseId";
1199
                if (!empty($session_id)) {
1200
                    $sql .= " AND session_id = ".$session_id;
1201
                } else {
1202
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1203
                }
1204
            } else {
1205
                $sql .= ' AND c_id IN
1206
                     (
1207
                        SELECT c.id
1208
                        FROM '.$main_course_user_table.' cu
1209
                        WHERE
1210
                            cu.user_id = '.api_get_user_id().' AND
1211
                            cu.status = '.COURSEMANAGER.'
1212
                    )';
1213
            }
1214
        } elseif (api_is_platform_admin()) {
1215
            if (0 != $session_id) {
1216
                $sql .= ' AND session_id='.$session_id;
1217
            } else {
1218
                $sql .= ' AND coalesce(session_id,0)=0';
1219
            }
1220
        }
1221
        $result = Database::query($sql);
1222
        $cats = self::create_category_objects_from_sql_result($result);
1223
1224
        // course independent categories
1225
        if (empty($courseId)) {
1226
            $cats = $this->getIndependentCategoriesWithStudentResult(
1227
                0,
1228
                $stud_id,
1229
                $cats
1230
            );
1231
        }
1232
1233
        return $cats;
1234
    }
1235
1236
    /**
1237
     * Return array of Category objects where a teacher is admin for.
1238
     *
1239
     * @param int    $user_id (to return everything, use 'null' here)
1240
     * @param ?int   $courseId (optional)
1241
     * @param ?int    $session_id (optional)
1242
     *
1243
     * @return array
1244
     * @throws \Doctrine\DBAL\Exception
1245
     */
1246
    public function get_root_categories_for_teacher(
1247
        int $user_id,
1248
        ?int $courseId = null,
1249
        ?int $session_id = null
1250
    ) {
1251
        if (null == $user_id) {
1252
            return self::load(null, null, $courseId, 0, null, $session_id);
1253
        }
1254
1255
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1256
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1257
1258
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1259
                WHERE parent_id = 0 ';
1260
        if (!empty($courseId)) {
1261
            $sql .= " AND c_id = $courseId ";
1262
            if (!empty($session_id)) {
1263
                $sql .= " AND session_id = ".$session_id;
1264
            }
1265
        } else {
1266
            $sql .= ' AND c_id in
1267
                 (
1268
                    SELECT cu.id
1269
                    FROM '.$main_course_user_table.' cu
1270
                    WHERE user_id = '.$user_id.'
1271
                )';
1272
        }
1273
        $result = Database::query($sql);
1274
        $cats = self::create_category_objects_from_sql_result($result);
1275
        // course independent categories
1276
        if (!empty($courseId)) {
1277
            $indcats = self::load(
1278
                null,
1279
                $user_id,
1280
                $courseId,
1281
                0,
1282
                null,
1283
                $session_id
1284
            );
1285
            $cats = array_merge($cats, $indcats);
1286
        }
1287
1288
        return $cats;
1289
    }
1290
1291
    /**
1292
     * Can this category be moved to somewhere else ?
1293
     * The root and courses cannot be moved.
1294
     *
1295
     * @return bool
1296
     */
1297
    public function is_movable()
1298
    {
1299
        return !(!isset($this->id) || 0 == $this->id || $this->is_course());
1300
    }
1301
1302
    /**
1303
     * Generate an array of possible categories where this category can be moved to.
1304
     * Notice: its own parent will be included in the list: it's up to the frontend
1305
     * to disable this element.
1306
     *
1307
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1308
     */
1309
    public function get_target_categories()
1310
    {
1311
        // the root or a course -> not movable
1312
        if (!$this->is_movable()) {
1313
            return null;
1314
        } else {
1315
            // otherwise:
1316
            // - course independent category
1317
            //   -> movable to root or other independent categories
1318
            // - category inside a course
1319
            //   -> movable to root, independent categories or categories inside the course
1320
            $user = api_is_platform_admin() ? null : api_get_user_id();
1321
            $targets = [];
1322
            $level = 0;
1323
1324
            $root = [0, get_lang('Main folder'), $level];
1325
            $targets[] = $root;
1326
1327
            if (!empty($this->courseId)) {
1328
                $crscats = self::load(null, null, $this->courseId, 0);
1329
                foreach ($crscats as $cat) {
1330
                    if ($this->can_be_moved_to_cat($cat)) {
1331
                        $targets[] = [
1332
                            $cat->get_id(),
1333
                            $cat->get_name(),
1334
                            $level + 1,
1335
                        ];
1336
                        $targets = $this->addTargetSubcategories(
1337
                            $targets,
1338
                            $level + 1,
1339
                            $cat->get_id()
1340
                        );
1341
                    }
1342
                }
1343
            }
1344
1345
            $indcats = self::load(null, $user, 0, 0);
1346
            foreach ($indcats as $cat) {
1347
                if ($this->can_be_moved_to_cat($cat)) {
1348
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1349
                    $targets = $this->addTargetSubcategories(
1350
                        $targets,
1351
                        $level + 1,
1352
                        $cat->get_id()
1353
                    );
1354
                }
1355
            }
1356
1357
            return $targets;
1358
        }
1359
    }
1360
1361
    /**
1362
     * Move this category to the given category.
1363
     * If this category moves from inside a course to outside,
1364
     * its course code must be changed, as well as the course code
1365
     * of all underlying categories and evaluations. All links will
1366
     * be deleted as well !
1367
     */
1368
    public function move_to_cat($cat)
1369
    {
1370
        $this->set_parent_id($cat->get_id());
1371
        if ($this->get_course_code() != $cat->get_course_code()) {
1372
            $this->setCourseId($cat->getCourseId());
1373
            $this->applyCourseCodeToChildren();
1374
        }
1375
        $this->save();
1376
    }
1377
1378
    /**
1379
     * Generate an array of all categories the user can navigate to.
1380
     */
1381
    public function get_tree()
1382
    {
1383
        $targets = [];
1384
        $level = 0;
1385
        $root = [0, get_lang('Main folder'), $level];
1386
        $targets[] = $root;
1387
1388
        // course or platform admin
1389
        if (api_is_allowed_to_edit()) {
1390
            $user = api_is_platform_admin() ? null : api_get_user_id();
1391
            $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

1391
            /** @scrutinizer ignore-call */ 
1392
            $cats = self::get_root_categories_for_teacher($user);
Loading history...
1392
            foreach ($cats as $cat) {
1393
                $targets[] = [
1394
                    $cat->get_id(),
1395
                    $cat->get_name(),
1396
                    $level + 1,
1397
                ];
1398
                $targets = $this->add_subtree(
1399
                    $targets,
1400
                    $level + 1,
1401
                    $cat->get_id(),
1402
                    null
1403
                );
1404
            }
1405
        } else {
1406
            // student
1407
            $cats = $this->get_root_categories_for_student(api_get_user_id());
1408
            foreach ($cats as $cat) {
1409
                $targets[] = [
1410
                    $cat->get_id(),
1411
                    $cat->get_name(),
1412
                    $level + 1,
1413
                ];
1414
                $targets = $this->add_subtree(
1415
                    $targets,
1416
                    $level + 1,
1417
                    $cat->get_id(),
1418
                    1
1419
                );
1420
            }
1421
        }
1422
1423
        return $targets;
1424
    }
1425
1426
    /**
1427
     * Generate an array of courses that a teacher hasn't created a category for.
1428
     *
1429
     * @param int $user_id
1430
     *
1431
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1432
     */
1433
    public static function get_not_created_course_categories($user_id)
1434
    {
1435
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1436
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1437
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1438
1439
        $user_id = (int) $user_id;
1440
1441
        $sql = 'SELECT DISTINCT(cc.code), title
1442
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1443
                WHERE
1444
                    cc.id = cu.c_id AND
1445
                    cu.status = '.COURSEMANAGER;
1446
1447
        if (!api_is_platform_admin()) {
1448
            $sql .= ' AND cu.user_id = '.$user_id;
1449
        }
1450
        $sql .= ' AND cc.id NOT IN
1451
             (
1452
                SELECT c_id FROM '.$tbl_grade_categories.'
1453
                WHERE
1454
                    parent_id = 0 AND
1455
                    c_id IS NOT NULL
1456
                )';
1457
        $result = Database::query($sql);
1458
1459
        $cats = [];
1460
        while ($data = Database::fetch_array($result)) {
1461
            $cats[] = [$data['code'], $data['title']];
1462
        }
1463
1464
        return $cats;
1465
    }
1466
1467
    /**
1468
     * Generate an array of all courses that a teacher is admin of.
1469
     *
1470
     * @param int $user_id
1471
     *
1472
     * @return array 2-dimensional array - every element contains 2 sub-elements (code, title)
1473
     * @throws Exception
1474
     */
1475
    public static function get_all_courses(int $user_id): array
1476
    {
1477
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1478
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1479
        $sql = 'SELECT DISTINCT(code), title, cc.id
1480
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1481
                WHERE cc.id = cu.c_id AND cu.status = '.COURSEMANAGER;
1482
        if (!api_is_platform_admin()) {
1483
            $sql .= ' AND cu.user_id = '.$user_id;
1484
        }
1485
1486
        $result = Database::query($sql);
1487
        $cats = [];
1488
        while ($data = Database::fetch_array($result)) {
1489
            $cats[] = [$data['code'], $data['title']];
1490
        }
1491
1492
        return $cats;
1493
    }
1494
1495
    /**
1496
     * Apply the same visibility to every subcategory, evaluation and link.
1497
     */
1498
    public function apply_visibility_to_children()
1499
    {
1500
        $cats = self::load(null, null, 0, $this->id, null);
1501
        $evals = Evaluation::load(null, null, null, $this->id, null);
1502
        $links = LinkFactory::load(
1503
            null,
1504
            null,
1505
            null,
1506
            null,
1507
            null,
1508
            $this->id,
1509
            null
1510
        );
1511
        if (!empty($cats)) {
1512
            foreach ($cats as $cat) {
1513
                $cat->set_visible($this->is_visible());
1514
                $cat->save();
1515
                $cat->apply_visibility_to_children();
1516
            }
1517
        }
1518
        if (!empty($evals)) {
1519
            foreach ($evals as $eval) {
1520
                $eval->set_visible($this->is_visible());
1521
                $eval->save();
1522
            }
1523
        }
1524
        if (!empty($links)) {
1525
            foreach ($links as $link) {
1526
                $link->set_visible($this->is_visible());
1527
                $link->save();
1528
            }
1529
        }
1530
    }
1531
1532
    /**
1533
     * Check if a category contains evaluations with a result for a given student.
1534
     *
1535
     * @param int $studentId
1536
     *
1537
     * @return bool
1538
     */
1539
    public function hasEvaluationsWithStudentResults($studentId)
1540
    {
1541
        $evals = Evaluation::get_evaluations_with_result_for_student(
1542
            $studentId,
1543
            $this->id
1544
        );
1545
        if (0 != count($evals)) {
1546
            return true;
1547
        } else {
1548
            $cats = self::load(
1549
                null,
1550
                null,
1551
                0,
1552
                $this->id,
1553
                api_is_allowed_to_edit() ? null : 1
1554
            );
1555
1556
            /** @var Category $cat */
1557
            foreach ($cats as $cat) {
1558
                if ($cat->hasEvaluationsWithStudentResults($studentId)) {
1559
                    return true;
1560
                }
1561
            }
1562
1563
            return false;
1564
        }
1565
    }
1566
1567
    /**
1568
     * Retrieve all categories inside a course independent category
1569
     * that should be visible to a student.
1570
     *
1571
     * @param int   $categoryId parent category
1572
     * @param int   $studentId
1573
     * @param array $cats       optional: if defined, the categories will be added to this array
1574
     *
1575
     * @return array
1576
     */
1577
    public function getIndependentCategoriesWithStudentResult(
1578
        $categoryId,
1579
        $studentId,
1580
        $cats = []
1581
    ) {
1582
        $creator = api_is_allowed_to_edit() && !api_is_platform_admin() ? api_get_user_id() : null;
1583
1584
        $categories = self::load(
1585
            null,
1586
            $creator,
1587
            0,
1588
            $categoryId,
1589
            api_is_allowed_to_edit() ? null : 1
1590
        );
1591
1592
        if (!empty($categories)) {
1593
            /** @var Category $category */
1594
            foreach ($categories as $category) {
1595
                if ($category->hasEvaluationsWithStudentResults($studentId)) {
1596
                    $cats[] = $category;
1597
                }
1598
            }
1599
        }
1600
1601
        return $cats;
1602
    }
1603
1604
    /**
1605
     * Return the session id (in any case, even if it's null or 0).
1606
     *
1607
     * @return int Session id (can be null)
1608
     */
1609
    public function get_session_id()
1610
    {
1611
        return $this->session_id;
1612
    }
1613
1614
    /**
1615
     * Get appropriate subcategories visible for the user (and optionally the course and session).
1616
     *
1617
     * @param ?int   $studentId student id (default: all students)
1618
     * @param ?int  $courseId Course code (optional)
1619
     * @param ?int  $session_id Session ID (optional)
1620
     * @param ?string $order A sorting string like 'ORDER BY id'
1621
     *
1622
     * @return array Array of subcategories
1623
     * @throws \Doctrine\DBAL\Exception
1624
     */
1625
    public function get_subcategories(
1626
        ?int $studentId = null,
1627
        ?int $courseId = 0,
1628
        ?int $session_id = 0,
1629
        ?string $order = null
1630
    ): array {
1631
        // 1 student
1632
        if (isset($studentId)) {
1633
            // Special case: this is the root
1634
            if (0 == $this->id) {
1635
                return $this->get_root_categories_for_student($studentId, $courseId, $session_id);
1636
            } else {
1637
                return self::load(
1638
                    null,
1639
                    null,
1640
                    $courseId,
1641
                    $this->id,
1642
                    api_is_allowed_to_edit() ? null : 1,
1643
                    $session_id,
1644
                    $order
1645
                );
1646
            }
1647
        } else {
1648
            // All students
1649
            // Course admin
1650
            if (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1651
1652
                // root
1653
                if (0 == $this->id) {
1654
1655
                    // inside a course
1656
                    return $this->get_root_categories_for_teacher(
1657
                        api_get_user_id(),
1658
                        $courseId,
1659
                        $session_id
1660
                    );
1661
                } elseif (!empty($this->courseId)) {
1662
1663
                    return self::load(
1664
                        null,
1665
                        null,
1666
                        $this->courseId,
1667
                        $this->id,
1668
                        null,
1669
                        $session_id,
1670
                        $order
1671
                    );
1672
                } elseif (!empty($courseId)) {
1673
1674
                    // course independent
1675
                    return self::load(
1676
                        null,
1677
                        null,
1678
                        $courseId,
1679
                        $this->id,
1680
                        null,
1681
                        $session_id,
1682
                        $order
1683
                    );
1684
                } else {
1685
                    return self::load(
1686
                        null,
1687
                        api_get_user_id(),
1688
                        0,
1689
                        $this->id,
1690
                        null
1691
                    );
1692
                }
1693
            } elseif (api_is_platform_admin()) {
1694
                // platform admin
1695
                // we explicitly avoid listing subcats from another session
1696
                return self::load(
1697
                    null,
1698
                    null,
1699
                    $courseId,
1700
                    $this->id,
1701
                    null,
1702
                    $session_id,
1703
                    $order
1704
                );
1705
            }
1706
        }
1707
1708
        return [];
1709
    }
1710
1711
    /**
1712
     * Get appropriate evaluations visible for the user.
1713
     *
1714
     * @param ?int  $studentId student id (default: all students)
1715
     * @param ?bool $recursive process subcategories (default: no recursion)
1716
     * @param ?int  $courseId
1717
     * @param ?int  $sessionId
1718
     *
1719
     * @return array
1720
     * @throws \Doctrine\DBAL\Exception
1721
     */
1722
    public function get_evaluations(
1723
        ?int $studentId = null,
1724
        ?bool $recursive = false,
1725
        ?int $courseId = 0,
1726
        ?int $sessionId = 0
1727
    ): array
1728
    {
1729
        $evals = [];
1730
        $courseId = empty($courseId) ? $this->getCourseId() : $courseId;
1731
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1732
1733
        // 1 student
1734
        if (!empty($studentId)) {
1735
            // Special case: this is the root
1736
            if (0 == $this->id) {
1737
                $evals = Evaluation::get_evaluations_with_result_for_student(
1738
                    $studentId,
1739
                    0
1740
                );
1741
            } else {
1742
                $evals = Evaluation::load(
1743
                    null,
1744
                    null,
1745
                    $courseId,
1746
                    $this->id,
1747
                    api_is_allowed_to_edit() ? null : 1
1748
                );
1749
            }
1750
        } else {
1751
            // All students
1752
            // course admin
1753
            if ((api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) &&
1754
                !api_is_platform_admin()
1755
            ) {
1756
                // root
1757
                if (0 == $this->id) {
1758
                    $evals = Evaluation::load(
1759
                        null,
1760
                        api_get_user_id(),
1761
                        null,
1762
                        $this->id,
1763
                        null
1764
                    );
1765
                } elseif (!empty($this->courseId)) {
1766
                    // inside a course
1767
                    $evals = Evaluation::load(
1768
                        null,
1769
                        null,
1770
                        $courseId,
1771
                        $this->id,
1772
                        null
1773
                    );
1774
                } else {
1775
                    // course independent
1776
                    $evals = Evaluation::load(
1777
                        null,
1778
                        api_get_user_id(),
1779
                        null,
1780
                        $this->id,
1781
                        null
1782
                    );
1783
                }
1784
            } else {
1785
                $evals = Evaluation::load(
1786
                    null,
1787
                    null,
1788
                    $courseId,
1789
                    $this->id,
1790
                    null
1791
                );
1792
            }
1793
        }
1794
1795
        if ($recursive) {
1796
            $subcats = $this->get_subcategories(
1797
                $studentId,
1798
                $courseId,
1799
                $sessionId
1800
            );
1801
1802
            if (!empty($subcats)) {
1803
                foreach ($subcats as $subcat) {
1804
                    /* @var Category $subcat */
1805
                    $subevals = $subcat->get_evaluations(
1806
                        $studentId,
1807
                        true,
1808
                        $courseId
1809
                    );
1810
                    $evals = array_merge($evals, $subevals);
1811
                }
1812
            }
1813
        }
1814
1815
        return $evals;
1816
    }
1817
1818
    /**
1819
     * Get appropriate links visible for the user.
1820
     *
1821
     * @param ?int    $studentId   student id (default: all students)
1822
     * @param ?bool   $recursive   process subcategories (default: no recursion)
1823
     * @param ?int $courseId
1824
     * @param ?int    $sessionId
1825
     *
1826
     * @return array
1827
     */
1828
    public function get_links(
1829
        ?int $studentId = null,
1830
        ?bool $recursive = false,
1831
        ?int $courseId = 0,
1832
        ?int $sessionId = 0
1833
    ): array
1834
    {
1835
        $links = [];
1836
        $courseId = empty($courseId) ? $this->getCourseId() : $courseId;
1837
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1838
1839
        // no links in root or course independent categories
1840
        if (0 == $this->id) {
1841
        } elseif (isset($studentId)) {
1842
            // 1 student $studentId
1843
            $links = LinkFactory::load(
1844
                null,
1845
                null,
1846
                null,
1847
                null,
1848
                empty($courseId) ? null : $courseId,
1849
                $this->id,
1850
                api_is_allowed_to_edit() ? null : 1
1851
            );
1852
        } else {
1853
            // All students -> only for course/platform admin
1854
            $links = LinkFactory::load(
1855
                null,
1856
                null,
1857
                null,
1858
                null,
1859
                empty($courseId) ? null : $courseId,
1860
                $this->id,
1861
                null
1862
            );
1863
        }
1864
1865
        if ($recursive) {
1866
            $subcats = $this->get_subcategories(
1867
                $studentId,
1868
                $courseId,
1869
                $sessionId
1870
            );
1871
            if (!empty($subcats)) {
1872
                /** @var Category $subcat */
1873
                foreach ($subcats as $subcat) {
1874
                    $sublinks = $subcat->get_links(
1875
                        $studentId,
1876
                        false,
1877
                        $courseId,
1878
                        $sessionId
1879
                    );
1880
                    $links = array_merge($links, $sublinks);
1881
                }
1882
            }
1883
        }
1884
1885
        return $links;
1886
    }
1887
1888
    /**
1889
     * Get all the categories from with the same given direct parent.
1890
     *
1891
     * @param int $catId Category parent ID
1892
     *
1893
     * @return array Array of Category objects
1894
     */
1895
    public function getCategories($catId)
1896
    {
1897
        $catId = (int) $catId;
1898
        $tblGradeCategories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1899
        $sql = 'SELECT * FROM '.$tblGradeCategories.'
1900
                WHERE parent_id = '.$catId;
1901
1902
        $result = Database::query($sql);
1903
        $categories = self::create_category_objects_from_sql_result($result);
1904
1905
        return $categories;
1906
    }
1907
1908
    /**
1909
     * Gets the type for the current object.
1910
     *
1911
     * @return string 'C' to represent "Category" object type
1912
     */
1913
    public function get_item_type()
1914
    {
1915
        return 'C';
1916
    }
1917
1918
    /**
1919
     * @param array $skills
1920
     */
1921
    public function set_skills($skills)
1922
    {
1923
        $this->skills = $skills;
1924
    }
1925
1926
    public function get_date()
1927
    {
1928
        return null;
1929
    }
1930
1931
    /**
1932
     * @return string
1933
     */
1934
    public function get_icon_name()
1935
    {
1936
        return 'cat';
1937
    }
1938
1939
    /**
1940
     * Find category by name.
1941
     *
1942
     * @param string $name_mask search string
1943
     *
1944
     * @return array category objects matching the search criterium
1945
     */
1946
    public static function find_category($name_mask, $allcat)
1947
    {
1948
        $categories = [];
1949
        foreach ($allcat as $search_cat) {
1950
            if (!(false === strpos(strtolower($search_cat->get_name()), strtolower($name_mask)))) {
1951
                $categories[] = $search_cat;
1952
            }
1953
        }
1954
1955
        return $categories;
1956
    }
1957
1958
    /**
1959
     * This function, locks a category , only one who can unlock it is
1960
     * the platform administrator.
1961
     *
1962
     * @param int $locked locked = 1, unlocked = 0
1963
     *
1964
     * @return void
1965
     *
1966
     * @throws Exception
1967
     */
1968
    public function lock(int $locked): void
1969
    {
1970
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1971
        $sql = "UPDATE $table SET locked = '".intval($locked)."'
1972
                WHERE id = ".$this->id;
1973
        Database::query($sql);
1974
    }
1975
1976
    /**
1977
     * @param $locked
1978
     * @throws \Doctrine\ORM\Exception\ORMException
1979
     * @throws Exception
1980
     */
1981
    public function lockAllItems($locked)
1982
    {
1983
        if ('true' == api_get_setting('gradebook_locking_enabled')) {
1984
            $this->lock($locked);
1985
            $evals_to_lock = $this->get_evaluations();
1986
            if (!empty($evals_to_lock)) {
1987
                foreach ($evals_to_lock as $item) {
1988
                    $item->lock($locked);
1989
                }
1990
            }
1991
1992
            $link_to_lock = $this->get_links();
1993
            if (!empty($link_to_lock)) {
1994
                foreach ($link_to_lock as $item) {
1995
                    $item->lock($locked);
1996
                }
1997
            }
1998
1999
            $event_type = LOG_GRADEBOOK_UNLOCKED;
2000
            if (1 == $locked) {
2001
                $event_type = LOG_GRADEBOOK_LOCKED;
2002
            }
2003
            Event::addEvent($event_type, LOG_GRADEBOOK_ID, $this->id);
0 ignored issues
show
Bug introduced by
The method addEvent() does not exist on Event. ( Ignorable by Annotation )

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

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

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2004
        }
2005
    }
2006
2007
    /**
2008
     * Generates a certificate for this user if everything matches.
2009
     */
2010
    public static function generateUserCertificate(
2011
        GradebookCategory $category,
2012
        int               $user_id,
2013
        bool              $sendNotification = false,
2014
        bool $skipGenerationIfExists = false
2015
    ) {
2016
        $user_id = (int) $user_id;
2017
        $categoryId = $category->getId();
2018
        $sessionId = $category->getSession() ? $category->getSession()->getId() : 0;
2019
        $courseId = $category->getCourse()->getId();
2020
2021
        // check if all min_score requirements are met
2022
        if (!self::userMeetsMinimumScores($user_id, $category)) {
2023
            return false; // Do not generate certificate if the user does not meet all min_score criteria
2024
        }
2025
2026
        $skillToolEnabled = SkillModel::hasAccessToUserSkill(api_get_user_id(), $user_id);
2027
        $userHasSkills = false;
2028
        if ($skillToolEnabled) {
2029
            $skill = new SkillModel();
2030
            $skill->addSkillToUser($user_id, $category, $courseId, $sessionId);
2031
            $objSkillRelUser = new SkillRelUserModel();
2032
            $userSkills = $objSkillRelUser->getUserSkills($user_id, $courseId, $sessionId);
2033
            $userHasSkills = !empty($userSkills);
2034
        }
2035
2036
        // If certificate generation is disabled, return only badge link (if available)
2037
        if (empty($category->getGenerateCertificates())) {
2038
            if ($userHasSkills) {
2039
                return [
2040
                    'badge_link' => Display::toolbarButton(
2041
                        get_lang('Export badges'),
2042
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2043
                        'open-in-new'
2044
                    ),
2045
                ];
2046
            }
2047
2048
            return false;
2049
        }
2050
        $my_certificate = GradebookUtils::get_certificate_by_user_id($categoryId, $user_id);
2051
2052
        // If certificate already exists and we should skip regeneration, return false
2053
        if ($skipGenerationIfExists && !empty($my_certificate)) {
2054
            return false;
2055
        }
2056
2057
        $categoryLegacy = self::load($categoryId);
2058
        $categoryLegacy = $categoryLegacy[0];
2059
2060
        /** @var Category $categoryLegacy */
2061
        $totalScore = $categoryLegacy->calc_score($user_id);
2062
2063
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2064
        // file load this variable as a global
2065
        $scoredisplay = ScoreDisplay::instance();
2066
        $my_score_in_gradebook = $scoredisplay->display_score($totalScore, SCORE_SIMPLE);
2067
2068
        if (empty($my_certificate)) {
2069
            GradebookUtils::registerUserInfoAboutCertificate(
2070
                $categoryId,
2071
                $user_id,
2072
                $my_score_in_gradebook,
2073
                api_get_utc_datetime()
2074
            );
2075
            $my_certificate = GradebookUtils::get_certificate_by_user_id($categoryId, $user_id);
2076
        }
2077
2078
        $html = [];
2079
        if (!empty($my_certificate)) {
2080
            $pathToCertificate = $category->getDocument()->getResourceNode()->getResourceFiles()->first()->getFile()->getPathname();
2081
2082
            $certificate_obj = new Certificate(
2083
                $my_certificate['id'],
2084
                0,
2085
                $sendNotification,
2086
                true,
2087
                $pathToCertificate
2088
            );
2089
2090
            $fileWasGenerated = $certificate_obj->isHtmlFileGenerated();
2091
2092
            // Fix when using a custom certificate plugin
2093
            if ('true' === api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate')) {
2094
                $infoCertificate = CustomCertificatePlugin::getCertificateData($my_certificate['id'], $user_id);
2095
                if (!empty($infoCertificate)) {
2096
                    $fileWasGenerated = true;
2097
                }
2098
            }
2099
2100
            if (!empty($fileWasGenerated)) {
2101
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'].'&user_id='.$user_id;
2102
                $certificates = Display::toolbarButton(
2103
                    get_lang('Display certificate'),
2104
                    $url,
2105
                    'eye',
2106
                    'primary',
2107
                    ['target' => '_blank']
2108
                );
2109
2110
                $exportToPDF = Display::url(
2111
                    Display::getMdiIcon(ActionIcon::EXPORT_PDF, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Export to PDF')),
2112
                    "$url&action=export"
2113
                );
2114
2115
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2116
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2117
                if ('true' === $hideExportLink || (api_is_student() && 'true' === $hideExportLinkStudent)) {
2118
                    $exportToPDF = null;
2119
                }
2120
2121
                $html = [
2122
                    'certificate_link' => $certificates,
2123
                    'pdf_link' => $exportToPDF,
2124
                    'pdf_url' => "$url&action=export",
2125
                ];
2126
            }
2127
2128
            if ($skillToolEnabled && $userHasSkills) {
2129
                $html['badge_link'] = Display::toolbarButton(
2130
                    get_lang('Export badges'),
2131
                    api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2132
                    'open-in-new'
2133
                );
2134
            }
2135
2136
            return $html;
2137
        }
2138
2139
        return false;
2140
    }
2141
2142
    /**
2143
     * Checks whether the user has met the minimum score (`min_score`) in all required evaluations.
2144
     */
2145
    public static function userMeetsMinimumScores(int $userId, GradebookCategory $category): bool
2146
    {
2147
        $evaluations = $category->getEvaluations();
2148
2149
        foreach ($evaluations as $evaluation) {
2150
            $minScore = $evaluation->getMinScore();
2151
            if ($minScore !== null) {
2152
                $userScore = self::getUserScoreForEvaluation($userId, $evaluation->getId());
2153
                if ($userScore === null || $userScore < $minScore) {
2154
                    return false; // If at least one evaluation is below `min_score`, return false
2155
                }
2156
            }
2157
        }
2158
2159
        return true;
2160
    }
2161
2162
    /**
2163
     * Retrieves the score of a user for a specific evaluation using the GradebookResult repository.
2164
     */
2165
    public static function getUserScoreForEvaluation(int $userId, int $evaluationId): ?float
2166
    {
2167
        $gradebookResultRepo = Container::getGradebookResultRepository();
2168
2169
        $gradebookResult = $gradebookResultRepo->findOneBy([
2170
            'user' => $userId,
2171
            'evaluation' => $evaluationId,
2172
        ]);
2173
2174
        return $gradebookResult ? $gradebookResult->getScore() : null;
2175
    }
2176
2177
    /**
2178
     * @param int   $catId
2179
     * @param array $userList
2180
     */
2181
    public static function exportAllCertificates($catId, $userList = [])
2182
    {
2183
        $orientation = api_get_setting('document.certificate_pdf_orientation');
2184
2185
        $params['orientation'] = 'landscape';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
2186
        if (!empty($orientation)) {
2187
            $params['orientation'] = $orientation;
2188
        }
2189
2190
        $params['left'] = 0;
2191
        $params['right'] = 0;
2192
        $params['top'] = 0;
2193
        $params['bottom'] = 0;
2194
        $page_format = 'landscape' == $params['orientation'] ? 'A4-L' : 'A4';
2195
        $pdf = new PDF($page_format, $params['orientation'], $params);
2196
        if ('true' === api_get_setting('certificate.add_certificate_pdf_footer')) {
2197
            $pdf->setCertificateFooter();
2198
        }
2199
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2200
        $certificate_path_list = [];
2201
2202
        if (!empty($certificate_list)) {
2203
            foreach ($certificate_list as $index => $value) {
2204
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2205
                    $value['user_id'],
2206
                    $catId
2207
                );
2208
                foreach ($list_certificate as $value_certificate) {
2209
                    $certificate_obj = new Certificate($value_certificate['id']);
2210
                    $certificate_obj->generate(['hide_print_button' => true]);
2211
                    if ($certificate_obj->isHtmlFileGenerated()) {
2212
                        $certificate_path_list[] = $certificate_obj->html_file;
2213
                    }
2214
                }
2215
            }
2216
        }
2217
2218
        if (!empty($certificate_path_list)) {
2219
            // Print certificates (without the common header/footer/watermark
2220
            //  stuff) and return as one multiple-pages PDF
2221
            $pdf->html_to_pdf(
2222
                $certificate_path_list,
2223
                get_lang('Certificates'),
2224
                null,
2225
                false,
2226
                false
2227
            );
2228
        }
2229
    }
2230
2231
    /**
2232
     * @param int $catId
2233
     */
2234
    public static function deleteAllCertificates($catId)
2235
    {
2236
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2237
        if (!empty($certificate_list)) {
2238
            foreach ($certificate_list as $index => $value) {
2239
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2240
                    $value['user_id'],
2241
                    $catId
2242
                );
2243
                foreach ($list_certificate as $value_certificate) {
2244
                    $certificate_obj = new Certificate($value_certificate['id']);
2245
                    $certificate_obj->delete(true);
2246
                }
2247
            }
2248
        }
2249
    }
2250
2251
    /**
2252
     * Check whether a user has finished a course by its gradebook.
2253
     */
2254
    public static function userFinishedCourse(
2255
        int $userId,
2256
        GradebookCategory $category,
2257
        bool $recalculateScore = false,
2258
        ?int $courseId = null,
2259
        ?int $sessionId = null
2260
    ): bool {
2261
        $currentScore = self::getCurrentScore(
2262
            $userId,
2263
            $category,
2264
            $recalculateScore,
2265
            $courseId,
2266
            $sessionId
2267
        );
2268
2269
        $minCertificateScore = $category->getCertifMinScore();
2270
2271
        return $currentScore >= $minCertificateScore;
2272
    }
2273
2274
    /**
2275
     * Get the current score (as percentage) on a gradebook category for a user.
2276
     */
2277
    public static function getCurrentScore(
2278
        int               $userId,
2279
        GradebookCategory $category,
2280
        bool              $recalculate = false,
2281
        ?int              $courseId = null,
2282
        ?int              $sessionId = null
2283
    ): float|int {
2284
2285
        if ($recalculate) {
2286
            return self::calculateCurrentScore(
2287
                $userId,
2288
                $category,
2289
                $courseId,
2290
                $sessionId
2291
            );
2292
        }
2293
2294
        $resultData = Database::select(
2295
            '*',
2296
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2297
            [
2298
                'where' => [
2299
                    'category_id = ? AND user_id = ?' => [$category->getId(), $userId],
2300
                ],
2301
                'order' => 'registered_at DESC',
2302
                'limit' => '1',
2303
            ],
2304
            'first'
2305
        );
2306
2307
        if (empty($resultData)) {
2308
            return 0;
2309
        }
2310
2311
        return $resultData['score'];
2312
    }
2313
2314
    /**
2315
     * Register the current score for a user on a category gradebook.
2316
     *
2317
     * @param float $score      The achieved score
2318
     * @param int   $userId     The user id
2319
     * @param int   $categoryId The gradebook category
2320
     *
2321
     * @return int The insert id
2322
     */
2323
    public static function registerCurrentScore($score, $userId, $categoryId)
2324
    {
2325
        return Database::insert(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::insert(...pi_get_utc_datetime())) could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

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

Loading history...
2326
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2327
            [
2328
                'category_id' => intval($categoryId),
2329
                'user_id' => intval($userId),
2330
                'score' => api_float_val($score),
2331
                'registered_at' => api_get_utc_datetime(),
2332
            ]
2333
        );
2334
    }
2335
2336
    /**
2337
     * @return array
2338
     */
2339
    public function getStudentList()
2340
    {
2341
        return $this->studentList;
2342
    }
2343
2344
    /**
2345
     * @param array $list
2346
     */
2347
    public function setStudentList($list)
2348
    {
2349
        $this->studentList = $list;
2350
    }
2351
2352
    /**
2353
     * @return string
2354
     */
2355
    public static function getUrl()
2356
    {
2357
        $url = Session::read('gradebook_dest');
2358
        if (empty($url)) {
2359
            // We guess the link
2360
            $courseInfo = api_get_course_info();
2361
            if (!empty($courseInfo)) {
2362
                return api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2363
            } else {
2364
                return api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2365
            }
2366
        }
2367
2368
        return $url;
2369
    }
2370
2371
    /**
2372
     * Destination is index.php or gradebook.php.
2373
     *
2374
     * @param string $url
2375
     */
2376
    public static function setUrl($url)
2377
    {
2378
        switch ($url) {
2379
            case 'gradebook.php':
2380
                $url = api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2381
                break;
2382
            case 'index.php':
2383
                $url = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2384
                break;
2385
        }
2386
        Session::write('gradebook_dest', $url);
2387
    }
2388
2389
    /**
2390
     * @return int
2391
     */
2392
    public function getGradeBooksToValidateInDependence()
2393
    {
2394
        return $this->gradeBooksToValidateInDependence;
2395
    }
2396
2397
    /**
2398
     * @param int $value
2399
     *
2400
     * @return Category
2401
     */
2402
    public function setGradeBooksToValidateInDependence($value)
2403
    {
2404
        $this->gradeBooksToValidateInDependence = $value;
2405
2406
        return $this;
2407
    }
2408
2409
    /**
2410
     * Return HTML code with links to download and view certificate.
2411
     *
2412
     * @return string
2413
     */
2414
    public static function getDownloadCertificateBlock(array $certificate)
2415
    {
2416
        if (!isset($certificate['pdf_url'])) {
2417
            return '';
2418
        }
2419
2420
        $downloadLink = Display::toolbarButton(
2421
            get_lang('Download certificate in PDF'),
2422
            $certificate['pdf_url'],
2423
            'file-pdf-box'
2424
        );
2425
        $viewLink = $certificate['certificate_link'];
2426
2427
        return "
2428
            <div class='panel panel-default'>
2429
                <div class='panel-body'>
2430
                    <h3 class='text-center'>".get_lang('You can now download your certificate by clicking here')."</h3>
2431
                    <div class='text-center'>$downloadLink $viewLink</div>
2432
                </div>
2433
            </div>
2434
        ";
2435
    }
2436
2437
    /**
2438
     * Find a gradebook category by the certificate ID.
2439
     *
2440
     * @param int $id certificate id
2441
     *
2442
     * @throws \Doctrine\ORM\NonUniqueResultException
2443
     *
2444
     * @return Category|null
2445
     */
2446
    public static function findByCertificate($id)
2447
    {
2448
        $category = Database::getManager()
2449
            ->createQuery('SELECT c.catId FROM ChamiloCoreBundle:GradebookCertificate c WHERE c.id = :id')
2450
            ->setParameters(['id' => $id])
2451
            ->getOneOrNullResult();
2452
2453
        if (empty($category)) {
2454
            return null;
2455
        }
2456
2457
        $category = self::load($category['catId']);
2458
2459
        if (empty($category)) {
2460
            return null;
2461
        }
2462
2463
        return $category[0];
2464
    }
2465
2466
    /**
2467
     * @param int $value
2468
     */
2469
    public function setDocumentId($value)
2470
    {
2471
        $this->documentId = (int) $value;
2472
    }
2473
2474
    /**
2475
     * @return int
2476
     */
2477
    public function getDocumentId()
2478
    {
2479
        return $this->documentId;
2480
    }
2481
2482
    /**
2483
     * Get the remaining weight in root category.
2484
     *
2485
     * @return int
2486
     */
2487
    public function getRemainingWeight()
2488
    {
2489
        $subCategories = $this->get_subcategories();
2490
2491
        $subWeight = 0;
2492
2493
        /** @var Category $subCategory */
2494
        foreach ($subCategories as $subCategory) {
2495
            $subWeight += $subCategory->get_weight();
2496
        }
2497
2498
        return $this->weight - $subWeight;
2499
    }
2500
2501
    /**
2502
     * @return int
2503
     */
2504
    public function getCourseId()
2505
    {
2506
        return $this->courseId;
2507
    }
2508
2509
    /**
2510
     * Sets both the course ID and course code. If course ID is empty, set both to null;
2511
     * @param ?int $courseId
2512
     *
2513
     * @return Category
2514
     */
2515
    public function setCourseId(?int $courseId = null): Category
2516
    {
2517
        $courseInfo = api_get_course_info_by_id($courseId);
2518
        if (!empty($courseInfo)) {
2519
            $this->course_code = $courseInfo['code'];
2520
            $this->courseId = $courseId;
2521
        } else {
2522
            $this->course_code = null;
2523
            $this->courseId = null;
2524
        }
2525
2526
        return $this;
2527
    }
2528
2529
    /**
2530
     * @return Category
2531
     */
2532
    private static function create_root_category()
2533
    {
2534
        $cat = new Category();
2535
        $cat->set_id(0);
2536
        $cat->set_name(get_lang('Main folder'));
2537
        $cat->set_description('');
2538
        $cat->set_user_id(0);
2539
        $cat->setCourseId(0);
2540
        $cat->set_parent_id(0);
2541
        $cat->set_weight(0);
2542
        $cat->set_visible(1);
2543
        $cat->setGenerateCertificates(0);
2544
        $cat->setIsRequirement(0);
2545
2546
        return $cat;
2547
    }
2548
2549
    /**
2550
     * @param ?Doctrine\DBAL\Result $result
2551
     *
2552
     * @return array
2553
     * @throws \Doctrine\DBAL\Exception
2554
     */
2555
    private static function create_category_objects_from_sql_result(?Doctrine\DBAL\Result $result)
2556
    {
2557
        $categories = [];
2558
        $allow = ('true' === api_get_setting('gradebook.allow_gradebook_stats'));
2559
        if ($allow) {
2560
            $em = Database::getManager();
2561
            $repo = $em->getRepository(GradebookCategory::class);
2562
        }
2563
2564
        if (!empty($result)) {
2565
            while ($data = Database::fetch_array($result)) {
2566
                $cat = new Category();
2567
                $cat->set_id($data['id']);
2568
                $cat->set_name($data['title']);
2569
                $cat->set_description($data['description']);
2570
                $cat->set_user_id($data['user_id']);
2571
                $cat->setCourseId($data['c_id']);
2572
                $cat->set_parent_id($data['parent_id']);
2573
                $cat->set_weight($data['weight']);
2574
                $cat->set_visible($data['visible']);
2575
                $cat->set_session_id($data['session_id']);
2576
                $cat->set_certificate_min_score($data['certif_min_score']);
2577
                $cat->set_grade_model_id((int) $data['grade_model_id']);
2578
                $cat->set_locked($data['locked']);
2579
                $cat->setGenerateCertificates($data['generate_certificates']);
2580
                $cat->setIsRequirement($data['is_requirement']);
2581
                //$cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2582
                $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2583
                $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2584
                $cat->setDocumentId($data['document_id']);
2585
                if ($allow) {
2586
                    $cat->entity = $repo->find($data['id']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $repo does not seem to be defined for all execution paths leading up to this point.
Loading history...
2587
                }
2588
2589
                $categories[] = $cat;
2590
            }
2591
        }
2592
2593
        return $categories;
2594
    }
2595
2596
    /**
2597
     * Internal function used by get_target_categories().
2598
     *
2599
     * @param array $targets
2600
     * @param int   $level
2601
     * @param int   $catid
2602
     *
2603
     * @return array
2604
     */
2605
    private function addTargetSubcategories($targets, $level, $catid)
2606
    {
2607
        $subcats = self::load(null, null, 0, $catid);
2608
        foreach ($subcats as $cat) {
2609
            if ($this->can_be_moved_to_cat($cat)) {
2610
                $targets[] = [
2611
                    $cat->get_id(),
2612
                    $cat->get_name(),
2613
                    $level + 1,
2614
                ];
2615
                $targets = $this->addTargetSubcategories(
2616
                    $targets,
2617
                    $level + 1,
2618
                    $cat->get_id()
2619
                );
2620
            }
2621
        }
2622
2623
        return $targets;
2624
    }
2625
2626
    /**
2627
     * Internal function used by get_target_categories() and addTargetSubcategories()
2628
     * Can this category be moved to the given category ?
2629
     * Impossible when origin and target are the same... children won't be processed
2630
     * either. (a category can't be moved to one of its own children).
2631
     */
2632
    private function can_be_moved_to_cat($cat)
2633
    {
2634
        return $cat->get_id() != $this->get_id();
2635
    }
2636
2637
    /**
2638
     * Internal function used by move_to_cat().
2639
     */
2640
    private function applyCourseCodeToChildren()
2641
    {
2642
        $cats = self::load(null, null, 0, $this->id, null);
2643
        $evals = Evaluation::load(null, null, 0, $this->id, null);
2644
        $links = LinkFactory::load(
2645
            null,
2646
            null,
2647
            null,
2648
            null,
2649
            0,
2650
            $this->id,
2651
            null
2652
        );
2653
        /** @var Category $cat */
2654
        foreach ($cats as $cat) {
2655
            $cat->setCourseId($this->getCourseId());
2656
            $cat->save();
2657
            $cat->applyCourseCodeToChildren();
2658
        }
2659
2660
        foreach ($evals as $eval) {
2661
            $eval->setCourseId($this->getCourseId());
2662
            $eval->save();
2663
        }
2664
2665
        foreach ($links as $link) {
2666
            $link->delete();
2667
        }
2668
    }
2669
2670
    /**
2671
     * Internal function used by get_tree().
2672
     *
2673
     * @param int      $level
2674
     * @param int|null $visible
2675
     *
2676
     * @return array
2677
     */
2678
    private function add_subtree($targets, $level, $catid, $visible)
2679
    {
2680
        $subcats = self::load(null, null, 0, $catid, $visible);
2681
2682
        if (!empty($subcats)) {
2683
            foreach ($subcats as $cat) {
2684
                $targets[] = [
2685
                    $cat->get_id(),
2686
                    $cat->get_name(),
2687
                    $level + 1,
2688
                ];
2689
                $targets = self::add_subtree(
0 ignored issues
show
Bug Best Practice introduced by
The method Category::add_subtree() is not static, but was called statically. ( Ignorable by Annotation )

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

2689
                /** @scrutinizer ignore-call */ 
2690
                $targets = self::add_subtree(
Loading history...
2690
                    $targets,
2691
                    $level + 1,
2692
                    $cat->get_id(),
2693
                    $visible
2694
                );
2695
            }
2696
        }
2697
2698
        return $targets;
2699
    }
2700
2701
    /**
2702
     * Calculate the current score on a gradebook category for a user.
2703
     *
2704
     * @return float The score
2705
     */
2706
    private static function calculateCurrentScore(
2707
        int $userId,
2708
        ?GradebookCategory $category = null,
2709
        ?int $courseId = null,
2710
        ?int $sessionId = null,
2711
    ): float|int {
2712
2713
        if (null === $category) {
2714
            return 0;
2715
        }
2716
2717
        $categoryList = self::load(
2718
            null,
2719
            null,
2720
            $courseId,
2721
            null,
2722
            null,
2723
            $sessionId
2724
        );
2725
2726
        /* @var Category $category */
2727
        $category = $categoryList[0] ?? null;
2728
2729
        if (null === $category) {
2730
            return 0;
2731
        }
2732
2733
        $courseEvaluations = $category->get_evaluations($userId, true);
2734
        $courseLinks = $category->get_links($userId, true);
2735
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2736
        $count = count($evaluationsAndLinks);
2737
        if (empty($count)) {
2738
            return 0;
2739
        }
2740
2741
        $categoryScore = 0;
2742
        for ($i = 0; $i < $count; $i++) {
2743
            /** @var AbstractLink $item */
2744
            $item = $evaluationsAndLinks[$i];
2745
            // Set session id from category
2746
            $item->set_session_id($category->get_session_id());
2747
            $score = $item->calc_score($userId);
2748
            $itemValue = 0;
2749
            if (!empty($score)) {
2750
                $divider = 0 == $score[1] ? 1 : $score[1];
2751
                $itemValue = $score[0] / $divider * $item->get_weight();
2752
            }
2753
2754
            $categoryScore += $itemValue;
2755
        }
2756
2757
        return api_float_val($categoryScore);
2758
    }
2759
}
2760