Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

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

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

1464
            /** @scrutinizer ignore-call */ 
1465
            $cats = self::get_root_categories_for_teacher($user);
Loading history...
1465
            foreach ($cats as $cat) {
1466
                $targets[] = [
1467
                    $cat->get_id(),
1468
                    $cat->get_name(),
1469
                    $level + 1,
1470
                ];
1471
                $targets = $this->add_subtree(
1472
                    $targets,
1473
                    $level + 1,
1474
                    $cat->get_id(),
1475
                    null
1476
                );
1477
            }
1478
        } else {
1479
            // student
1480
            $cats = $this->get_root_categories_for_student(api_get_user_id());
1481
            foreach ($cats as $cat) {
1482
                $targets[] = [
1483
                    $cat->get_id(),
1484
                    $cat->get_name(),
1485
                    $level + 1,
1486
                ];
1487
                $targets = $this->add_subtree(
1488
                    $targets,
1489
                    $level + 1,
1490
                    $cat->get_id(),
1491
                    1
1492
                );
1493
            }
1494
        }
1495
1496
        return $targets;
1497
    }
1498
1499
    /**
1500
     * Generate an array of courses that a teacher hasn't created a category for.
1501
     *
1502
     * @param int $user_id
1503
     *
1504
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1505
     */
1506
    public function get_not_created_course_categories($user_id)
1507
    {
1508
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1509
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1510
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1511
1512
        $user_id = (int) $user_id;
1513
1514
        $sql = 'SELECT DISTINCT(code), title
1515
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1516
                WHERE
1517
                    cc.id = cu.c_id AND
1518
                    cu.status = '.COURSEMANAGER;
1519
1520
        if (!api_is_platform_admin()) {
1521
            $sql .= ' AND cu.user_id = '.$user_id;
1522
        }
1523
        $sql .= ' AND cc.code NOT IN
1524
             (
1525
                SELECT course_code FROM '.$tbl_grade_categories.'
1526
                WHERE
1527
                    parent_id = 0 AND
1528
                    course_code IS NOT NULL
1529
                )';
1530
        $result = Database::query($sql);
1531
1532
        $cats = [];
1533
        while ($data = Database::fetch_array($result)) {
1534
            $cats[] = [$data['code'], $data['title']];
1535
        }
1536
1537
        return $cats;
1538
    }
1539
1540
    /**
1541
     * Generate an array of all courses that a teacher is admin of.
1542
     *
1543
     * @param int $user_id
1544
     *
1545
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1546
     */
1547
    public function get_all_courses($user_id)
1548
    {
1549
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1550
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1551
        $sql = 'SELECT DISTINCT(code), title
1552
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1553
                WHERE cc.id = cu.c_id AND cu.status = '.COURSEMANAGER;
1554
        if (!api_is_platform_admin()) {
1555
            $sql .= ' AND cu.user_id = '.intval($user_id);
1556
        }
1557
1558
        $result = Database::query($sql);
1559
        $cats = [];
1560
        while ($data = Database::fetch_array($result)) {
1561
            $cats[] = [$data['code'], $data['title']];
1562
        }
1563
1564
        return $cats;
1565
    }
1566
1567
    /**
1568
     * Apply the same visibility to every subcategory, evaluation and link.
1569
     */
1570
    public function apply_visibility_to_children()
1571
    {
1572
        $cats = self::load(null, null, null, $this->id, null);
1573
        $evals = Evaluation::load(null, null, null, $this->id, null);
1574
        $links = LinkFactory::load(
1575
            null,
1576
            null,
1577
            null,
1578
            null,
1579
            null,
1580
            $this->id,
1581
            null
1582
        );
1583
        if (!empty($cats)) {
1584
            foreach ($cats as $cat) {
1585
                $cat->set_visible($this->is_visible());
1586
                $cat->save();
1587
                $cat->apply_visibility_to_children();
1588
            }
1589
        }
1590
        if (!empty($evals)) {
1591
            foreach ($evals as $eval) {
1592
                $eval->set_visible($this->is_visible());
1593
                $eval->save();
1594
            }
1595
        }
1596
        if (!empty($links)) {
1597
            foreach ($links as $link) {
1598
                $link->set_visible($this->is_visible());
1599
                $link->save();
1600
            }
1601
        }
1602
    }
1603
1604
    /**
1605
     * Check if a category contains evaluations with a result for a given student.
1606
     *
1607
     * @param int $studentId
1608
     *
1609
     * @return bool
1610
     */
1611
    public function hasEvaluationsWithStudentResults($studentId)
1612
    {
1613
        $evals = Evaluation::get_evaluations_with_result_for_student(
1614
            $this->id,
1615
            $studentId
1616
        );
1617
        if (0 != count($evals)) {
1618
            return true;
1619
        } else {
1620
            $cats = self::load(
1621
                null,
1622
                null,
1623
                null,
1624
                $this->id,
1625
                api_is_allowed_to_edit() ? null : 1
1626
            );
1627
1628
            /** @var Category $cat */
1629
            foreach ($cats as $cat) {
1630
                if ($cat->hasEvaluationsWithStudentResults($studentId)) {
1631
                    return true;
1632
                }
1633
            }
1634
1635
            return false;
1636
        }
1637
    }
