Passed
Push — master ( f3b60f...e63ea7 )
by Yannick
09:11
created

Category::get_parent_id()   A

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 0
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
            $category->setUser(api_get_user_entity($this->user_id));
524
            $category->setCourse($course);
525
            $category->setParent($parent);
526
            $category->setWeight(api_float_val($this->weight));
527
            $category->setVisible($this->visible ? true : false);
528
            $category->setCertifMinScore($this->certificate_min_score);
529
            $category->setSession(api_get_session_entity($this->session_id));
530
            $category->setGenerateCertificates($this->generateCertificates);
531
            if (!empty($this->grade_model_id)) {
532
                $model = $em->getRepository(\Chamilo\CoreBundle\Entity\GradeModel::class)->find($this->grade_model_id);
533
                $category->setGradeModel($model);
534
            }
535
536
            $category->setIsRequirement($this->isRequirement);
537
            $category->setLocked(0);
538
539
            $em->persist($category);
540
            $em->flush();
541
542
            $id = $category->getId();
543
            $this->set_id($id);
544
545
            if (!empty($id)) {
546
                $parent_id = $this->get_parent_id();
547
                $grade_model_id = $this->get_grade_model_id();
548
                if (0 == $parent_id) {
549
                    //do something
550
                    if (isset($grade_model_id) &&
551
                        !empty($grade_model_id) &&
552
                        '-1' != $grade_model_id
553
                    ) {
554
                        $obj = new GradeModel();
555
                        $components = $obj->get_components($grade_model_id);
556
                        $default_weight_setting = api_get_setting('gradebook_default_weight');
557
                        $default_weight = 100;
558
                        if (isset($default_weight_setting)) {
559
                            $default_weight = $default_weight_setting;
560
                        }
561
                        foreach ($components as $component) {
562
                            $gradebook = new Gradebook();
563
                            $params = [];
564
565
                            $params['name'] = $component['acronym'];
566
                            $params['description'] = $component['title'];
567
                            $params['user_id'] = api_get_user_id();
568
                            $params['parent_id'] = $id;
569
                            $params['weight'] = $component['percentage'] / 100 * $default_weight;
570
                            $params['session_id'] = api_get_session_id();
571
                            $params['c_id'] = $this->getCourseId();
572
573
                            $gradebook->save($params);
574
                        }
575
                    }
576
                }
577
            }
578
579
            $gradebook = new Gradebook();
580
            $gradebook->updateSkillsToGradeBook(
581
                $this->id,
582
                $this->get_skills(false)
583
            );
584
585
            return $id;
586
        }
587
    }
588
589
    /**
590
     * Update the properties of this category in the database.
591
     *
592
     * @throws \Doctrine\ORM\Exception\NotSupported
593
     * @throws \Doctrine\ORM\Exception\ORMException
594
     * @todo fix me
595
     */
596
    public function save()
597
    {
598
        $em = Database::getManager();
599
        $repo = $em->getRepository(GradebookCategory::class);
600
601
        /** @var GradebookCategory $category */
602
        $category = $repo->find($this->id);
603
604
        if (null === $category) {
605
            return false;
606
        }
607
608
        $parent = null;
609
        if (!empty($this->parent)) {
610
            $parent = $repo->find($this->parent);
611
        }
612
        $course = api_get_course_entity();
613
614
        $category->setTitle($this->name);
615
        $category->setDescription($this->description);
616
        $category->setUser(api_get_user_entity($this->user_id));
617
        $category->setCourse($course);
618
        $category->setParent($parent);
619
        $category->setWeight($this->weight);
620
        $category->setVisible($this->visible);
621
        $category->setCertifMinScore($this->certificate_min_score);
622
        $category->setGenerateCertificates($this->generateCertificates);
623
        if (!empty($this->grade_model_id)) {
624
            $model = $em->getRepository(\Chamilo\CoreBundle\Entity\GradeModel::class)->find($this->grade_model_id);
625
            $category->setGradeModel($model);
626
        }
627
628
        $category->setIsRequirement($this->isRequirement);
629
630
        $em->persist($category);
631
        $em->flush();
632
633
        if (!empty($this->id)) {
634
            $parent_id = $this->get_parent_id();
635
            $grade_model_id = $this->get_grade_model_id();
636
            if (0 == $parent_id) {
637
                if (!empty($grade_model_id) &&
638
                    '-1' != $grade_model_id
639
                ) {
640
                    $obj = new GradeModel();
641
                    $components = $obj->get_components($grade_model_id);
642
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
643
                    $default_weight = 100;
644
                    if (isset($default_weight_setting)) {
645
                        $default_weight = $default_weight_setting;
646
                    }
647
                    $final_weight = $this->get_weight();
648
                    if (!empty($final_weight)) {
649
                        $default_weight = $this->get_weight();
650
                    }
651
                    foreach ($components as $component) {
652
                        $gradebook = new Gradebook();
653
                        $params = [];
654
                        $params['name'] = $component['acronym'];
655
                        $params['description'] = $component['title'];
656
                        $params['user_id'] = api_get_user_id();
657
                        $params['parent_id'] = $this->id;
658
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
659
                        $params['session_id'] = api_get_session_id();
660
                        $params['c_id'] = $this->getCourseId();
661
                        $gradebook->save($params);
662
                    }
663
                }
664
            }
665
        }
666
667
        $gradebook = new Gradebook();
668
        $gradebook->updateSkillsToGradeBook(
669
            $this->id,
670
            $this->get_skills(false),
671
            true
672
        );
673
    }
674
675
    /**
676
     * Update link weights see #5168.
677
     *
678
     * @param int $new_weight
679
     */
680
    public function updateChildrenWeight($new_weight)
681
    {
682
        $links = $this->get_links();
683
        $old_weight = $this->get_weight();
684
685
        if (!empty($links)) {
686
            foreach ($links as $link_item) {
687
                if (isset($link_item)) {
688
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
689
                    $link_item->set_weight($new_item_weight);
690
                    $link_item->save();
691
                }
692
            }
693
        }
694
    }
695
696
    /**
697
     * Delete this evaluation from the database.
698
     * @throws Exception
699
     */
700
    public function delete(): void
701
    {
702
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
703
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
704
        Database::query($sql);
705
    }
