Passed
Push — translations ( 6cd971...bddd68 )
by Yannick
35:39 queued 27:44
created

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

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

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

2651
                /** @scrutinizer ignore-call */ 
2652
                $targets = self::add_subtree(
Loading history...
2652
                    $targets,
2653
                    $level + 1,
2654
                    $cat->get_id(),
2655
                    $visible
2656
                );
2657
            }
2658
        }
2659
2660
        return $targets;
2661
    }
2662
2663
    /**
2664
     * Calculate the current score on a gradebook category for a user.
2665
     *
2666
     * @return float The score
2667
     */
2668
    private static function calculateCurrentScore(int $userId, GradebookCategory $category)
2669
    {
2670
        if (null === $category) {
2671
            return 0;
2672
        }
2673
2674
        $categoryList = self::load($category->getId());
2675
        $category = $categoryList[0] ?? null;
2676
2677
        if (null === $category) {
2678
            return 0;
2679
        }
2680
2681
        $courseEvaluations = $category->get_evaluations($userId, true);
2682
        $courseLinks = $category->get_links($userId, true);
2683
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2684
        $count = count($evaluationsAndLinks);
2685
        if (empty($count)) {
2686
            return 0;
2687
        }
2688
2689
        $categoryScore = 0;
2690
        for ($i = 0; $i < $count; $i++) {
2691
            /** @var AbstractLink $item */
2692
            $item = $evaluationsAndLinks[$i];
2693
            // Set session id from category
2694
            $item->set_session_id($category->get_session_id());
2695
            $score = $item->calc_score($userId);
2696
            $itemValue = 0;
2697
            if (!empty($score)) {
2698
                $divider = 0 == $score[1] ? 1 : $score[1];
2699
                $itemValue = $score[0] / $divider * $item->get_weight();
2700
            }
2701
2702
            $categoryScore += $itemValue;
2703
        }
2704
2705
        return api_float_val($categoryScore);
2706
    }
2707
}
2708