1638
1639
    /**
1640
     * Retrieve all categories inside a course independent category
1641
     * that should be visible to a student.
1642
     *
1643
     * @param int   $categoryId parent category
1644
     * @param int   $studentId
1645
     * @param array $cats       optional: if defined, the categories will be added to this array
1646
     *
1647
     * @return array
1648
     */
1649
    public function getIndependentCategoriesWithStudentResult(
1650
        $categoryId,
1651
        $studentId,
1652
        $cats = []
1653
    ) {
1654
        $creator = api_is_allowed_to_edit() && !api_is_platform_admin() ? api_get_user_id() : null;
1655
1656
        $categories = self::load(
1657
            null,
1658
            $creator,
1659
            '0',
1660
            $categoryId,
1661
            api_is_allowed_to_edit() ? null : 1
1662
        );
1663
1664
        if (!empty($categories)) {
1665
            /** @var Category $category */
1666
            foreach ($categories as $category) {
1667
                if ($category->hasEvaluationsWithStudentResults($studentId)) {
1668
                    $cats[] = $category;
1669
                }
1670
            }
1671
        }
1672
1673
        return $cats;
1674
    }
1675
1676
    /**
1677
     * Return the session id (in any case, even if it's null or 0).
1678
     *
1679
     * @return int Session id (can be null)
1680
     */
1681
    public function get_session_id()
1682
    {
1683
        return $this->session_id;
1684
    }
1685
1686
    /**
1687
     * Get appropriate subcategories visible for the user (and optionally the course and session).
1688
     *
1689
     * @param int    $studentId   student id (default: all students)
1690
     * @param string $course_code Course code (optional)
1691
     * @param int    $session_id  Session ID (optional)
1692
     * @param bool   $order
1693
     *
1694
     * @return array Array of subcategories
1695
     */
1696
    public function get_subcategories(
1697
        $studentId = null,
1698
        $course_code = null,
1699
        $session_id = null,
1700
        $order = null
1701
    ) {
1702
        // 1 student
1703
        if (isset($studentId)) {
1704
            // Special case: this is the root
1705
            if (0 == $this->id) {
1706
                return $this->get_root_categories_for_student($studentId, $course_code, $session_id);
1707
            } else {
1708
                return self::load(
1709
                    null,
1710
                    null,
1711
                    $course_code,
1712
                    $this->id,
1713
                    api_is_allowed_to_edit() ? null : 1,
1714
                    $session_id,
1715
                    $order
1716
                );
1717
            }
1718
        } else {
1719
            // All students
1720
            // Course admin
1721
            if (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1722
                // root
1723
                if (0 == $this->id) {
1724
                    // inside a course
1725
                    return $this->get_root_categories_for_teacher(
1726
                        api_get_user_id(),
1727
                        $course_code,
1728
                        $session_id,
1729
                        false
1730
                    );
1731
                } elseif (!empty($this->course_code)) {
1732
                    return self::load(
1733
                        null,
1734
                        null,
1735
                        $this->course_code,
1736
                        $this->id,
1737
                        null,
1738
                        $session_id,
1739
                        $order
1740
                    );
1741
                } elseif (!empty($course_code)) {
1742
                    // course independent
1743
                    return self::load(
1744
                        null,
1745
                        null,
1746
                        $course_code,
1747
                        $this->id,
1748
                        null,
1749
                        $session_id,
1750
                        $order
1751
                    );
1752
                } else {
1753
                    return self::load(
1754
                        null,
1755
                        api_get_user_id(),
1756
                        0,
1757
                        $this->id,
1758
                        null
1759
                    );
1760
                }
1761
            } elseif (api_is_platform_admin()) {
1762
                // platform admin
1763
                // we explicitly avoid listing subcats from another session
1764
                return self::load(
1765
                    null,
1766
                    null,
1767
                    $course_code,
1768
                    $this->id,
1769
                    null,
1770
                    $session_id,
1771
                    $order
1772
                );
1773
            }
1774
        }
1775
1776
        return [];
1777
    }
1778
1779
    /**
1780
     * Get appropriate evaluations visible for the user.
1781
     *
1782
     * @param int    $studentId   student id (default: all students)
1783
     * @param bool   $recursive   process subcategories (default: no recursion)
1784
     * @param string $course_code
1785
     * @param int    $sessionId
1786
     *
1787
     * @return array
1788
     */