706
707
    /**
708
     * Return an HTML span block if the given resource has been deleted
709
     * @param ?int $courseId
710
     *
711
     * @return string
712
     * @throws \Doctrine\DBAL\Exception
713
     * @throws Exception
714
     */
715
    public static function show_message_resource_delete(?int $courseId): string
716
    {
717
        if (empty($courseId)) {
718
            return '';
719
        }
720
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
721
        $sql = "SELECT count(*) AS num
722
                FROM $table
723
                WHERE
724
                    c_id = $courseId AND
725
                    visible = 3";
726
        $res = Database::query($sql);
727
        $option = Database::fetch_array($res);
728
        if ($option['num'] >= 1) {
729
            return '&nbsp;&nbsp;<span class="resource-deleted">
730
                (&nbsp;'.get_lang('The resource has been deleted').'&nbsp;)
731
                </span>';
732
        }
733
734
        return '';
735
    }
736
737
    /**
738
     * Shows all information of a category.
739
     *
740
     * @param int $categoryId
741
     *
742
     * @return array
743
     * @throws \Doctrine\DBAL\Exception
744
     * @throws Exception
745
     */
746
    public function showAllCategoryInfo(int $categoryId): array
747
    {
748
        if (empty($categoryId)) {
749
            return [];
750
        }
751
752
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
753
        $sql = "SELECT * FROM $table
754
                WHERE id = $categoryId";
755
        $result = Database::query($sql);
756
757
        return Database::fetch_array($result);
758
    }
759
760
    /**
761
     * Checks if the certificate is available for the given user in this category.
762
     *
763
     * @param int $user_id User ID
764
     *
765
     * @return bool True if conditions match, false if fails
766
     */
767
    public function is_certificate_available($user_id)
768
    {
769
        $score = $this->calc_score(
770
            $user_id,
771
            null,
772
            $this->courseId,
773
            $this->session_id
774
        );
775
776
        if (isset($score) && isset($score[0])) {
777
            // Get a percentage score to compare to minimum certificate score
778
            // $certification_score = $score[0] / $score[1] * 100;
779
            // Get real score not a percentage.
780
            $certification_score = $score[0];
781
            if ($certification_score >= $this->certificate_min_score) {
782
                return true;
783
            }
784
        }
785
786
        return false;
787
    }
788
789
    /**
790
     * Is this category the main one in a course ?
791
     * A category is a course if it has a course code and no parent category.
792
     */
793
    public function is_course(): bool
794
    {
795
        return !empty($this->getCourseId())
796
            && (!isset($this->parent) || 0 == $this->parent);
797
    }
798
799
    /**
800
     * Calculate the score of this category.
801
     */
802
    public function calc_score(
803
        ?int $studentId = null,
804
        ?string $type = null,
805
        ?int $courseId = 0,
806
        ?int $session_id = null
807
    ): ?array {
808
        $key = 'category:'.$this->id.'student:'.(int) $studentId.'type:'.$type.'course:'.$courseId.'session:'.(int) $session_id;
809
        $useCache = ('true' === api_get_setting('gradebook.gradebook_use_apcu_cache'));
810
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
811
812
        if ($cacheAvailable) {
813
            $cache = new \Symfony\Component\Cache\Adapter\ApcuAdapter();
814
            if ($cache->hasItem($key)) {
815
                return $cache->getItem($key)->get();
816
            }
817
        }
818
        // Classic
819
        if (!empty($studentId) && '' == $type) {
820
            if (!empty($courseId)) {
821
                $cats = $this->get_subcategories(
822
                    $studentId,
823
                    $courseId,
824
                    $session_id
825
                );
826
                $evals = $this->get_evaluations($studentId, false, $courseId);
827
                $links = $this->get_links($studentId, false, $courseId);
828
            } else {
829
                $cats = $this->get_subcategories($studentId);
830
                $evals = $this->get_evaluations($studentId);
831
                $links = $this->get_links($studentId);
832
            }
833
834
            // Calculate score
835
            $count = 0;
836
            $ressum = 0;
837
            $weightsum = 0;
838
            if (!empty($cats)) {
839
                /** @var Category $cat */
840
                foreach ($cats as $cat) {
841
                    $cat->set_session_id($session_id);
842
                    $cat->setCourseId($courseId);
843
                    $cat->setStudentList($this->getStudentList());
844
                    $score = $cat->calc_score(
845
                        $studentId,
846
                        null,
847
                        $courseId,
848
                        $session_id
849
                    );
850
851
                    $catweight = 0;
852
                    if (0 != $cat->get_weight()) {
853
                        $catweight = $cat->get_weight();
854
                        $weightsum += $catweight;
855
                    }
856
857
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
858
                        $ressum += $score[0] / $score[1] * $catweight;
859
                    }
860
                }
861
            }
862
863
            if (!empty($evals)) {
864
                /** @var Evaluation $eval */
865
                foreach ($evals as $eval) {
866
                    $eval->setStudentList($this->getStudentList());
867
                    $evalres = $eval->calc_score($studentId);
868
                    if (isset($evalres) && 0 != $eval->get_weight()) {
869
                        $evalweight = $eval->get_weight();
870
                        $weightsum += $evalweight;
871
                        if (!empty($evalres[1])) {
872
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
873
                        }
874
                    } else {
875
                        if (0 != $eval->get_weight()) {
876
                            $evalweight = $eval->get_weight();
877
                            $weightsum += $evalweight;
878
                        }
879
                    }
880
                }
881
            }
882
883
            if (!empty($links)) {
884
                /** @var EvalLink|ExerciseLink $link */
885
                foreach ($links as $link) {
886
                    $link->setStudentList($this->getStudentList());
887
888
                    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...
889
                        $link->set_session_id($session_id);
890
                    }
891
892
                    $linkres = $link->calc_score($studentId, null);
893
                    if (!empty($linkres) && 0 != $link->get_weight()) {
894
                        $linkweight = $link->get_weight();
895
                        $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
896
                        $weightsum += $linkweight;
897
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
898
                    } else {
899
                        // Adding if result does not exists
900
                        if (0 != $link->get_weight()) {
901
                            $linkweight = $link->get_weight();
902
                            $weightsum += $linkweight;
903
                        }
904
                    }
905
                }
906
            }
