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);
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++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
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