1789
    public function get_evaluations(
1790
        $studentId = null,
1791
        $recursive = false,
1792
        $course_code = '',
1793
        $sessionId = 0
1794
    ) {
1795
        $evals = [];
1796
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1797
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1798
1799
        // 1 student
1800
        if (isset($studentId) && !empty($studentId)) {
1801
            // Special case: this is the root
1802
            if (0 == $this->id) {
1803
                $evals = Evaluation::get_evaluations_with_result_for_student(
1804
                    0,
1805
                    $studentId
1806
                );
1807
            } else {
1808
                $evals = Evaluation::load(
1809
                    null,
1810
                    null,
1811
                    $course_code,
1812
                    $this->id,
1813
                    api_is_allowed_to_edit() ? null : 1
1814
                );
1815
            }
1816
        } else {
1817
            // All students
1818
            // course admin
1819
            if ((api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) &&
1820
                !api_is_platform_admin()
1821
            ) {
1822
                // root
1823
                if (0 == $this->id) {
1824
                    $evals = Evaluation::load(
1825
                        null,
1826
                        api_get_user_id(),
1827
                        null,
1828
                        $this->id,
1829
                        null
1830
                    );
1831
                } elseif (isset($this->course_code) &&
1832
                    !empty($this->course_code)
1833
                ) {
1834
                    // inside a course
1835
                    $evals = Evaluation::load(
1836
                        null,
1837
                        null,
1838
                        $course_code,
1839
                        $this->id,
1840
                        null
1841
                    );
1842
                } else {
1843
                    // course independent
1844
                    $evals = Evaluation::load(
1845
                        null,
1846
                        api_get_user_id(),
1847
                        null,
1848
                        $this->id,
1849
                        null
1850
                    );
1851
                }
1852
            } else {
1853
                $evals = Evaluation::load(
1854
                    null,
1855
                    null,
1856
                    $course_code,
1857
                    $this->id,
1858
                    null
1859
                );
1860
            }
1861
        }
1862
1863
        if ($recursive) {
1864
            $subcats = $this->get_subcategories(
1865
                $studentId,
1866
                $course_code,
1867
                $sessionId
1868
            );
1869
1870
            if (!empty($subcats)) {
1871
                foreach ($subcats as $subcat) {
1872
                    $subevals = $subcat->get_evaluations(
1873
                        $studentId,
1874
                        true,
1875
                        $course_code
1876
                    );
1877
                    $evals = array_merge($evals, $subevals);
1878
                }
1879
            }
1880
        }
1881
1882
        return $evals;
1883
    }
1884
1885
    /**
1886
     * Get appropriate links visible for the user.
1887
     *
1888
     * @param int    $studentId   student id (default: all students)
1889
     * @param bool   $recursive   process subcategories (default: no recursion)
1890
     * @param string $course_code
1891
     * @param int    $sessionId
1892
     *
1893
     * @return array
1894
     */
1895
    public function get_links(
1896
        $studentId = null,
1897
        $recursive = false,
1898
        $course_code = '',
1899
        $sessionId = 0
1900
    ) {
1901
        $links = [];
1902
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1903
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1904
1905
        // no links in root or course independent categories
1906
        if (0 == $this->id) {
1907
        } elseif (isset($studentId)) {
1908
            // 1 student $studentId
1909
            $links = LinkFactory::load(
1910
                null,
1911
                null,
1912
                null,
1913
                null,
1914
                $course_code,
1915
                $this->id,
1916
                api_is_allowed_to_edit() ? null : 1
1917
            );
1918
        } else {
1919
            // All students -> only for course/platform admin
1920
            $links = LinkFactory::load(
1921
                null,
1922
                null,
1923
                null,
1924
                null,
1925
                $course_code,
1926
                $this->id,
1927
                null
1928
            );
1929
        }
1930
1931
        if ($recursive) {
1932
            $subcats = $this->get_subcategories(
1933
                $studentId,
1934
                $course_code,
1935
                $sessionId
1936
            );
1937
            if (!empty($subcats)) {
1938
                /** @var Category $subcat */
1939
                foreach ($subcats as $subcat) {
1940
                    $sublinks = $subcat->get_links(
1941
                        $studentId,
1942
                        false,
1943
                        $course_code,
1944
                        $sessionId
1945
                    );
1946
                    $links = array_merge($links, $sublinks);
1947
                }
1948
            }
1949
        }
1950
1951
        return $links;
1952
    }
1953
1954
    /**
1955
     * Get all the categories from with the same given direct parent.
1956
     *
1957
     * @param int $catId Category parent ID
1958
     *
1959
     * @return array Array of Category objects
1960
     */
1961
    public function getCategories($catId)
1962
    {
1963
        $catId = (int) $catId;
1964
        $tblGradeCategories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1965
        $sql = 'SELECT * FROM '.$tblGradeCategories.'
1966
                WHERE parent_id = '.$catId;
1967
1968
        $result = Database::query($sql);
1969
        $categories = self::create_category_objects_from_sql_result($result);
1970
1971
        return $categories;
1972
    }
1973
1974
    /**
1975
     * Gets the type for the current object.
1976
     *
1977
     * @return string 'C' to represent "Category" object type
1978
     */
1979
    public function get_item_type()
1980
    {
1981
        return 'C';
1982
    }
1983
1984
    /**
1985
     * @param array $skills
1986
     */
1987
    public function set_skills($skills)
1988
    {
1989
        $this->skills = $skills;
1990
    }
1991
1992
    public function get_date()
1993
    {
1994
        return null;
1995
    }