907
        } else {
908
            if (!empty($courseId)) {
909
                $cats = $this->get_subcategories(
910
                    null,
911
                    $courseId,
912
                    $session_id
913
                );
914
                $evals = $this->get_evaluations(null, false, $courseId);
915
                $links = $this->get_links(null, false, $courseId);
916
            } else {
917
                $cats = $this->get_subcategories(null);
918
                $evals = $this->get_evaluations(null);
919
                $links = $this->get_links(null);
920
            }
921
922
            // Calculate score
923
            $ressum = 0;
924
            $weightsum = 0;
925
            $bestResult = 0;
926
            $totalScorePerStudent = [];
927
928
            if (!empty($cats)) {
929
                /** @var Category $cat */
930
                foreach ($cats as $cat) {
931
                    $cat->setStudentList($this->getStudentList());
932
                    $score = $cat->calc_score(
933
                        null,
934
                        $type,
935
                        $courseId,
936
                        $session_id
937
                    );
938
939
                    $catweight = 0;
940
                    if (0 != $cat->get_weight()) {
941
                        $catweight = $cat->get_weight();
942
                        $weightsum += $catweight;
943
                    }
944
945
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
946
                        $ressum += $score[0] / $score[1] * $catweight;
947
948
                        if ($ressum > $bestResult) {
949
                            $bestResult = $ressum;
950
                        }
951
                    }
952
                }
953
            }
954
955
            if (!empty($evals)) {
956
                if ('best' === $type) {
957
                    $studentList = $this->getStudentList();
958
                    foreach ($studentList as $student) {
959
                        $studentId = $student['user_id'];
960
                        foreach ($evals as $eval) {
961
                            $linkres = $eval->calc_score($studentId, null);
962
                            $linkweight = $eval->get_weight();
963
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
964
                            $ressum = $linkres[0] / $link_res_denom * $linkweight;
965
966
                            if (!isset($totalScorePerStudent[$studentId])) {
967
                                $totalScorePerStudent[$studentId] = 0;
968
                            }
969
                            $totalScorePerStudent[$studentId] += $ressum;
970
                        }
971
                    }
972
                } else {
973
                    /** @var Evaluation $eval */
974
                    foreach ($evals as $eval) {
975
                        $evalres = $eval->calc_score(null, $type);
976
                        $eval->setStudentList($this->getStudentList());
977
978
                        if (isset($evalres) && 0 != $eval->get_weight()) {
979
                            $evalweight = $eval->get_weight();
980
                            $weightsum += $evalweight;
981
                            if (!empty($evalres[1])) {
982
                                $ressum += $evalres[0] / $evalres[1] * $evalweight;
983
                            }
984
985
                            if ($ressum > $bestResult) {
986
                                $bestResult = $ressum;
987
                            }
988
                        } else {
989
                            if (0 != $eval->get_weight()) {
990
                                $evalweight = $eval->get_weight();
991
                                $weightsum += $evalweight;
992
                            }
993
                        }
994
                    }
995
                }
996
            }
997
998
            if (!empty($links)) {
999
                $studentList = $this->getStudentList();
1000
                if ('best' === $type) {
1001
                    foreach ($studentList as $student) {
1002
                        $studentId = $student['user_id'];
1003
                        foreach ($links as $link) {
1004
                            $linkres = $link->calc_score($studentId, null);
1005
                            $linkweight = $link->get_weight();
1006
                            if ($linkres) {
1007
                                $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1008
                                $ressum = $linkres[0] / $link_res_denom * $linkweight;
1009
                            }
1010
1011
                            if (!isset($totalScorePerStudent[$studentId])) {
1012
                                $totalScorePerStudent[$studentId] = 0;
1013
                            }
1014
                            $totalScorePerStudent[$studentId] += $ressum;
1015
                        }
1016
                    }
1017
                } else {
1018
                    /** @var EvalLink|ExerciseLink $link */
1019
                    foreach ($links as $link) {
1020
                        $link->setStudentList($this->getStudentList());
1021
1022
                        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...
1023
                            $link->set_session_id($session_id);
1024
                        }
1025
1026
                        $linkres = $link->calc_score($studentId, $type);
1027
1028
                        if (!empty($linkres) && 0 != $link->get_weight()) {
1029
                            $linkweight = $link->get_weight();
1030
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1031
1032
                            $weightsum += $linkweight;
1033
                            $ressum += $linkres[0] / $link_res_denom * $linkweight;
1034
                            if ($ressum > $bestResult) {
1035
                                $bestResult = $ressum;
1036
                            }
1037
                        } else {
1038
                            // Adding if result does not exist
1039
                            if (0 != $link->get_weight()) {
1040
                                $linkweight = $link->get_weight();
1041
                                $weightsum += $linkweight;
1042
                            }
1043
                        }
1044
                    }
1045
                }
1046
            }
1047
        }
1048
1049
        switch ($type) {
1050
            case 'best':
1051
                arsort($totalScorePerStudent);
1052
                $maxScore = current($totalScorePerStudent);
1053
1054
                return [$maxScore, $this->get_weight()];
1055
                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...
1056
            case 'average':
1057
                if (empty($ressum)) {
1058
                    if ($cacheAvailable) {
1059
                        $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...
1060
                        $cacheItem->set(null);
1061
1062
                        $cache->save($cacheItem);
1063
                    }
1064
1065
                    return null;
1066
                }
1067
1068
                if ($cacheAvailable) {
1069
                    $cacheItem = $cache->getItem($key);
1070
                    $cacheItem->set([$ressum, $weightsum]);
1071
1072
                    $cache->save($cacheItem);
1073
                }
1074
1075
                return [$ressum, $weightsum];
1076
                //break;
1077
            case 'ranking':
1078
                // category ranking is calculated in gradebook_data_generator.class.php
1079
                // function get_data
1080
                return null;
1081
1082
                //return AbstractLink::getCurrentUserRanking($studentId, []);
1083
                //break;
1084
            default:
1085
                if ($cacheAvailable) {
1086
                    $cacheItem = $cache->getItem($key);
1087
                    $cacheItem->set([$ressum, $weightsum]);
1088
1089
                    $cache->save($cacheItem);
1090
                }
1091
1092
                return [$ressum, $weightsum];
1093
        }
1094
    }
1095
1096
    /**
1097
     * Delete this category and every subcategory, evaluation and result inside.
1098
     */
1099
    public function delete_all()
1100
    {
1101
        $cats = self::load(null, null, $this->getCourseId(), $this->id, null);
1102
        $evals = Evaluation::load(
1103
            null,
1104
            null,
1105
            $this->getCourseId(),
1106
            $this->id,
1107
            null
1108
        );
1109
1110
        $links = LinkFactory::load(
1111
            null,
1112
            null,
1113
            null,
1114
            null,
1115
            $this->getCourseId(),
1116
            $this->id,
1117
            null
1118
        );
1119
1120
        if (!empty($cats)) {
1121
            /** @var Category $cat */
1122
            foreach ($cats as $cat) {
1123
                $cat->delete_all();
1124
                $cat->delete();
1125
            }
1126
        }
1127
1128
        if (!empty($evals)) {
1129
            /** @var Evaluation $eval */
1130
            foreach ($evals as $eval) {
1131
                $eval->delete_with_results();
1132
            }
1133
        }
1134
1135
        if (!empty($links)) {
1136
            /** @var AbstractLink $link */
1137
            foreach ($links as $link) {
1138
                $link->delete();
1139
            }
1140
        }
1141
1142
        $this->delete();
1143
    }
1144
1145
    /**
1146
     * Return array of Category objects where a student is subscribed to.
1147
     *
1148
     * @param int $stud_id
1149
     * @param int $courseId
1150
     * @param int $session_id
1151
     *
1152
     * @return array
1153
     * @throws \Doctrine\DBAL\Exception
1154
     */
1155
    public function get_root_categories_for_student(
1156
        int $stud_id,
1157
        int $courseId = 0,
1158
        int $session_id = 0
1159
    ) {
1160
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1161
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1162
1163
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1164
1165
        if (!api_is_allowed_to_edit()) {
1166
            $sql .= ' AND visible = 1';
1167
            //proceed with checks on optional parameters course & session
1168
            if (!empty($courseId)) {
1169
                // TODO: considering it highly improbable that a user would get here
1170
                // if he doesn't have the rights to view this course and this
1171
                // session, we don't check his registration to these, but this
1172
                // could be an improvement
1173
                if (!empty($session_id)) {
1174
                    $sql .= " AND c_id = '".$courseId."' AND session_id = ".$session_id;
1175
                } else {
1176
                    $sql .= " AND c_id = '".$courseId."' AND session_id is null OR session_id=0";
1177
                }
1178
            } else {
1179
                //no optional parameter, proceed as usual
1180
                $sql .= ' AND c_id in
1181
                     (
1182
                        SELECT c.id
1183
                        FROM '.$main_course_user_table.' cu
1184
                        WHERE cu.user_id = '.intval($stud_id).'
1185
                        AND cu.status = '.STUDENT.'
1186
                    )';
1187
            }
1188
        } elseif (!api_is_platform_admin()) {
1189
            //proceed with checks on optional parameters course & session
1190
            if (!empty($courseId)) {
1191
                // TODO: considering it highly improbable that a user would get here
1192
                // if he doesn't have the rights to view this course and this
1193
                // session, we don't check his registration to these, but this
1194
                // could be an improvement
1195
                $sql .= " AND c_id  = $courseId";
1196
                if (!empty($session_id)) {
1197
                    $sql .= " AND session_id = ".$session_id;
1198
                } else {
1199
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1200
                }
1201
            } else {
1202
                $sql .= ' AND c_id IN
1203
                     (
1204
                        SELECT c.id
1205
                        FROM '.$main_course_user_table.' cu
1206
                        WHERE
1207
                            cu.user_id = '.api_get_user_id().' AND
1208
                            cu.status = '.COURSEMANAGER.'
1209
                    )';
1210
            }
1211
        } elseif (api_is_platform_admin()) {
1212
            if (0 != $session_id) {
1213
                $sql .= ' AND session_id='.$session_id;
1214
            } else {
1215
                $sql .= ' AND coalesce(session_id,0)=0';
1216
            }
1217
        }
1218
        $result = Database::query($sql);
1219
        $cats = self::create_category_objects_from_sql_result($result);
1220
1221
        // course independent categories
1222
        if (empty($courseId)) {
1223
            $cats = $this->getIndependentCategoriesWithStudentResult(
1224
                0,
1225
                $stud_id,
1226
                $cats
1227
            );
1228
        }
1229
1230
        return $cats;
1231
    }
1232
1233
    /**
1234
     * Return array of Category objects where a teacher is admin for.
1235
     *
1236
     * @param int    $user_id (to return everything, use 'null' here)
1237
     * @param ?int   $courseId (optional)
1238
     * @param ?int    $session_id (optional)
1239
     *
1240
     * @return array
1241
     * @throws \Doctrine\DBAL\Exception
1242
     */
1243
    public function get_root_categories_for_teacher(
1244
        int $user_id,
1245
        ?int $courseId = null,
1246
        ?int $session_id = null
1247
    ) {
1248
        if (null == $user_id) {
1249
            return self::load(null, null, $courseId, 0, null, $session_id);
1250
        }
1251
1252
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1253
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1254
1255
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1256
                WHERE parent_id = 0 ';
1257
        if (!empty($courseId)) {
1258
            $sql .= " AND c_id = $courseId ";
1259
            if (!empty($session_id)) {
1260
                $sql .= " AND session_id = ".$session_id;
1261
            }
1262
        } else {
1263
            $sql .= ' AND c_id in
1264
                 (
1265
                    SELECT cu.id
1266
                    FROM '.$main_course_user_table.' cu
1267
                    WHERE user_id = '.$user_id.'
1268
                )';
1269
        }
1270
        $result = Database::query($sql);
1271
        $cats = self::create_category_objects_from_sql_result($result);
1272
        // course independent categories
1273
        if (!empty($courseId)) {
1274
            $indcats = self::load(
1275
                null,
1276
                $user_id,
1277
                $courseId,
1278
                0,
1279
                null,
1280
                $session_id
1281
            );
1282
            $cats = array_merge($cats, $indcats);
1283
        }
1284
1285
        return $cats;
1286
    }
1287
1288
    /**
1289
     * Can this category be moved to somewhere else ?
1290
     * The root and courses cannot be moved.
1291
     *
1292
     * @return bool
1293
     */
1294
    public function is_movable()