1996
1997
    /**
1998
     * @return string
1999
     */
2000
    public function get_icon_name()
2001
    {
2002
        return 'cat';
2003
    }
2004
2005
    /**
2006
     * Find category by name.
2007
     *
2008
     * @param string $name_mask search string
2009
     *
2010
     * @return array category objects matching the search criterium
2011
     */
2012
    public function find_category($name_mask, $allcat)
2013
    {
2014
        $categories = [];
2015
        foreach ($allcat as $search_cat) {
2016
            if (!(strpos(strtolower($search_cat->get_name()), strtolower($name_mask)) === false)) {
2017
                $categories[] = $search_cat;
2018
            }
2019
        }
2020
2021
        return $categories;
2022
    }
2023
2024
    /**
2025
     * This function, locks a category , only one who can unlock it is
2026
     * the platform administrator.
2027
     *
2028
     * @param int locked 1 or unlocked 0
2029
2030
     *
2031
     * @return bool|null
2032
     * */
2033
    public function lock($locked)
2034
    {
2035
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
2036
        $sql = "UPDATE $table SET locked = '".intval($locked)."'
2037
                WHERE id='".intval($this->id)."'";
2038
        Database::query($sql);
2039
    }
2040
2041
    /**
2042
     * @param $locked
2043
     */
2044
    public function lockAllItems($locked)
2045
    {
2046
        if ('true' == api_get_setting('gradebook_locking_enabled')) {
2047
            $this->lock($locked);
2048
            $evals_to_lock = $this->get_evaluations();
2049
            if (!empty($evals_to_lock)) {
2050
                foreach ($evals_to_lock as $item) {
2051
                    $item->lock($locked);
2052
                }
2053
            }
2054
2055
            $link_to_lock = $this->get_links();
2056
            if (!empty($link_to_lock)) {
2057
                foreach ($link_to_lock as $item) {
2058
                    $item->lock($locked);
2059
                }
2060
            }
2061
2062
            $event_type = LOG_GRADEBOOK_UNLOCKED;
2063
            if (1 == $locked) {
2064
                $event_type = LOG_GRADEBOOK_LOCKED;
2065
            }
2066
            Event::addEvent($event_type, LOG_GRADEBOOK_ID, $this->id);
2067
        }
2068
    }
2069
2070
    /**
2071
     * Generates a certificate for this user if everything matches.
2072
     *
2073
     * @param int  $category_id            gradebook id
2074
     * @param int  $user_id
2075
     * @param bool $sendNotification
2076
     * @param bool $skipGenerationIfExists
2077
     *
2078
     * @return array
2079
     */