1295
    {
1296
        return !(!isset($this->id) || 0 == $this->id || $this->is_course());
1297
    }
1298
1299
    /**
1300
     * Generate an array of possible categories where this category can be moved to.
1301
     * Notice: its own parent will be included in the list: it's up to the frontend
1302
     * to disable this element.
1303
     *
1304
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1305
     */
1306
    public function get_target_categories()
1307
    {
1308
        // the root or a course -> not movable
1309
        if (!$this->is_movable()) {
1310
            return null;
1311
        } else {
1312
            // otherwise:
1313
            // - course independent category
1314
            //   -> movable to root or other independent categories
1315
            // - category inside a course
1316
            //   -> movable to root, independent categories or categories inside the course
1317
            $user = api_is_platform_admin() ? null : api_get_user_id();
1318
            $targets = [];
1319
            $level = 0;
1320
1321
            $root = [0, get_lang('Main folder'), $level];
1322
            $targets[] = $root;
1323
1324
            if (!empty($this->courseId)) {
1325
                $crscats = self::load(null, null, $this->courseId, 0);
1326
                foreach ($crscats as $cat) {
1327
                    if ($this->can_be_moved_to_cat($cat)) {
1328
                        $targets[] = [
1329
                            $cat->get_id(),
1330
                            $cat->get_name(),
1331
                            $level + 1,
1332
                        ];
1333
                        $targets = $this->addTargetSubcategories(
1334
                            $targets,
1335
                            $level + 1,
1336
                            $cat->get_id()
1337
                        );
1338
                    }
1339
                }
1340
            }
1341
1342
            $indcats = self::load(null, $user, 0, 0);
1343
            foreach ($indcats as $cat) {
1344
                if ($this->can_be_moved_to_cat($cat)) {
1345
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1346
                    $targets = $this->addTargetSubcategories(
1347
                        $targets,
1348
                        $level + 1,
1349
                        $cat->get_id()
1350
                    );
1351
                }
1352
            }
1353
1354
            return $targets;
1355
        }
1356
    }
1357
1358
    /**
1359
     * Move this category to the given category.
1360
     * If this category moves from inside a course to outside,
1361
     * its course code must be changed, as well as the course code
1362
     * of all underlying categories and evaluations. All links will
1363
     * be deleted as well !
1364
     */
1365
    public function move_to_cat($cat)
1366
    {
1367
        $this->set_parent_id($cat->get_id());
1368
        if ($this->get_course_code() != $cat->get_course_code()) {
1369
            $this->setCourseId($cat->getCourseId());
1370
            $this->applyCourseCodeToChildren();
1371
        }
1372
        $this->save();
1373
    }
1374
1375
    /**
1376
     * Generate an array of all categories the user can navigate to.
1377
     */
1378
    public function get_tree()
1379
    {
1380
        $targets = [];
1381
        $level = 0;
1382
        $root = [0, get_lang('Main folder'), $level];
1383
        $targets[] = $root;
1384
1385
        // course or platform admin
1386
        if (api_is_allowed_to_edit()) {
1387
            $user = api_is_platform_admin() ? null : api_get_user_id();
1388
            $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

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

2000
            Event::/** @scrutinizer ignore-call */ 
2001
                   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...
2001
        }
2002
    }
2003
2004
    /**
2005
     * Generates a certificate for this user if everything matches.
2006
     */
2007
    public static function generateUserCertificate(
2008
        GradebookCategory $category,
2009
        int               $user_id,
2010
        bool              $sendNotification = false,
2011
        bool $skipGenerationIfExists = false
2012
    ) {
2013
        $user_id = (int) $user_id;
2014
        $categoryId = $category->getId();
2015
        $sessionId = $category->getSession() ? $category->getSession()->getId() : 0;
2016
        $courseId = $category->getCourse()->getId();
2017
2018
        // check if all min_score requirements are met
2019
        if (!self::userMeetsMinimumScores($user_id, $category)) {
2020
            return false; // Do not generate certificate if the user does not meet all min_score criteria
2021
        }
2022
2023
        $skillToolEnabled = SkillModel::hasAccessToUserSkill(api_get_user_id(), $user_id);
2024
        $userHasSkills = false;
2025
        if ($skillToolEnabled) {
2026
            $skill = new SkillModel();
2027
            $skill->addSkillToUser($user_id, $category, $courseId, $sessionId);
2028
            $objSkillRelUser = new SkillRelUserModel();
2029
            $userSkills = $objSkillRelUser->getUserSkills($user_id, $courseId, $sessionId);
2030
            $userHasSkills = !empty($userSkills);
2031
        }
2032
2033
        // If certificate generation is disabled, return only badge link (if available)
2034
        if (empty($category->getGenerateCertificates())) {
2035
            if ($userHasSkills) {
2036
                return [
2037
                    'badge_link' => Display::toolbarButton(
2038
                        get_lang('Export badges'),
2039
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2040
                        'open-in-new'
2041
                    ),
2042
                ];
2043
            }
2044
2045
            return false;
2046
        }
2047
        $my_certificate = GradebookUtils::get_certificate_by_user_id($categoryId, $user_id);
2048
2049
        // If certificate already exists and we should skip regeneration, return false
2050
        if ($skipGenerationIfExists && !empty($my_certificate)) {
2051
            return false;
2052
        }
2053
2054
        $categoryLegacy = self::load($categoryId);
2055
        $categoryLegacy = $categoryLegacy[0];
2056
2057
        /** @var Category $categoryLegacy */
2058
        $totalScore = $categoryLegacy->calc_score($user_id);
2059
2060
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2061
        // file load this variable as a global
2062
        $scoredisplay = ScoreDisplay::instance();
2063
        $my_score_in_gradebook = $scoredisplay->display_score($totalScore, SCORE_SIMPLE);
2064
2065
        if (empty($my_certificate)) {
2066
            GradebookUtils::registerUserInfoAboutCertificate(
2067
                $categoryId,
2068
                $user_id,
2069
                $my_score_in_gradebook,
2070
                api_get_utc_datetime()
2071
            );
2072
            $my_certificate = GradebookUtils::get_certificate_by_user_id($categoryId, $user_id);
2073
        }
2074
2075
        $html = [];
2076
        if (!empty($my_certificate)) {
2077
            $pathToCertificate = $category->getDocument()->getResourceNode()->getResourceFiles()->first()->getFile()->getPathname();
2078
2079
            $certificate_obj = new Certificate(
2080
                $my_certificate['id'],
2081
                0,
2082
                $sendNotification,
2083
                true,
2084
                $pathToCertificate
2085
            );
2086
2087
            $fileWasGenerated = $certificate_obj->isHtmlFileGenerated();
2088
2089
            // Fix when using a custom certificate plugin
2090
            if ('true' === api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate')) {
2091
                $infoCertificate = CustomCertificatePlugin::getCertificateData($my_certificate['id'], $user_id);
2092
                if (!empty($infoCertificate)) {
2093
                    $fileWasGenerated = true;
2094
                }
2095
            }
2096
2097
            if (!empty($fileWasGenerated)) {
2098
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'].'&user_id='.$user_id;
2099
                $certificates = Display::toolbarButton(
2100
                    get_lang('Display certificate'),
2101
                    $url,
2102
                    'eye',
2103
                    'primary',
2104
                    ['target' => '_blank']
2105
                );
2106
2107
                $exportToPDF = Display::url(
2108
                    Display::getMdiIcon(ActionIcon::EXPORT_PDF, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Export to PDF')),
2109
                    "$url&action=export"
2110
                );
2111
2112
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2113
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2114
                if ('true' === $hideExportLink || (api_is_student() && 'true' === $hideExportLinkStudent)) {
2115
                    $exportToPDF = null;
2116
                }
2117
2118
                $html = [
2119
                    'certificate_link' => $certificates,
2120
                    'pdf_link' => $exportToPDF,
2121
                    'pdf_url' => "$url&action=export",
2122
                ];
2123
            }
2124
2125
            if ($skillToolEnabled && $userHasSkills) {
2126
                $html['badge_link'] = Display::toolbarButton(
2127
                    get_lang('Export badges'),
2128
                    api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2129
                    'open-in-new'
2130
                );
2131
            }
2132
2133
            return $html;
2134
        }
2135
2136
        return false;
2137
    }
2138
2139
    /**
2140
     * Checks whether the user has met the minimum score (`min_score`) in all required evaluations.
2141
     */
2142
    public static function userMeetsMinimumScores(int $userId, GradebookCategory $category): bool
2143
    {
2144
        $evaluations = $category->getEvaluations();
2145
2146
        foreach ($evaluations as $evaluation) {
2147
            $minScore = $evaluation->getMinScore();
2148
            if ($minScore !== null) {
2149
                $userScore = self::getUserScoreForEvaluation($userId, $evaluation->getId());
2150
                if ($userScore === null || $userScore < $minScore) {
2151
                    return false; // If at least one evaluation is below `min_score`, return false
2152
                }
2153
            }
2154
        }
2155
2156
        return true;
2157
    }
2158
2159
    /**
2160
     * Retrieves the score of a user for a specific evaluation using the GradebookResult repository.
2161
     */
2162
    public static function getUserScoreForEvaluation(int $userId, int $evaluationId): ?float
2163
    {
2164
        $gradebookResultRepo = Container::getGradebookResultRepository();
2165
2166
        $gradebookResult = $gradebookResultRepo->findOneBy([
2167
            'user' => $userId,
2168
            'evaluation' => $evaluationId,
2169
        ]);
2170
2171
        return $gradebookResult ? $gradebookResult->getScore() : null;
2172
    }
2173
2174
    /**
2175
     * @param int   $catId
2176
     * @param array $userList
2177
     */
2178
    public static function exportAllCertificates($catId, $userList = [])
2179
    {
2180
        $orientation = api_get_setting('document.certificate_pdf_orientation');
2181
2182
        $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...
2183
        if (!empty($orientation)) {
2184
            $params['orientation'] = $orientation;
2185
        }
2186
2187
        $params['left'] = 0;
2188
        $params['right'] = 0;
2189
        $params['top'] = 0;
2190
        $params['bottom'] = 0;
2191
        $page_format = 'landscape' == $params['orientation'] ? 'A4-L' : 'A4';
2192
        $pdf = new PDF($page_format, $params['orientation'], $params);
2193
        if ('true' === api_get_setting('certificate.add_certificate_pdf_footer')) {
2194
            $pdf->setCertificateFooter();
2195
        }
2196
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2197
        $certificate_path_list = [];
2198
2199
        if (!empty($certificate_list)) {
2200
            foreach ($certificate_list as $index => $value) {
2201
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2202
                    $value['user_id'],
2203
                    $catId
2204
                );
2205
                foreach ($list_certificate as $value_certificate) {
2206
                    $certificate_obj = new Certificate($value_certificate['id']);
2207
                    $certificate_obj->generate(['hide_print_button' => true]);
2208
                    if ($certificate_obj->isHtmlFileGenerated()) {
2209
                        $certificate_path_list[] = $certificate_obj->html_file;
2210
                    }
2211
                }
2212
            }
2213
        }
2214
2215
        if (!empty($certificate_path_list)) {
2216
            // Print certificates (without the common header/footer/watermark
2217
            //  stuff) and return as one multiple-pages PDF
2218
            $pdf->html_to_pdf(
2219
                $certificate_path_list,
2220
                get_lang('Certificates'),
2221
                null,
2222
                false,
2223
                false
2224
            );
2225
        }
2226
    }
2227
2228
    /**
2229
     * @param int $catId
2230
     */
2231
    public static function deleteAllCertificates($catId)
2232
    {
2233
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2234
        if (!empty($certificate_list)) {
2235
            foreach ($certificate_list as $index => $value) {
2236
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2237
                    $value['user_id'],
2238
                    $catId
2239
                );
2240
                foreach ($list_certificate as $value_certificate) {
2241
                    $certificate_obj = new Certificate($value_certificate['id']);
2242
                    $certificate_obj->delete(true);
2243
                }
2244
            }
2245
        }
2246
    }
2247
2248
    /**
2249
     * Check whether a user has finished a course by its gradebook.
2250
     */