2080
    public static function generateUserCertificate(
2081
        $category_id,
2082
        $user_id,
2083
        $sendNotification = false,
2084
        $skipGenerationIfExists = false
2085
    ) {
2086
        $user_id = (int) $user_id;
2087
        $category_id = (int) $category_id;
2088
2089
        // Generating the total score for a course
2090
        $category = self::load(
2091
            $category_id,
2092
            null,
2093
            null,
2094
            null,
2095
            null,
2096
            null,
2097
            false
2098
        );
2099
2100
        /** @var Category $category */
2101
        $category = $category[0];
2102
2103
        if (empty($category)) {
2104
            return false;
2105
        }
2106
2107
        $sessionId = $category->get_session_id();
2108
        $courseCode = $category->get_course_code();
2109
        $courseInfo = api_get_course_info($courseCode);
2110
        $courseId = $courseInfo['real_id'];
2111
2112
        $userFinishedCourse = self::userFinishedCourse(
2113
            $user_id,
2114
            $category,
2115
            true
2116
        );
2117
2118
        if (!$userFinishedCourse) {
2119
            return false;
2120
        }
2121
2122
        $skillToolEnabled = Skill::hasAccessToUserSkill(
2123
            api_get_user_id(),
2124
            $user_id
2125
        );
2126
2127
        $userHasSkills = false;
2128
        if ($skillToolEnabled) {
2129
            $skill = new Skill();
2130
            $skill->addSkillToUser(
2131
                $user_id,
2132
                $category,
2133
                $courseId,
2134
                $sessionId
2135
            );
2136
2137
            $objSkillRelUser = new SkillRelUser();
2138
            $userSkills = $objSkillRelUser->getUserSkills(
2139
                $user_id,
2140
                $courseId,
2141
                $sessionId
2142
            );
2143
            $userHasSkills = !empty($userSkills);
2144
        }
2145
2146
        // Block certification links depending gradebook configuration (generate certifications)
2147
        if (empty($category->getGenerateCertificates())) {
2148
            if ($userHasSkills) {
2149
                return [
2150
                    'badge_link' => Display::toolbarButton(
2151
                        get_lang('ExportBadges'),
2152
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2153
                        'external-link'
2154
                    ),
2155
                ];
2156
            }
2157
2158
            return false;
2159
        }
2160
2161
        $scoretotal = $category->calc_score($user_id);
2162
2163
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2164
        // file load this variable as a global
2165
        $scoredisplay = ScoreDisplay::instance();
2166
        $my_score_in_gradebook = $scoredisplay->display_score(
2167
            $scoretotal,
2168
            SCORE_SIMPLE
2169
        );
2170
2171
        $my_certificate = GradebookUtils::get_certificate_by_user_id(
2172
            $category_id,
2173
            $user_id
2174
        );
2175
2176
        if ($skipGenerationIfExists && !empty($my_certificate)) {
2177
            return false;
2178
        }
2179
2180
        if (empty($my_certificate)) {
2181
            GradebookUtils::registerUserInfoAboutCertificate(
2182
                $category_id,
2183
                $user_id,
2184
                $my_score_in_gradebook,
2185
                api_get_utc_datetime()
2186
            );
2187
            $my_certificate = GradebookUtils::get_certificate_by_user_id(
2188
                $category_id,
2189
                $user_id
2190
            );
2191
        }
2192
2193
        $html = [];
2194
        if (!empty($my_certificate)) {
2195
            $certificate_obj = new Certificate(
2196
                $my_certificate['id'],
2197
                0,
2198
                $sendNotification
2199
            );
2200
2201
            $fileWasGenerated = $certificate_obj->isHtmlFileGenerated();
2202
2203
            // Fix when using custom certificate BT#15937
2204
            if (api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate') === 'true') {
2205
                $infoCertificate = CustomCertificatePlugin::getCertificateData($my_certificate['id'], $user_id);
2206
                if (!empty($infoCertificate)) {
2207
                    $fileWasGenerated = true;
2208
                }
2209
            }
2210
2211
            if (!empty($fileWasGenerated)) {
2212
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'].'&user_id='.$user_id;
2213
                $certificates = Display::toolbarButton(
2214
                    get_lang('DisplayCertificate'),
2215
                    $url,
2216
                    'eye',
2217
                    'primary',
2218
                    ['target' => '_blank']
2219
                );
2220
2221
                $exportToPDF = Display::url(
2222
                    Display::return_icon(
2223
                        'pdf.png',
2224
                        get_lang('ExportToPDF'),
2225
                        [],
2226
                        ICON_SIZE_MEDIUM
2227
                    ),
2228
                    "$url&action=export"
2229
                );
2230
2231
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2232
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2233
                if ($hideExportLink === 'true' || (api_is_student() && $hideExportLinkStudent === 'true')) {
2234
                    $exportToPDF = null;
2235
                }
2236
2237
                $html = [
2238
                    'certificate_link' => $certificates,
2239
                    'pdf_link' => $exportToPDF,
2240
                    'pdf_url' => "$url&action=export",
2241
                ];
2242
            }
2243
2244
            if ($skillToolEnabled && $userHasSkills) {
2245
                $html['badge_link'] = Display::toolbarButton(
2246
                    get_lang('ExportBadges'),
2247
                    api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2248
                    'external-link'
2249
                );
2250
            }
2251
2252
            return $html;
2253
        }
2254
    }
2255
2256
    /**
2257
     * @param int   $catId
2258
     * @param array $userList
2259
     */
2260
    public static function generateCertificatesInUserList($catId, $userList)
2261
    {
2262
        if (!empty($userList)) {
2263
            foreach ($userList as $userInfo) {
2264
                self::generateUserCertificate($catId, $userInfo['user_id']);
2265
            }
2266
        }
2267
    }
2268
2269
    /**
2270
     * @param int         $catId
2271
     * @param array       $userList
2272
     * @param string|null $courseCode
2273
     * @param bool        $generateToFile
2274
     * @param string      $pdfName
2275
     *
2276
     * @throws \MpdfException
2277
     */
2278
    public static function exportAllCertificates(
2279
        $catId,
2280
        $userList = [],
2281
        $courseCode = null,
2282
        $generateToFile = false,
2283
        $pdfName = ''
2284
    ) {
2285
        $orientation = api_get_configuration_value('certificate_pdf_orientation');
2286
2287
        $params['orientation'] = 'landscape';
2288
        if (!empty($orientation)) {
2289
            $params['orientation'] = $orientation;
2290
        }
2291
2292
        $params['left'] = 0;
2293
        $params['right'] = 0;
2294
        $params['top'] = 0;
2295
        $params['bottom'] = 0;
2296
        $pageFormat = $params['orientation'] === 'landscape' ? 'A4-L' : 'A4';
2297
        $pdf = new PDF($pageFormat, $params['orientation'], $params);
2298
        if (api_get_configuration_value('add_certificate_pdf_footer')) {
2299
            $pdf->setCertificateFooter();
2300
        }
2301
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2302
        $certificate_path_list = [];
2303
2304
        if (!empty($certificate_list)) {
2305
            foreach ($certificate_list as $index => $value) {
2306
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2307
                    $value['user_id'],
2308
                    $catId
2309
                );
2310
                foreach ($list_certificate as $value_certificate) {
2311
                    $certificate_obj = new Certificate($value_certificate['id']);
2312
                    $certificate_obj->generate(['hide_print_button' => true]);
2313
                    if ($certificate_obj->isHtmlFileGenerated()) {
2314
                        $certificate_path_list[] = $certificate_obj->html_file;
2315
                    }
2316
                }
2317
            }
2318
        }