2251
    public static function userFinishedCourse(
2252
        int $userId,
2253
        GradebookCategory $category,
2254
        bool $recalculateScore = false,
2255
        ?int $courseId = null,
2256
        ?int $sessionId = null
2257
    ): bool {
2258
        $currentScore = self::getCurrentScore(
2259
            $userId,
2260
            $category,
2261
            $recalculateScore,
2262
            $courseId,
2263
            $sessionId
2264
        );
2265
2266
        $minCertificateScore = $category->getCertifMinScore();
2267
2268
        return $currentScore >= $minCertificateScore;
2269
    }
2270
2271
    /**
2272
     * Get the current score (as percentage) on a gradebook category for a user.
2273
     */
2274
    public static function getCurrentScore(
2275
        int               $userId,
2276
        GradebookCategory $category,
2277
        bool              $recalculate = false,
2278
        ?int              $courseId = null,
2279
        ?int              $sessionId = null
2280
    ): float|int {
2281
2282
        if ($recalculate) {
2283
            return self::calculateCurrentScore(
2284
                $userId,
2285
                $category,
2286
                $courseId,
2287
                $sessionId
2288
            );
2289
        }
2290
2291
        $resultData = Database::select(
2292
            '*',
2293
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2294
            [
2295
                'where' => [
2296
                    'category_id = ? AND user_id = ?' => [$category->getId(), $userId],
2297
                ],
2298
                'order' => 'registered_at DESC',
2299
                'limit' => '1',
2300
            ],
2301
            'first'
2302
        );
2303
2304
        if (empty($resultData)) {
2305
            return 0;
2306
        }
2307
2308
        return $resultData['score'];
2309
    }
2310
2311
    /**
2312
     * Register the current score for a user on a category gradebook.
2313
     *
2314
     * @param float $score      The achieved score
2315
     * @param int   $userId     The user id
2316
     * @param int   $categoryId The gradebook category
2317
     *
2318
     * @return int The insert id
2319
     */
2320
    public static function registerCurrentScore($score, $userId, $categoryId)
2321
    {
2322
        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...
2323
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2324
            [
2325
                'category_id' => intval($categoryId),
2326
                'user_id' => intval($userId),
2327
                'score' => api_float_val($score),
2328
                'registered_at' => api_get_utc_datetime(),
2329
            ]
2330
        );
2331
    }
2332
2333
    /**
2334
     * @return array
2335
     */
2336
    public function getStudentList()
2337
    {
2338
        return $this->studentList;
2339
    }
2340
2341
    /**
2342
     * @param array $list
2343
     */
2344
    public function setStudentList($list)
2345
    {
2346
        $this->studentList = $list;
2347
    }
2348
2349
    /**
2350
     * @return string
2351
     */
2352
    public static function getUrl()
2353
    {
2354
        $url = Session::read('gradebook_dest');
2355
        if (empty($url)) {
2356
            // We guess the link
2357
            $courseInfo = api_get_course_info();
2358
            if (!empty($courseInfo)) {
2359
                return api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2360
            } else {
2361
                return api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2362
            }
2363
        }
2364
2365
        return $url;
2366
    }
2367
2368
    /**
2369
     * Destination is index.php or gradebook.php.
2370
     *
2371
     * @param string $url
2372
     */
2373
    public static function setUrl($url)
2374
    {
2375
        switch ($url) {
2376
            case 'gradebook.php':
2377
                $url = api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2378
                break;
2379
            case 'index.php':
2380
                $url = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2381
                break;
2382
        }
2383
        Session::write('gradebook_dest', $url);
2384
    }
2385
2386
    /**
2387
     * @return int
2388
     */
2389
    public function getGradeBooksToValidateInDependence()
2390
    {
2391
        return $this->gradeBooksToValidateInDependence;
2392
    }
2393
2394
    /**
2395
     * @param int $value
2396
     *
2397
     * @return Category
2398
     */
2399
    public function setGradeBooksToValidateInDependence($value)
2400
    {
2401
        $this->gradeBooksToValidateInDependence = $value;
2402
2403
        return $this;
2404
    }
2405
2406
    /**
2407
     * Return HTML code with links to download and view certificate.
2408
     *
2409
     * @return string
2410
     */
2411
    public static function getDownloadCertificateBlock(array $certificate)
2412
    {
2413
        if (!isset($certificate['pdf_url'])) {
2414
            return '';
2415
        }
2416
2417
        $downloadLink = Display::toolbarButton(
2418
            get_lang('Download certificate in PDF'),
2419
            $certificate['pdf_url'],
2420
            'file-pdf-box'
2421
        );
2422
        $viewLink = $certificate['certificate_link'];
2423
2424
        return "
2425
            <div class='panel panel-default'>
2426
                <div class='panel-body'>
2427
                    <h3 class='text-center'>".get_lang('You can now download your certificate by clicking here')."</h3>
2428
                    <div class='text-center'>$downloadLink $viewLink</div>
2429
                </div>
2430
            </div>
2431
        ";
2432
    }
2433
2434
    /**
2435
     * Find a gradebook category by the certificate ID.
2436
     *
2437
     * @param int $id certificate id
2438
     *
2439
     * @throws \Doctrine\ORM\NonUniqueResultException
2440
     *
2441
     * @return Category|null
2442
     */
2443
    public static function findByCertificate($id)
2444
    {
2445
        $category = Database::getManager()
2446
            ->createQuery('SELECT c.catId FROM ChamiloCoreBundle:GradebookCertificate c WHERE c.id = :id')
2447
            ->setParameters(['id' => $id])
2448
            ->getOneOrNullResult();
2449
2450
        if (empty($category)) {
2451
            return null;
2452
        }
2453
2454
        $category = self::load($category['catId']);
2455
2456
        if (empty($category)) {
2457
            return null;
2458
        }
2459
2460
        return $category[0];
2461
    }
2462
2463
    /**
2464
     * @param int $value
2465
     */
2466
    public function setDocumentId($value)
2467
    {
2468
        $this->documentId = (int) $value;
2469
    }
2470
2471
    /**
2472
     * @return int
2473
     */
2474
    public function getDocumentId()
2475
    {
2476
        return $this->documentId;
2477
    }
2478
2479
    /**
2480
     * Get the remaining weight in root category.
2481
     *
2482
     * @return int
2483
     */
2484
    public function getRemainingWeight()
2485
    {
2486
        $subCategories = $this->get_subcategories();
2487
2488
        $subWeight = 0;
2489
2490
        /** @var Category $subCategory */
2491
        foreach ($subCategories as $subCategory) {
2492
            $subWeight += $subCategory->get_weight();
2493
        }
2494
2495
        return $this->weight - $subWeight;
2496
    }