2319
2320
        if (!empty($certificate_path_list)) {
2321
            // Print certificates (without the common header/footer/watermark
2322
            //  stuff) and return as one multiple-pages PDF
2323
            $pdf->html_to_pdf(
2324
                $certificate_path_list,
2325
                empty($pdfName) ? get_lang('Certificates') : $pdfName,
2326
                $courseCode,
2327
                false,
2328
                false,
2329
                true,
2330
                '',
2331
                $generateToFile
2332
            );
2333
        }
2334
    }
2335
2336
    /**
2337
     * @param int $catId
2338
     */
2339
    public static function deleteAllCertificates($catId)
2340
    {
2341
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2342
        if (!empty($certificate_list)) {
2343
            foreach ($certificate_list as $index => $value) {
2344
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2345
                    $value['user_id'],
2346
                    $catId
2347
                );
2348
                foreach ($list_certificate as $value_certificate) {
2349
                    $certificate_obj = new Certificate($value_certificate['id']);
2350
                    $certificate_obj->delete(true);
2351
                }
2352
            }
2353
        }
2354
    }
2355
2356
    /**
2357
     * Check whether a user has finished a course by its gradebook.
2358
     *
2359
     * @param int       $userId           The user ID
2360
     * @param \Category $category         Optional. The gradebook category.
2361
     *                                    To check by the gradebook category
2362
     * @param bool      $recalculateScore Whether recalculate the score
2363
     *
2364
     * @return bool
2365
     */
2366
    public static function userFinishedCourse(
2367
        $userId,
2368
        Category $category,
2369
        $recalculateScore = false
2370
    ) {
2371
        if (empty($category)) {
2372
            return false;
2373
        }
2374
2375
        $currentScore = self::getCurrentScore(
2376
            $userId,
2377
            $category,
2378
            $recalculateScore
2379
        );
2380
2381
        $minCertificateScore = $category->getCertificateMinScore();
2382
2383
        return $currentScore >= $minCertificateScore;
2384
    }
2385
2386
    /**
2387
     * Get the current score (as percentage) on a gradebook category for a user.
2388
     *
2389
     * @param int      $userId      The user id
2390
     * @param Category $category    The gradebook category
2391
     * @param bool     $recalculate
2392
     *
2393
     * @return float The score
2394
     */
2395
    public static function getCurrentScore(
2396
        $userId,
2397
        $category,
2398
        $recalculate = false
2399
    ) {
2400
        if (empty($category)) {
2401
            return 0;
2402
        }
2403
2404
        if ($recalculate) {
2405
            return self::calculateCurrentScore(
2406
                $userId,
2407
                $category
2408
            );
2409
        }
2410
2411
        $resultData = Database::select(
2412
            '*',
2413
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2414
            [
2415
                'where' => [
2416
                    'category_id = ? AND user_id = ?' => [$category->get_id(), $userId],
2417
                ],
2418
                'order' => 'registered_at DESC',
2419
                'limit' => '1',
2420
            ],
2421
            'first'
2422
        );
2423
2424
        if (empty($resultData)) {
2425
            return 0;
2426
        }
2427
2428
        return $resultData['score'];
2429
    }
2430
2431
    /**
2432
     * Register the current score for a user on a category gradebook.
2433
     *
2434
     * @param float $score      The achieved score
2435
     * @param int   $userId     The user id
2436
     * @param int   $categoryId The gradebook category
2437
     *
2438
     * @return int The insert id
2439
     */
2440
    public static function registerCurrentScore($score, $userId, $categoryId)
2441
    {
2442
        return Database::insert(
2443
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2444
            [
2445
                'category_id' => intval($categoryId),
2446
                'user_id' => intval($userId),
2447
                'score' => api_float_val($score),
2448
                'registered_at' => api_get_utc_datetime(),
2449
            ]
2450
        );
2451
    }
2452
2453
    /**
2454
     * @return array
2455
     */
2456
    public function getStudentList()
2457
    {
2458
        return $this->studentList;
2459
    }
2460
2461
    /**
2462
     * @param array $list
2463
     */
2464
    public function setStudentList($list)
2465
    {
2466
        $this->studentList = $list;
2467
    }
2468
2469
    /**
2470
     * @return string
2471
     */
2472
    public static function getUrl()
2473
    {
2474
        $url = Session::read('gradebook_dest');
2475
        if (empty($url)) {
2476
            // We guess the link
2477
            $courseInfo = api_get_course_info();
2478
            if (!empty($courseInfo)) {
2479
                return api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2480
            } else {
2481
                return api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2482
            }
2483
        }
2484
2485
        return $url;
2486
    }
2487
2488
    /**
2489
     * Destination is index.php or gradebook.php.
2490
     *
2491
     * @param string $url
2492
     */
2493
    public static function setUrl($url)
2494
    {
2495
        switch ($url) {
2496
            case 'gradebook.php':
2497
                $url = api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2498
                break;
2499
            case 'index.php':
2500
                $url = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2501
                break;
2502
        }
2503
        Session::write('gradebook_dest', $url);
2504
    }
2505
2506
    /**
2507
     * @return int
2508
     */
2509
    public function getGradeBooksToValidateInDependence()
2510
    {
2511
        return $this->gradeBooksToValidateInDependence;
2512
    }
2513
2514
    /**
2515
     * @param int $value
2516
     *
2517
     * @return Category
2518
     */
2519
    public function setGradeBooksToValidateInDependence($value)
2520
    {
2521
        $this->gradeBooksToValidateInDependence = $value;
2522
2523
        return $this;
2524
    }
2525
2526
    /**
2527
     * Return HTML code with links to download and view certificate.
2528
     *
2529
     * @return string
2530
     */
2531
    public static function getDownloadCertificateBlock(array $certificate)
2532
    {
2533
        if (!isset($certificate['pdf_url'])) {
2534
            return '';
2535
        }
2536
2537
        $downloadLink = Display::toolbarButton(
2538
            get_lang('DownloadCertificatePdf'),
2539
            $certificate['pdf_url'],
2540
            'file-pdf-o'
2541
        );
2542
        $viewLink = $certificate['certificate_link'];
2543
2544
        return "
2545
            <div class='panel panel-default'>
2546
                <div class='panel-body'>
2547
                    <h3 class='text-center'>".get_lang('NowDownloadYourCertificateClickHere')."</h3>
2548
                    <div class='text-center'>$downloadLink $viewLink</div>
2549
                </div>
2550
            </div>
2551
        ";
2552
    }
2553
2554
    /**
2555
     * Find a gradebook category by the certificate ID.
2556
     *
2557
     * @param int $id certificate id
2558
     *
2559
     * @throws \Doctrine\ORM\NonUniqueResultException
2560
     *
2561
     * @return Category|null
2562
     */
2563
    public static function findByCertificate($id)
2564
    {
2565
        $category = Database::getManager()
2566
            ->createQuery('SELECT c.catId FROM ChamiloCoreBundle:GradebookCertificate c WHERE c.id = :id')
2567
            ->setParameters(['id' => $id])
2568
            ->getOneOrNullResult();
2569
2570
        if (empty($category)) {
2571
            return null;
2572
        }
2573
2574
        $category = self::load($category['catId']);
2575
2576
        if (empty($category)) {
2577
            return null;
2578
        }
2579
2580
        return $category[0];
2581
    }
2582
2583
    /**
2584
     * @param int $value
2585
     */
2586
    public function setDocumentId($value)
2587
    {
2588
        $this->documentId = (int) $value;
2589
    }
2590
2591
    /**
2592
     * @return int
2593
     */
2594
    public function getDocumentId()
2595
    {
2596
        return $this->documentId;
2597
    }
2598
2599
    /**
2600
     * Get the remaining weight in root category.
2601
     *
2602
     * @return int
2603
     */
2604
    public function getRemainingWeight()
2605
    {
2606
        $subCategories = $this->get_subcategories();
2607
2608
        $subWeight = 0;
2609
2610
        /** @var Category $subCategory */
2611
        foreach ($subCategories as $subCategory) {
2612
            $subWeight += $subCategory->get_weight();
2613
        }
2614
2615
        return $this->weight - $subWeight;
2616
    }
2617
2618
    /**
2619
     * @return Category
2620
     */
2621
    private static function create_root_category()
2622
    {
2623
        $cat = new Category();
2624
        $cat->set_id(0);
2625
        $cat->set_name(get_lang('RootCat'));
2626
        $cat->set_description(null);
2627
        $cat->set_user_id(0);
2628
        $cat->set_course_code(null);
2629
        $cat->set_parent_id(null);
2630
        $cat->set_weight(0);
2631
        $cat->set_visible(1);
2632
        $cat->setGenerateCertificates(0);
2633
        $cat->setIsRequirement(false);
2634
2635
        return $cat;
2636
    }
2637
2638
    /**
2639
     * @param Doctrine\DBAL\Driver\Statement|null $result
2640
     *
2641
     * @return array
2642
     */
2643
    private static function create_category_objects_from_sql_result($result)
2644
    {
2645
        $categories = [];
2646
        $allow = api_get_configuration_value('allow_gradebook_stats');
2647
        if ($allow) {
2648
            $em = Database::getManager();
2649
            $repo = $em->getRepository('ChamiloCoreBundle:GradebookCategory');
2650
        }
2651
2652
        while ($data = Database::fetch_array($result)) {
2653
            $cat = new Category();
2654
            $cat->set_id($data['id']);
2655
            $cat->set_name($data['name']);
2656
            $cat->set_description($data['description']);
2657
            $cat->set_user_id($data['user_id']);
2658
            $cat->set_course_code($data['course_code']);
2659
            $cat->set_parent_id($data['parent_id']);
2660
            $cat->set_weight($data['weight']);
2661
            $cat->set_visible($data['visible']);
2662
            $cat->set_session_id($data['session_id']);
2663
            $cat->set_certificate_min_score($data['certif_min_score']);
2664
            $cat->set_grade_model_id($data['grade_model_id']);
2665
            $cat->set_locked($data['locked']);
2666
            $cat->setGenerateCertificates($data['generate_certificates']);
2667
            $cat->setIsRequirement($data['is_requirement']);
2668
            $cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2669
            $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2670
            $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2671
            $cat->setDocumentId($data['document_id']);
2672
            if ($allow) {
2673
                $cat->entity = $repo->find($data['id']);
2674
            }
2675
2676
            $categories[] = $cat;
2677
        }
2678
2679
        return $categories;
2680
    }