2497
2498
    /**
2499
     * @return int
2500
     */
2501
    public function getCourseId()
2502
    {
2503
        return $this->courseId;
2504
    }
2505
2506
    /**
2507
     * Sets both the course ID and course code. If course ID is empty, set both to null;
2508
     * @param ?int $courseId
2509
     *
2510
     * @return Category
2511
     */
2512
    public function setCourseId(?int $courseId = null): Category
2513
    {
2514
        $courseInfo = api_get_course_info_by_id($courseId);
2515
        if (!empty($courseInfo)) {
2516
            $this->course_code = $courseInfo['code'];
2517
            $this->courseId = $courseId;
2518
        } else {
2519
            $this->course_code = null;
2520
            $this->courseId = null;
2521
        }
2522
2523
        return $this;
2524
    }
2525
2526
    /**
2527
     * @return Category
2528
     */
2529
    private static function create_root_category()
2530
    {
2531
        $cat = new Category();
2532
        $cat->set_id(0);
2533
        $cat->set_name(get_lang('Main folder'));
2534
        $cat->set_description('');
2535
        $cat->set_user_id(0);
2536
        $cat->setCourseId(0);
2537
        $cat->set_parent_id(0);
2538
        $cat->set_weight(0);
2539
        $cat->set_visible(1);
2540
        $cat->setGenerateCertificates(0);
2541
        $cat->setIsRequirement(0);
2542
2543
        return $cat;
2544
    }
2545
2546
    /**
2547
     * @param ?Doctrine\DBAL\Result $result
2548
     *
2549
     * @return array
2550
     * @throws \Doctrine\DBAL\Exception
2551
     */
2552
    private static function create_category_objects_from_sql_result(?Doctrine\DBAL\Result $result)
2553
    {
2554
        $categories = [];
2555
        $allow = ('true' === api_get_setting('gradebook.allow_gradebook_stats'));
2556
        if ($allow) {
2557
            $em = Database::getManager();
2558
            $repo = $em->getRepository(GradebookCategory::class);
2559
        }
2560
2561
        if (!empty($result)) {
2562
            while ($data = Database::fetch_array($result)) {
2563
                $cat = new Category();
2564
                $cat->set_id($data['id']);
2565
                $cat->set_name($data['title']);
2566
                $cat->set_description($data['description']);
2567
                $cat->set_user_id($data['user_id']);
2568
                $cat->setCourseId($data['c_id']);
2569
                $cat->set_parent_id($data['parent_id']);
2570
                $cat->set_weight($data['weight']);
2571
                $cat->set_visible($data['visible']);
2572
                $cat->set_session_id($data['session_id']);
2573
                $cat->set_certificate_min_score($data['certif_min_score']);
2574
                $cat->set_grade_model_id((int) $data['grade_model_id']);
2575
                $cat->set_locked($data['locked']);
2576
                $cat->setGenerateCertificates($data['generate_certificates']);
2577
                $cat->setIsRequirement($data['is_requirement']);
2578
                //$cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2579
                $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2580
                $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2581
                $cat->setDocumentId($data['document_id']);
2582
                if ($allow) {
2583
                    $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...
2584
                }
2585
2586
                $categories[] = $cat;
2587
            }
2588
        }
2589
2590
        return $categories;
2591
    }
2592
2593
    /**
2594
     * Internal function used by get_target_categories().
2595
     *
2596
     * @param array $targets
2597
     * @param int   $level
2598
     * @param int   $catid
2599
     *
2600
     * @return array
2601
     */
2602
    private function addTargetSubcategories($targets, $level, $catid)
2603
    {
2604
        $subcats = self::load(null, null, 0, $catid);
2605
        foreach ($subcats as $cat) {
2606
            if ($this->can_be_moved_to_cat($cat)) {
2607
                $targets[] = [
2608
                    $cat->get_id(),
2609
                    $cat->get_name(),
2610
                    $level + 1,
2611
                ];
2612
                $targets = $this->addTargetSubcategories(
2613
                    $targets,
2614
                    $level + 1,
2615
                    $cat->get_id()
2616
                );
2617
            }
2618
        }
2619
2620
        return $targets;
2621
    }
2622
2623
    /**
2624
     * Internal function used by get_target_categories() and addTargetSubcategories()
2625
     * Can this category be moved to the given category ?
2626
     * Impossible when origin and target are the same... children won't be processed
2627
     * either. (a category can't be moved to one of its own children).
2628
     */
2629
    private function can_be_moved_to_cat($cat)
2630
    {
2631
        return $cat->get_id() != $this->get_id();
2632
    }
2633
2634
    /**
2635
     * Internal function used by move_to_cat().
2636
     */
2637
    private function applyCourseCodeToChildren()
2638
    {
2639
        $cats = self::load(null, null, 0, $this->id, null);
2640
        $evals = Evaluation::load(null, null, 0, $this->id, null);
2641
        $links = LinkFactory::load(
2642
            null,
2643
            null,
2644
            null,
2645
            null,
2646
            0,
2647
            $this->id,
2648
            null
2649
        );
2650
        /** @var Category $cat */
2651
        foreach ($cats as $cat) {
2652
            $cat->setCourseId($this->getCourseId());
2653
            $cat->save();
2654
            $cat->applyCourseCodeToChildren();
2655
        }
2656
2657
        foreach ($evals as $eval) {
2658
            $eval->setCourseId($this->getCourseId());
2659
            $eval->save();
2660
        }
2661
2662
        foreach ($links as $link) {
2663
            $link->delete();
2664
        }
2665
    }
2666
2667
    /**
2668
     * Internal function used by get_tree().
2669
     *
2670
     * @param int      $level
2671
     * @param int|null $visible
2672
     *
2673
     * @return array
2674
     */
2675
    private function add_subtree($targets, $level, $catid, $visible)
2676
    {
2677
        $subcats = self::load(null, null, 0, $catid, $visible);
2678
2679
        if (!empty($subcats)) {
2680
            foreach ($subcats as $cat) {
2681
                $targets[] = [
2682
                    $cat->get_id(),
2683
                    $cat->get_name(),
2684
                    $level + 1,
2685
                ];
2686
                $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

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