2681
2682
    /**
2683
     * Internal function used by get_target_categories().
2684
     *
2685
     * @param array $targets
2686
     * @param int   $level
2687
     * @param int   $catid
2688
     *
2689
     * @return array
2690
     */
2691
    private function addTargetSubcategories($targets, $level, $catid)
2692
    {
2693
        $subcats = self::load(null, null, null, $catid);
2694
        foreach ($subcats as $cat) {
2695
            if ($this->can_be_moved_to_cat($cat)) {
2696
                $targets[] = [
2697
                    $cat->get_id(),
2698
                    $cat->get_name(),
2699
                    $level + 1,
2700
                ];
2701
                $targets = $this->addTargetSubcategories(
2702
                    $targets,
2703
                    $level + 1,
2704
                    $cat->get_id()
2705
                );
2706
            }
2707
        }
2708
2709
        return $targets;
2710
    }
2711
2712
    /**
2713
     * Internal function used by get_target_categories() and addTargetSubcategories()
2714
     * Can this category be moved to the given category ?
2715
     * Impossible when origin and target are the same... children won't be processed
2716
     * either. (a category can't be moved to one of its own children).
2717
     */
2718
    private function can_be_moved_to_cat($cat)
2719
    {
2720
        return $cat->get_id() != $this->get_id();
2721
    }
2722
2723
    /**
2724
     * Internal function used by move_to_cat().
2725
     */
2726
    private function applyCourseCodeToChildren()
2727
    {
2728
        $cats = self::load(null, null, null, $this->id, null);
2729
        $evals = Evaluation::load(null, null, null, $this->id, null);
2730
        $links = LinkFactory::load(
2731
            null,
2732
            null,
2733
            null,
2734
            null,
2735
            null,
2736
            $this->id,
2737
            null
2738
        );
2739
        /** @var Category $cat */
2740
        foreach ($cats as $cat) {
2741
            $cat->set_course_code($this->get_course_code());
2742
            $cat->save();
2743
            $cat->applyCourseCodeToChildren();
2744
        }
2745
2746
        foreach ($evals as $eval) {
2747
            $eval->set_course_code($this->get_course_code());
2748
            $eval->save();
2749
        }
2750
2751
        foreach ($links as $link) {
2752
            $link->delete();
2753
        }
2754
    }
2755
2756
    /**
2757
     * Internal function used by get_tree().
2758
     *
2759
     * @param int      $level
2760
     * @param int|null $visible
2761
     *
2762
     * @return array
2763
     */
2764
    private function add_subtree($targets, $level, $catid, $visible)
2765
    {
2766
        $subcats = self::load(null, null, null, $catid, $visible);
2767
2768
        if (!empty($subcats)) {
2769
            foreach ($subcats as $cat) {
2770
                $targets[] = [
2771
                    $cat->get_id(),
2772
                    $cat->get_name(),
2773
                    $level + 1,
2774
                ];
2775
                $targets = self::add_subtree(
2776
                    $targets,
2777
                    $level + 1,
2778
                    $cat->get_id(),
2779
                    $visible
2780
                );
2781
            }
2782
        }
2783
2784
        return $targets;
2785
    }
2786
2787
    /**
2788
     * Calculate the current score on a gradebook category for a user.
2789
     *
2790
     * @param int      $userId   The user id
2791
     * @param Category $category The gradebook category
2792
     *
2793
     * @return float The score
2794
     */
2795
    private static function calculateCurrentScore($userId, $category)
2796
    {
2797
        if (empty($category)) {
2798
            return 0;
2799
        }
2800
2801
        $courseEvaluations = $category->get_evaluations($userId, true);
2802
        $courseLinks = $category->get_links($userId, true);
2803
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2804
2805
        $categoryScore = 0;
2806
        for ($i = 0; $i < count($evaluationsAndLinks); $i++) {
2807
            /** @var AbstractLink $item */
2808
            $item = $evaluationsAndLinks[$i];
2809
            // Set session id from category
2810
            $item->set_session_id($category->get_session_id());
2811
            $score = $item->calc_score($userId);
2812
            $itemValue = 0;
2813
            if (!empty($score)) {
2814
                $divider = $score[1] == 0 ? 1 : $score[1];
2815
                $itemValue = $score[0] / $divider * $item->get_weight();
2816
            }
2817
2818
            $categoryScore += $itemValue;
2819
        }
2820
2821
        return api_float_val($categoryScore);
2822
    }
2823
}
2824