Passed
Push — master ( 6304f4...fc13ff )
by Julito
08:01
created

Category::getMinimumToValidate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\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 $courseId;
26
    private $parent;
27
    private $weight;
28
    private $visible;
29
    private $certificate_min_score;
30
    private $session_id;
31
    private $skills = [];
32
    private $grade_model_id;
33
    private $generateCertificates;
34
    private $isRequirement;
35
    private $courseDependency;
36
    private $minimumToValidate;
37
    private $documentId;
38
    /** @var int */
39
    private $gradeBooksToValidateInDependence;
40
41
    /**
42
     * Consctructor.
43
     */
44
    public function __construct()
45
    {
46
        $this->id = 0;
47
        $this->name = null;
48
        $this->description = '';
49
        $this->user_id = 0;
50
        $this->course_code = '';
51
        $this->courseId = 0;
52
        $this->parent = 0;
53
        $this->weight = 0;
54
        $this->visible = false;
55
        $this->certificate_min_score = 0;
56
        $this->session_id = 0;
57
        $this->grade_model_id = 0;
58
        $this->generateCertificates = false;
59
        $this->isRequirement = false;
60
        $this->courseDependency = [];
61
        $this->documentId = 0;
62
        $this->minimumToValidate = null;
63
    }
64
65
    /**
66
     * @return int
67
     */
68
    public function get_id()
69
    {
70
        return $this->id;
71
    }
72
73
    /**
74
     * @return string
75
     */
76
    public function get_name()
77
    {
78
        return $this->name;
79
    }
80
81
    /**
82
     * @return string
83
     */
84
    public function get_description()
85
    {
86
        return $this->description;
87
    }
88
89
    /**
90
     * @return int
91
     */
92
    public function get_user_id()
93
    {
94
        return $this->user_id;
95
    }
96
97
    /**
98
     * @return int|null
99
     */
100
    public function getCertificateMinScore()
101
    {
102
        if (!empty($this->certificate_min_score)) {
103
            return $this->certificate_min_score;
104
        }
105
106
        return null;
107
    }
108
109
    /**
110
     * @return string
111
     */
112
    public function get_course_code()
113
    {
114
        return $this->course_code;
115
    }
116
117
    /**
118
     * @return int
119
     */
120
    public function get_parent_id()
121
    {
122
        return $this->parent;
123
    }
124
125
    /**
126
     * @return int
127
     */
128
    public function get_weight()
129
    {
130
        return $this->weight;
131
    }
132
133
    /**
134
     * @return bool
135
     */
136
    public function is_locked()
137
    {
138
        return isset($this->locked) && 1 == $this->locked ? true : false;
139
    }
140
141
    /**
142
     * @return bool
143
     */
144
    public function is_visible()
145
    {
146
        return $this->visible;
147
    }
148
149
    /**
150
     * Get $isRequirement.
151
     *
152
     * @return int
153
     */
154
    public function getIsRequirement()
155
    {
156
        return $this->isRequirement;
157
    }
158
159
    /**
160
     * @param int $id
161
     */
162
    public function set_id($id)
163
    {
164
        $this->id = $id;
165
    }
166
167
    /**
168
     * @param string $name
169
     */
170
    public function set_name($name)
171
    {
172
        $this->name = $name;
173
    }
174
175
    /**
176
     * @param string $description
177
     */
178
    public function set_description($description)
179
    {
180
        $this->description = $description;
181
    }
182
183
    /**
184
     * @param int $user_id
185
     */
186
    public function set_user_id($user_id)
187
    {
188
        $this->user_id = $user_id;
189
    }
190
191
    /**
192
     * @param string $course_code
193
     */
194
    public function set_course_code($course_code)
195
    {
196
        $this->course_code = $course_code;
197
    }
198
199
    /**
200
     * @param float $min_score
201
     */
202
    public function set_certificate_min_score($min_score = null)
203
    {
204
        $this->certificate_min_score = $min_score;
205
    }
206
207
    /**
208
     * @param int $parent
209
     */
210
    public function set_parent_id($parent)
211
    {
212
        $this->parent = (int) $parent;
213
    }
214
215
    /**
216
     * Filters to int and sets the session ID.
217
     *
218
     * @param   int     The session ID from the Dokeos course session
219
     */
220
    public function set_session_id($session_id = 0)
221
    {
222
        $this->session_id = (int) $session_id;
223
    }
224
225
    /**
226
     * @param $weight
227
     */
228
    public function set_weight($weight)
229
    {
230
        $this->weight = $weight;
231
    }
232
233
    /**
234
     * @param $visible
235
     */
236
    public function set_visible($visible)
237
    {
238
        $this->visible = $visible;
239
    }
240
241
    /**
242
     * @param int $id
243
     */
244
    public function set_grade_model_id($id)
245
    {
246
        $this->grade_model_id = $id;
247
    }
248
249
    /**
250
     * @param $locked
251
     */
252
    public function set_locked($locked)
253
    {
254
        $this->locked = $locked;
255
    }
256
257
    /**
258
     * Set $isRequirement.
259
     *
260
     * @param int $isRequirement
261
     */
262
    public function setIsRequirement($isRequirement)
263
    {
264
        $this->isRequirement = $isRequirement;
265
    }
266
267
    /**
268
     * @param $value
269
     */
270
    public function setCourseListDependency($value)
271
    {
272
        $this->courseDependency = [];
273
274
        $unserialized = UnserializeApi::unserialize('not_allowed_classes', $value, true);
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) && 0 === (int) $id) {
389
            $cats = [];
390
            $cats[] = self::create_root_category();
391
392
            return $cats;
393
        }
394
        $courseId = api_get_course_int_id();
395
        $courseInfo = api_get_course_info_by_id($courseId);
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, c_id
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['c_id'] == $courseId) {
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) && 0 === (int) $id) {
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 (0 != $paramcount) {
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 (0 != $paramcount) {
473
                $sql .= ' AND';
474
            } else {
475
                $sql .= ' WHERE';
476
            }
477
478
            if ('0' == $course_code) {
479
                $sql .= ' c_id is null ';
480
            } else {
481
                $courseInfo = api_get_course_info($course_code);
482
                if ($courseInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $courseInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
483
                    $sql .= " c_id = '".intval($courseInfo['real_id'])."'";
484
                }
485
            }
486
487
            /*if ($show_session_categories !== true) {
488
                // a query on the course should show all
489
                // the categories inside sessions for this course
490
                // otherwise a special parameter is given to ask explicitely
491
                $sql .= " AND (session_id IS NULL OR session_id = 0) ";
492
            } else {*/
493
            if (empty($session_id)) {
494
                $sql .= ' AND (session_id IS NULL OR session_id = 0) ';
495
            } else {
496
                $sql .= ' AND session_id = '.(int) $session_id.' ';
497
            }
498
            //}
499
            $paramcount++;
500
        }
501
502
        if (isset($parent_id)) {
503
            if (0 != $paramcount) {
504
                $sql .= ' AND ';
505
            } else {
506
                $sql .= ' WHERE ';
507
            }
508
            $sql .= ' parent_id = '.intval($parent_id);
509
            $paramcount++;
510
        }
511
512
        if (isset($visible)) {
513
            if (0 != $paramcount) {
514
                $sql .= ' AND';
515
            } else {
516
                $sql .= ' WHERE';
517
            }
518
            $sql .= ' visible = '.intval($visible);
519
        }
520
521
        if (!empty($order_by)) {
522
            if (!empty($order_by) && '' != $order_by) {
523
                $sql .= ' '.$order_by;
524
            }
525
        }
526
527
        $result = Database::query($sql);
528
        $categories = [];
529
        if (Database::num_rows($result) > 0) {
530
            $categories = self::create_category_objects_from_sql_result($result);
531
        }
532
533
        return $categories;
534
    }
535
536
    /**
537
     * Create a category object from a GradebookCategory entity.
538
     *
539
     * @param GradebookCategory $gradebookCategory The entity
540
     *
541
     * @return \Category
542
     */
543
    public static function createCategoryObjectFromEntity(GradebookCategory $gradebookCategory)
544
    {
545
        $category = new Category();
546
        $category->set_id($gradebookCategory->getId());
547
        $category->set_name($gradebookCategory->getName());
548
        $category->set_description($gradebookCategory->getDescription());
549
        $category->set_user_id($gradebookCategory->getId());
550
        //$category->set_course_code($gradebookCategory->getCourseCode());
551
        $category->setCourseId($gradebookCategory->getCourse()->getId());
552
        $category->set_parent_id($gradebookCategory->getParent()->getId());
553
        $category->set_weight($gradebookCategory->getWeight());
554
        $category->set_visible($gradebookCategory->getVisible());
555
        $category->set_session_id($gradebookCategory->getSessionId());
556
        $category->set_certificate_min_score($gradebookCategory->getCertifMinScore());
557
        $category->set_grade_model_id($gradebookCategory->getGradeModelId());
558
        $category->set_locked($gradebookCategory->getLocked());
559
        $category->setGenerateCertificates($gradebookCategory->getGenerateCertificates());
560
        $category->setIsRequirement($gradebookCategory->getIsRequirement());
561
562
        return $category;
563
    }
564
565
    /**
566
     * Insert this category into the database.
567
     */
568
    public function add()
569
    {
570
        if (isset($this->name) && '-1' == $this->name) {
571
            return false;
572
        }
573
574
        if (isset($this->name) && isset($this->user_id)) {
575
            $em = Database::getManager();
576
577
            $courseInfo = api_get_course_info($this->course_code);
578
            $course = api_get_course_entity($courseInfo['real_id']);
579
            $parent = null;
580
            if (!empty($this->parent)) {
581
                $parent = $em->getRepository(GradebookCategory::class)->find($this->parent);
582
            }
583
584
            $category = new GradebookCategory();
585
            $category->setName($this->name);
586
            $category->setDescription($this->description);
587
            $category->setUser(api_get_user_entity($this->user_id));
588
            $category->setCourse($course);
589
            $category->setParent($parent);
590
            $category->setWeight($this->weight);
591
            $category->setVisible($this->visible);
592
            $category->setCertifMinScore($this->certificate_min_score);
593
            $category->setSessionId($this->session_id);
594
            $category->setGenerateCertificates($this->generateCertificates);
595
            $category->setGradeModelId($this->grade_model_id);
596
            $category->setIsRequirement($this->isRequirement);
597
            $category->setLocked(false);
598
599
            $em->persist($category);
600
            $em->flush();
601
602
            $id = $category->getId();
603
            $this->set_id($id);
604
605
            if (!empty($id)) {
606
                $parent_id = $this->get_parent_id();
607
                $grade_model_id = $this->get_grade_model_id();
608
                if (0 == $parent_id) {
609
                    //do something
610
                    if (isset($grade_model_id) &&
611
                        !empty($grade_model_id) &&
612
                        '-1' != $grade_model_id
613
                    ) {
614
                        $obj = new GradeModel();
615
                        $components = $obj->get_components($grade_model_id);
616
                        $default_weight_setting = api_get_setting('gradebook_default_weight');
617
                        $default_weight = 100;
618
                        if (isset($default_weight_setting)) {
619
                            $default_weight = $default_weight_setting;
620
                        }
621
                        foreach ($components as $component) {
622
                            $gradebook = new Gradebook();
623
                            $params = [];
624
625
                            $params['name'] = $component['acronym'];
626
                            $params['description'] = $component['title'];
627
                            $params['user_id'] = api_get_user_id();
628
                            $params['parent_id'] = $id;
629
                            $params['weight'] = $component['percentage'] / 100 * $default_weight;
630
                            $params['session_id'] = api_get_session_id();
631
                            $params['course_code'] = $this->get_course_code();
632
633
                            $gradebook->save($params);
634
                        }
635
                    }
636
                }
637
            }
638
639
            $gradebook = new Gradebook();
640
            $gradebook->updateSkillsToGradeBook(
641
                $this->id,
642
                $this->get_skills(false)
643
            );
644
645
            return $id;
646
        }
647
    }
648
649
    /**
650
     * Update the properties of this category in the database.
651
     *
652
     * @todo fix me
653
     */
654
    public function save()
655
    {
656
        $em = Database::getManager();
657
        $repo = $em->getRepository(GradebookCategory::class);
658
659
        /** @var GradebookCategory $category */
660
        $category = $repo->find($this->id);
661
662
        if (null === $category) {
663
            return false;
664
        }
665
666
        $parent = null;
667
        if (!empty($this->parent)) {
668
            $parent = $repo->find($this->parent);
669
        }
670
        $course = api_get_course_entity();
671
672
        $category->setName($this->name);
673
        $category->setDescription($this->description);
674
        $category->setUser(api_get_user_entity($this->user_id));
675
        $category->setCourse($course);
676
        //$category->setCourseCode($this->course_code);
677
        $category->setParent($parent);
678
        $category->setWeight($this->weight);
679
        $category->setVisible($this->visible);
680
        $category->setCertifMinScore($this->certificate_min_score);
681
        $category->setGenerateCertificates($this->generateCertificates);
682
        $category->setGradeModelId($this->grade_model_id);
683
        $category->setIsRequirement($this->isRequirement);
684
685
        $em->persist($category);
686
        $em->flush();
687
688
        if (!empty($this->id)) {
689
            $parent_id = $this->get_parent_id();
690
            $grade_model_id = $this->get_grade_model_id();
691
            if (0 == $parent_id) {
692
                if (isset($grade_model_id) &&
693
                    !empty($grade_model_id) &&
694
                    '-1' != $grade_model_id
695
                ) {
696
                    $obj = new GradeModel();
697
                    $components = $obj->get_components($grade_model_id);
698
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
699
                    $default_weight = 100;
700
                    if (isset($default_weight_setting)) {
701
                        $default_weight = $default_weight_setting;
702
                    }
703
                    $final_weight = $this->get_weight();
704
                    if (!empty($final_weight)) {
705
                        $default_weight = $this->get_weight();
706
                    }
707
                    foreach ($components as $component) {
708
                        $gradebook = new Gradebook();
709
                        $params = [];
710
                        $params['name'] = $component['acronym'];
711
                        $params['description'] = $component['title'];
712
                        $params['user_id'] = api_get_user_id();
713
                        $params['parent_id'] = $this->id;
714
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
715
                        $params['session_id'] = api_get_session_id();
716
                        $params['course_code'] = $this->get_course_code();
717
                        $gradebook->save($params);
718
                    }
719
                }
720
            }
721
        }
722
723
        $gradebook = new Gradebook();
724
        $gradebook->updateSkillsToGradeBook(
725
            $this->id,
726
            $this->get_skills(false),
727
            true
728
        );
729
    }
730
731
    /**
732
     * Update link weights see #5168.
733
     *
734
     * @param int $new_weight
735
     */
736
    public function updateChildrenWeight($new_weight)
737
    {
738
        $links = $this->get_links();
739
        $old_weight = $this->get_weight();
740
741
        if (!empty($links)) {
742
            foreach ($links as $link_item) {
743
                if (isset($link_item)) {
744
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
745
                    $link_item->set_weight($new_item_weight);
746
                    $link_item->save();
747
                }
748
            }
749
        }
750
    }
751
752
    /**
753
     * Delete this evaluation from the database.
754
     */
755
    public function delete()
756
    {
757
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
758
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
759
        Database::query($sql);
760
    }
761
762
    /**
763
     * Delete the gradebook categories from a course, including course sessions.
764
     *
765
     * @param \Chamilo\CoreBundle\Entity\Course $course
766
     */
767
    public static function deleteFromCourse($course)
768
    {
769
        $em = Database::getManager();
770
        $categories = $em
771
            ->createQuery(
772
                'SELECT DISTINCT gc.sessionId
773
                FROM ChamiloCoreBundle:GradebookCategory gc WHERE gc.course = :course'
774
            )
775
            ->setParameter('course', $course)
776
            ->getResult();
777
778
        foreach ($categories as $category) {
779
            $cats = self::load(
780
                null,
781
                null,
782
                $course->getCode(),
783
                null,
784
                null,
785
                (int) $category['sessionId']
786
            );
787
788
            if (!empty($cats)) {
789
                /** @var self $cat */
790
                foreach ($cats as $cat) {
791
                    $cat->delete_all();
792
                }
793
            }
794
        }
795
    }
796
797
    /**
798
     * @param int $course_id
799
     *
800
     * @return bool|string
801
     */
802
    public function show_message_resource_delete($course_id)
803
    {
804
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
805
        $sql = 'SELECT count(*) AS num
806
                FROM '.$table.'
807
                WHERE
808
                    c_id = "'.Database::escape_string($course_id).'" AND
809
                    visible = 3';
810
        $res = Database::query($sql);
811
        $option = Database::fetch_array($res, 'ASSOC');
812
        if ($option['num'] >= 1) {
813
            return '&nbsp;&nbsp;<span class="resource-deleted">(&nbsp;'.get_lang('The resource has been deleted').'&nbsp;)</span>';
814
        }
815
816
        return false;
817
    }
818
819
    /**
820
     * Shows all information of an category.
821
     *
822
     * @param int $categoryId
823
     *
824
     * @return array
825
     */
826
    public function showAllCategoryInfo($categoryId)
827
    {
828
        $categoryId = (int) $categoryId;
829
        if (empty($categoryId)) {
830
            return [];
831
        }
832
833
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
834
        $sql = 'SELECT * FROM '.$table.'
835
                WHERE id = '.$categoryId;
836
        $result = Database::query($sql);
837
        $row = Database::fetch_array($result, 'ASSOC');
838
839
        return $row;
840
    }
841
842
    /**
843
     * Checks if the certificate is available for the given user in this category.
844
     *
845
     * @param int $user_id User ID
846
     *
847
     * @return bool True if conditions match, false if fails
848
     */
849
    public function is_certificate_available($user_id)
850
    {
851
        $score = $this->calc_score(
852
            $user_id,
853
            null,
854
            $this->course_code,
855
            $this->session_id
856
        );
857
858
        if (isset($score) && isset($score[0])) {
859
            // Get a percentage score to compare to minimum certificate score
860
            // $certification_score = $score[0] / $score[1] * 100;
861
            // Get real score not a percentage.
862
            $certification_score = $score[0];
863
            if ($certification_score >= $this->certificate_min_score) {
864
                return true;
865
            }
866
        }
867
868
        return false;
869
    }
870
871
    /**
872
     * Is this category a course ?
873
     * A category is a course if it has a course code and no parent category.
874
     */
875
    public function is_course()
876
    {
877
        return isset($this->course_code) && !empty($this->course_code)
878
            && (!isset($this->parent) || 0 == $this->parent);
879
    }
880
881
    /**
882
     * Calculate the score of this category.
883
     *
884
     * @param int    $studentId   (default: all students - then the average is returned)
885
     * @param string $type
886
     * @param string $course_code
887
     * @param int    $session_id
888
     *
889
     * @return array (score sum, weight sum) or null if no scores available
890
     */
891
    public function calc_score(
892
        $studentId = null,
893
        $type = null,
894
        $course_code = '',
895
        $session_id = null
896
    ) {
897
        $key = 'category:'.$this->id.'student:'.(int) $studentId.'type:'.$type.'course:'.$course_code.'session:'.(int) $session_id;
898
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
899
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
900
901
        if ($cacheAvailable) {
902
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
903
            if ($cacheDriver->contains($key)) {
904
                return $cacheDriver->fetch($key);
905
            }
906
        }
907
        // Classic
908
        if (!empty($studentId) && '' == $type) {
909
            if (!empty($course_code)) {
910
                $cats = $this->get_subcategories(
911
                    $studentId,
912
                    $course_code,
913
                    $session_id
914
                );
915
                $evals = $this->get_evaluations($studentId, false, $course_code);
916
                $links = $this->get_links($studentId, false, $course_code);
917
            } else {
918
                $cats = $this->get_subcategories($studentId);
919
                $evals = $this->get_evaluations($studentId);
920
                $links = $this->get_links($studentId);
921
            }
922
923
            // Calculate score
924
            $count = 0;
925
            $ressum = 0;
926
            $weightsum = 0;
927
            if (!empty($cats)) {
928
                /** @var Category $cat */
929
                foreach ($cats as $cat) {
930
                    $cat->set_session_id($session_id);
931
                    $cat->set_course_code($course_code);
932
                    $cat->setStudentList($this->getStudentList());
933
                    $score = $cat->calc_score(
934
                        $studentId,
935
                        null,
936
                        $course_code,
937
                        $session_id
938
                    );
939
940
                    $catweight = 0;
941
                    if (0 != $cat->get_weight()) {
942
                        $catweight = $cat->get_weight();
943
                        $weightsum += $catweight;
944
                    }
945
946
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
947
                        $ressum += $score[0] / $score[1] * $catweight;
948
                    }
949
                }
950
            }
951
952
            if (!empty($evals)) {
953
                /** @var Evaluation $eval */
954
                foreach ($evals as $eval) {
955
                    $eval->setStudentList($this->getStudentList());
956
                    $evalres = $eval->calc_score($studentId);
957
                    if (isset($evalres) && 0 != $eval->get_weight()) {
958
                        $evalweight = $eval->get_weight();
959
                        $weightsum += $evalweight;
960
                        if (!empty($evalres[1])) {
961
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
962
                        }
963
                    } else {
964
                        if (0 != $eval->get_weight()) {
965
                            $evalweight = $eval->get_weight();
966
                            $weightsum += $evalweight;
967
                        }
968
                    }
969
                }
970
            }
971
972
            if (!empty($links)) {
973
                /** @var EvalLink|ExerciseLink $link */
974
                foreach ($links as $link) {
975
                    $link->setStudentList($this->getStudentList());
976
977
                    if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
978
                        $link->set_session_id($session_id);
979
                    }
980
981
                    $linkres = $link->calc_score($studentId, null);
982
                    if (!empty($linkres) && 0 != $link->get_weight()) {
983
                        $linkweight = $link->get_weight();
984
                        $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
985
                        $weightsum += $linkweight;
986
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
987
                    } else {
988
                        // Adding if result does not exists
989
                        if (0 != $link->get_weight()) {
990
                            $linkweight = $link->get_weight();
991
                            $weightsum += $linkweight;
992
                        }
993
                    }
994
                }
995
            }
996
        } else {
997
            if (!empty($course_code)) {
998
                $cats = $this->get_subcategories(
999
                    null,
1000
                    $course_code,
1001
                    $session_id
1002
                );
1003
                $evals = $this->get_evaluations(null, false, $course_code);
1004
                $links = $this->get_links(null, false, $course_code);
1005
            } else {
1006
                $cats = $this->get_subcategories(null);
1007
                $evals = $this->get_evaluations(null);
1008
                $links = $this->get_links(null);
1009
            }
1010
1011
            // Calculate score
1012
            $ressum = 0;
1013
            $weightsum = 0;
1014
            $bestResult = 0;
1015
            $totalScorePerStudent = [];
1016
1017
            if (!empty($cats)) {
1018
                /** @var Category $cat */
1019
                foreach ($cats as $cat) {
1020
                    $cat->setStudentList($this->getStudentList());
1021
                    $score = $cat->calc_score(
1022
                        null,
1023
                        $type,
1024
                        $course_code,
1025
                        $session_id
1026
                    );
1027
1028
                    $catweight = 0;
1029
                    if (0 != $cat->get_weight()) {
1030
                        $catweight = $cat->get_weight();
1031
                        $weightsum += $catweight;
1032
                    }
1033
1034
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
1035
                        $ressum += $score[0] / $score[1] * $catweight;
1036
1037
                        if ($ressum > $bestResult) {
1038
                            $bestResult = $ressum;
1039
                        }
1040
                    }
1041
                }
1042
            }
1043
1044
            if (!empty($evals)) {
1045
                if ('best' === $type) {
1046
                    $studentList = $this->getStudentList();
1047
                    foreach ($studentList as $student) {
1048
                        $studentId = $student['user_id'];
1049
                        foreach ($evals as $eval) {
1050
                            $linkres = $eval->calc_score($studentId, null);
1051
                            $linkweight = $eval->get_weight();
1052
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1053
                            $ressum = $linkres[0] / $link_res_denom * $linkweight;
1054
1055
                            if (!isset($totalScorePerStudent[$studentId])) {
1056
                                $totalScorePerStudent[$studentId] = 0;
1057
                            }
1058
                            $totalScorePerStudent[$studentId] += $ressum;
1059
                        }
1060
                    }
1061
                } else {
1062
                    /** @var Evaluation $eval */
1063
                    foreach ($evals as $eval) {
1064
                        $evalres = $eval->calc_score(null, $type);
1065
                        $eval->setStudentList($this->getStudentList());
1066
1067
                        if (isset($evalres) && 0 != $eval->get_weight()) {
1068
                            $evalweight = $eval->get_weight();
1069
                            $weightsum += $evalweight;
1070
                            if (!empty($evalres[1])) {
1071
                                $ressum += $evalres[0] / $evalres[1] * $evalweight;
1072
                            }
1073
1074
                            if ($ressum > $bestResult) {
1075
                                $bestResult = $ressum;
1076
                            }
1077
                        } else {
1078
                            if (0 != $eval->get_weight()) {
1079
                                $evalweight = $eval->get_weight();
1080
                                $weightsum += $evalweight;
1081
                            }
1082
                        }
1083
                    }
1084
                }
1085
            }
1086
1087
            if (!empty($links)) {
1088
                $studentList = $this->getStudentList();
1089
                if ('best' === $type) {
1090
                    foreach ($studentList as $student) {
1091
                        $studentId = $student['user_id'];
1092
                        foreach ($links as $link) {
1093
                            $linkres = $link->calc_score($studentId, null);
1094
                            $linkweight = $link->get_weight();
1095
                            if ($linkres) {
1096
                                $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1097
                                $ressum = $linkres[0] / $link_res_denom * $linkweight;
1098
                            }
1099
1100
                            if (!isset($totalScorePerStudent[$studentId])) {
1101
                                $totalScorePerStudent[$studentId] = 0;
1102
                            }
1103
                            $totalScorePerStudent[$studentId] += $ressum;
1104
                        }
1105
                    }
1106
                } else {
1107
                    /** @var EvalLink|ExerciseLink $link */
1108
                    foreach ($links as $link) {
1109
                        $link->setStudentList($this->getStudentList());
1110
1111
                        if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1112
                            $link->set_session_id($session_id);
1113
                        }
1114
1115
                        $linkres = $link->calc_score($studentId, $type);
1116
1117
                        if (!empty($linkres) && 0 != $link->get_weight()) {
1118
                            $linkweight = $link->get_weight();
1119
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1120
1121
                            $weightsum += $linkweight;
1122
                            $ressum += $linkres[0] / $link_res_denom * $linkweight;
1123
                            if ($ressum > $bestResult) {
1124
                                $bestResult = $ressum;
1125
                            }
1126
                        } else {
1127
                            // Adding if result does not exists
1128
                            if (0 != $link->get_weight()) {
1129
                                $linkweight = $link->get_weight();
1130
                                $weightsum += $linkweight;
1131
                            }
1132
                        }
1133
                    }
1134
                }
1135
            }
1136
        }
1137
1138
        switch ($type) {
1139
            case 'best':
1140
                arsort($totalScorePerStudent);
1141
                $maxScore = current($totalScorePerStudent);
1142
1143
                return [$maxScore, $this->get_weight()];
1144
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
1145
            case 'average':
1146
                if (empty($ressum)) {
1147
                    if ($cacheAvailable) {
1148
                        $cacheDriver->save($key, null);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cacheDriver does not seem to be defined for all execution paths leading up to this point.
Loading history...
1149
                    }
1150
1151
                    return null;
1152
                }
1153
1154
                if ($cacheAvailable) {
1155
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1156
                }
1157
1158
                return [$ressum, $weightsum];
1159
                break;
1160
            case 'ranking':
1161
                // category ranking is calculated in gradebook_data_generator.class.php
1162
                // function get_data
1163
                return null;
1164
1165
                return AbstractLink::getCurrentUserRanking($studentId, []);
0 ignored issues
show
Unused Code introduced by
return AbstractLink::get...ng($studentId, array()) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1166
                break;
1167
            default:
1168
                if ($cacheAvailable) {
1169
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1170
                }
1171
1172
                return [$ressum, $weightsum];
1173
                break;
1174
        }
1175
    }
1176
1177
    /**
1178
     * Delete this category and every subcategory, evaluation and result inside.
1179
     */
1180
    public function delete_all()
1181
    {
1182
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1183
        $evals = Evaluation::load(
1184
            null,
1185
            null,
1186
            $this->course_code,
1187
            $this->id,
1188
            null
1189
        );
1190
1191
        $links = LinkFactory::load(
1192
            null,
1193
            null,
1194
            null,
1195
            null,
1196
            $this->course_code,
1197
            $this->id,
1198
            null
1199
        );
1200
1201
        if (!empty($cats)) {
1202
            /** @var Category $cat */
1203
            foreach ($cats as $cat) {
1204
                $cat->delete_all();
1205
                $cat->delete();
1206
            }
1207
        }
1208
1209
        if (!empty($evals)) {
1210
            /** @var Evaluation $eval */
1211
            foreach ($evals as $eval) {
1212
                $eval->delete_with_results();
1213
            }
1214
        }
1215
1216
        if (!empty($links)) {
1217
            /** @var AbstractLink $link */
1218
            foreach ($links as $link) {
1219
                $link->delete();
1220
            }
1221
        }
1222
1223
        $this->delete();
1224
    }
1225
1226
    /**
1227
     * Return array of Category objects where a student is subscribed to.
1228
     *
1229
     * @param int    $stud_id
1230
     * @param string $course_code
1231
     * @param int    $session_id
1232
     *
1233
     * @return array
1234
     */
1235
    public function get_root_categories_for_student(
1236
        $stud_id,
1237
        $course_code = null,
1238
        $session_id = null
1239
    ) {
1240
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1241
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1242
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1243
1244
        $course_code = Database::escape_string($course_code);
1245
        $session_id = (int) $session_id;
1246
1247
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1248
1249
        if (!api_is_allowed_to_edit()) {
1250
            $sql .= ' AND visible = 1';
1251
            //proceed with checks on optional parameters course & session
1252
            if (!empty($course_code)) {
1253
                // TODO: considering it highly improbable that a user would get here
1254
                // if he doesn't have the rights to view this course and this
1255
                // session, we don't check his registration to these, but this
1256
                // could be an improvement
1257
                if (!empty($session_id)) {
1258
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1259
                } else {
1260
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1261
                }
1262
            } else {
1263
                //no optional parameter, proceed as usual
1264
                $sql .= ' AND course_code in
1265
                     (
1266
                        SELECT c.code
1267
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1268
                        ON (cu.c_id = c.id)
1269
                        WHERE cu.user_id = '.intval($stud_id).'
1270
                        AND cu.status = '.STUDENT.'
1271
                    )';
1272
            }
1273
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1274
            //proceed with checks on optional parameters course & session
1275
            if (!empty($course_code)) {
1276
                // TODO: considering it highly improbable that a user would get here
1277
                // if he doesn't have the rights to view this course and this
1278
                // session, we don't check his registration to these, but this
1279
                // could be an improvement
1280
                $sql .= " AND course_code  = '".$course_code."'";
1281
                if (!empty($session_id)) {
1282
                    $sql .= " AND session_id = ".$session_id;
1283
                } else {
1284
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1285
                }
1286
            } else {
1287
                $sql .= ' AND course_code IN
1288
                     (
1289
                        SELECT c.code
1290
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1291
                        ON (cu.c_id = c.id)
1292
                        WHERE
1293
                            cu.user_id = '.api_get_user_id().' AND
1294
                            cu.status = '.COURSEMANAGER.'
1295
                    )';
1296
            }
1297
        } elseif (api_is_platform_admin()) {
1298
            if (isset($session_id) && 0 != $session_id) {
1299
                $sql .= ' AND session_id='.$session_id;
1300
            } else {
1301
                $sql .= ' AND coalesce(session_id,0)=0';
1302
            }
1303
        }
1304
        $result = Database::query($sql);
1305
        $cats = self::create_category_objects_from_sql_result($result);
1306
1307
        // course independent categories
1308
        if (empty($course_code)) {
1309
            $cats = $this->getIndependentCategoriesWithStudentResult(
1310
                0,
1311
                $stud_id,
1312
                $cats
1313
            );
1314
        }
1315
1316
        return $cats;
1317
    }
1318
1319
    /**
1320
     * Return array of Category objects where a teacher is admin for.
1321
     *
1322
     * @param int    $user_id     (to return everything, use 'null' here)
1323
     * @param string $course_code (optional)
1324
     * @param int    $session_id  (optional)
1325
     *
1326
     * @return array
1327
     */
1328
    public function get_root_categories_for_teacher(
1329
        $user_id,
1330
        $course_code = null,
1331
        $session_id = null
1332
    ) {
1333
        if (null == $user_id) {
1334
            return self::load(null, null, $course_code, 0, null, $session_id);
1335
        }
1336
1337
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1338
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1339
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1340
1341
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1342
                WHERE parent_id = 0 ';
1343
        if (!empty($course_code)) {
1344
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1345
            if (!empty($session_id)) {
1346
                $sql .= " AND session_id = ".(int) $session_id;
1347
            }
1348
        } else {
1349
            $sql .= ' AND course_code in
1350
                 (
1351
                    SELECT c.code
1352
                    FROM '.$main_course_user_table.' cu
1353
                    INNER JOIN '.$courseTable.' c
1354
                    ON (cu.c_id = c.id)
1355
                    WHERE user_id = '.intval($user_id).'
1356
                )';
1357
        }
1358
        $result = Database::query($sql);
1359
        $cats = self::create_category_objects_from_sql_result($result);
1360
        // course independent categories
1361
        if (isset($course_code)) {
1362
            $indcats = self::load(
1363
                null,
1364
                $user_id,
1365
                $course_code,
1366
                0,
1367
                null,
1368
                $session_id
1369
            );
1370
            $cats = array_merge($cats, $indcats);
1371
        }
1372
1373
        return $cats;
1374
    }
1375
1376
    /**
1377
     * Can this category be moved to somewhere else ?
1378
     * The root and courses cannot be moved.
1379
     *
1380
     * @return bool
1381
     */
1382
    public function is_movable()
1383
    {
1384
        return !(!isset($this->id) || 0 == $this->id || $this->is_course());
1385
    }
1386
1387
    /**
1388
     * Generate an array of possible categories where this category can be moved to.
1389
     * Notice: its own parent will be included in the list: it's up to the frontend
1390
     * to disable this element.
1391
     *
1392
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1393
     */
1394
    public function get_target_categories()
1395
    {
1396
        // the root or a course -> not movable
1397
        if (!$this->is_movable()) {
1398
            return null;
1399
        } else {
1400
            // otherwise:
1401
            // - course independent category
1402
            //   -> movable to root or other independent categories
1403
            // - category inside a course
1404
            //   -> movable to root, independent categories or categories inside the course
1405
            $user = api_is_platform_admin() ? null : api_get_user_id();
1406
            $targets = [];
1407
            $level = 0;
1408
1409
            $root = [0, get_lang('Main folder'), $level];
1410
            $targets[] = $root;
1411
1412
            if (isset($this->course_code) && !empty($this->course_code)) {
1413
                $crscats = self::load(null, null, $this->course_code, 0);
1414
                foreach ($crscats as $cat) {
1415
                    if ($this->can_be_moved_to_cat($cat)) {
1416
                        $targets[] = [
1417
                            $cat->get_id(),
1418
                            $cat->get_name(),
1419
                            $level + 1,
1420
                        ];
1421
                        $targets = $this->addTargetSubcategories(
1422
                            $targets,
1423
                            $level + 1,
1424
                            $cat->get_id()
1425
                        );
1426
                    }
1427
                }
1428
            }
1429
1430
            $indcats = self::load(null, $user, 0, 0);
1431
            foreach ($indcats as $cat) {
1432
                if ($this->can_be_moved_to_cat($cat)) {
1433
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1434
                    $targets = $this->addTargetSubcategories(
1435
                        $targets,
1436
                        $level + 1,
1437
                        $cat->get_id()
1438
                    );
1439
                }
1440
            }
1441
1442
            return $targets;
1443
        }
1444
    }
1445
1446
    /**
1447
     * Move this category to the given category.
1448
     * If this category moves from inside a course to outside,
1449
     * its course code must be changed, as well as the course code
1450
     * of all underlying categories and evaluations. All links will
1451
     * be deleted as well !
1452
     */
1453
    public function move_to_cat($cat)
1454
    {
1455
        $this->set_parent_id($cat->get_id());
1456
        if ($this->get_course_code() != $cat->get_course_code()) {
1457
            $this->set_course_code($cat->get_course_code());
1458
            $this->applyCourseCodeToChildren();
1459
        }
1460
        $this->save();
1461
    }
1462
1463
    /**
1464
     * Generate an array of all categories the user can navigate to.
1465
     */
1466
    public function get_tree()
1467
    {
1468
        $targets = [];
1469
        $level = 0;
1470
        $root = [0, get_lang('Main folder'), $level];
1471
        $targets[] = $root;
1472
1473
        // course or platform admin
1474
        if (api_is_allowed_to_edit()) {
1475
            $user = api_is_platform_admin() ? null : api_get_user_id();
1476
            $cats = self::get_root_categories_for_teacher($user);
0 ignored issues
show
Bug Best Practice introduced by
The method Category::get_root_categories_for_teacher() is not static, but was called statically. ( Ignorable by Annotation )

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

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

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

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

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

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

Loading history...
2079
        }
2080
    }
2081
2082
    /**
2083
     * Generates a certificate for this user if everything matches.
2084
     *
2085
     * @param int  $category_id            gradebook id
2086
     * @param int  $user_id
2087
     * @param bool $sendNotification
2088
     * @param bool $skipGenerationIfExists
2089
     *
2090
     * @return array
2091
     */
2092
    public static function generateUserCertificate(
2093
        $category_id,
2094
        $user_id,
2095
        $sendNotification = false,
2096
        $skipGenerationIfExists = false
2097
    ) {
2098
        $user_id = (int) $user_id;
2099
        $category_id = (int) $category_id;
2100
2101
        // Generating the total score for a course
2102
        $category = self::load(
2103
            $category_id,
2104
            null,
2105
            null,
2106
            null,
2107
            null,
2108
            null,
2109
            false
2110
        );
2111
2112
        /** @var Category $category */
2113
        $category = $category[0];
2114
2115
        if (empty($category)) {
2116
            return false;
2117
        }
2118
2119
        $sessionId = $category->get_session_id();
2120
        $courseCode = $category->get_course_code();
2121
        $courseInfo = api_get_course_info($courseCode);
2122
        $courseId = $courseInfo['real_id'];
2123
2124
        $userFinishedCourse = self::userFinishedCourse(
2125
            $user_id,
2126
            $category,
2127
            true
2128
        );
2129
2130
        if (!$userFinishedCourse) {
2131
            return false;
2132
        }
2133
2134
        $skillToolEnabled = Skill::hasAccessToUserSkill(
2135
            api_get_user_id(),
2136
            $user_id
2137
        );
2138
2139
        $userHasSkills = false;
2140
        if ($skillToolEnabled) {
2141
            $skill = new Skill();
2142
            $skill->addSkillToUser(
2143
                $user_id,
2144
                $category,
2145
                $courseId,
2146
                $sessionId
2147
            );
2148
2149
            $objSkillRelUser = new SkillRelUser();
2150
            $userSkills = $objSkillRelUser->getUserSkills(
2151
                $user_id,
2152
                $courseId,
2153
                $sessionId
2154
            );
2155
            $userHasSkills = !empty($userSkills);
2156
        }
2157
2158
        // Block certification links depending gradebook configuration (generate certifications)
2159
        if (empty($category->getGenerateCertificates())) {
2160
            if ($userHasSkills) {
2161
                return [
2162
                    'badge_link' => Display::toolbarButton(
2163
                        get_lang('Export badges'),
2164
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2165
                        'external-link'
2166
                    ),
2167
                ];
2168
            }
2169
2170
            return false;
2171
        }
2172
2173
        $scoretotal = $category->calc_score($user_id);
2174
2175
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2176
        // file load this variable as a global
2177
        $scoredisplay = ScoreDisplay::instance();
2178
        $my_score_in_gradebook = $scoredisplay->display_score(
2179
            $scoretotal,
2180
            SCORE_SIMPLE
2181
        );
2182
2183
        $my_certificate = GradebookUtils::get_certificate_by_user_id(
2184
            $category_id,
2185
            $user_id
2186
        );
2187
2188
        if ($skipGenerationIfExists && !empty($my_certificate)) {
2189
            return false;
2190
        }
2191
2192
        if (empty($my_certificate)) {
2193
            GradebookUtils::registerUserInfoAboutCertificate(
2194
                $category_id,
2195
                $user_id,
2196
                $my_score_in_gradebook,
2197
                api_get_utc_datetime()
2198
            );
2199
            $my_certificate = GradebookUtils::get_certificate_by_user_id(
2200
                $category_id,
2201
                $user_id
2202
            );
2203
        }
2204
2205
        $html = [];
2206
        if (!empty($my_certificate)) {
2207
            $certificate_obj = new Certificate(
2208
                $my_certificate['id'],
2209
                0,
2210
                $sendNotification
2211
            );
2212
2213
            $fileWasGenerated = $certificate_obj->isHtmlFileGenerated();
2214
2215
            // Fix when using custom certificate BT#15937
2216
            if ('true' === api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate')) {
2217
                $infoCertificate = CustomCertificatePlugin::getCertificateData($my_certificate['id'], $user_id);
2218
                if (!empty($infoCertificate)) {
2219
                    $fileWasGenerated = true;
2220
                }
2221
            }
2222
2223
            if (!empty($fileWasGenerated)) {
2224
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'].'&user_id='.$user_id;
2225
                $certificates = Display::toolbarButton(
2226
                    get_lang('Display certificate'),
2227
                    $url,
2228
                    'eye',
2229
                    'primary',
2230
                    ['target' => '_blank']
2231
                );
2232
2233
                $exportToPDF = Display::url(
2234
                    Display::return_icon(
2235
                        'pdf.png',
2236
                        get_lang('Export to PDF'),
2237
                        [],
2238
                        ICON_SIZE_MEDIUM
2239
                    ),
2240
                    "$url&action=export"
2241
                );
2242
2243
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2244
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2245
                if ('true' === $hideExportLink || (api_is_student() && 'true' === $hideExportLinkStudent)) {
2246
                    $exportToPDF = null;
2247
                }
2248
2249
                $html = [
2250
                    'certificate_link' => $certificates,
2251
                    'pdf_link' => $exportToPDF,
2252
                    'pdf_url' => "$url&action=export",
2253
                ];
2254
            }
2255
2256
            if ($skillToolEnabled && $userHasSkills) {
2257
                $html['badge_link'] = Display::toolbarButton(
2258
                    get_lang('Export badges'),
2259
                    api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2260
                    'external-link'
2261
                );
2262
            }
2263
2264
            return $html;
2265
        }
2266
    }
2267
2268
    /**
2269
     * @param int   $catId
2270
     * @param array $userList
2271
     */
2272
    public static function generateCertificatesInUserList($catId, $userList)
2273
    {
2274
        if (!empty($userList)) {
2275
            foreach ($userList as $userInfo) {
2276
                self::generateUserCertificate($catId, $userInfo['user_id']);
2277
            }
2278
        }
2279
    }
2280
2281
    /**
2282
     * @param int   $catId
2283
     * @param array $userList
2284
     */
2285
    public static function exportAllCertificates($catId, $userList = [])
2286
    {
2287
        $orientation = api_get_configuration_value('certificate_pdf_orientation');
2288
2289
        $params['orientation'] = 'landscape';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
2290
        if (!empty($orientation)) {
2291
            $params['orientation'] = $orientation;
2292
        }
2293
2294
        $params['left'] = 0;
2295
        $params['right'] = 0;
2296
        $params['top'] = 0;
2297
        $params['bottom'] = 0;
2298
        $page_format = 'landscape' == $params['orientation'] ? 'A4-L' : 'A4';
2299
        $pdf = new PDF($page_format, $params['orientation'], $params);
2300
        if (api_get_configuration_value('add_certificate_pdf_footer')) {
2301
            $pdf->setCertificateFooter();
2302
        }
2303
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2304
        $certificate_path_list = [];
2305
2306
        if (!empty($certificate_list)) {
2307
            foreach ($certificate_list as $index => $value) {
2308
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2309
                    $value['user_id'],
2310
                    $catId
2311
                );
2312
                foreach ($list_certificate as $value_certificate) {
2313
                    $certificate_obj = new Certificate($value_certificate['id']);
2314
                    $certificate_obj->generate(['hide_print_button' => true]);
2315
                    if ($certificate_obj->isHtmlFileGenerated()) {
2316
                        $certificate_path_list[] = $certificate_obj->html_file;
2317
                    }
2318
                }
2319
            }
2320
        }
2321
2322
        if (!empty($certificate_path_list)) {
2323
            // Print certificates (without the common header/footer/watermark
2324
            //  stuff) and return as one multiple-pages PDF
2325
            $pdf->html_to_pdf(
2326
                $certificate_path_list,
2327
                get_lang('Certificates'),
2328
                null,
2329
                false,
2330
                false
2331
            );
2332
        }
2333
    }
2334
2335
    /**
2336
     * @param int $catId
2337
     */
2338
    public static function deleteAllCertificates($catId)
2339
    {
2340
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2341
        if (!empty($certificate_list)) {
2342
            foreach ($certificate_list as $index => $value) {
2343
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2344
                    $value['user_id'],
2345
                    $catId
2346
                );
2347
                foreach ($list_certificate as $value_certificate) {
2348
                    $certificate_obj = new Certificate($value_certificate['id']);
2349
                    $certificate_obj->delete(true);
2350
                }
2351
            }
2352
        }
2353
    }
2354
2355
    /**
2356
     * Check whether a user has finished a course by its gradebook.
2357
     *
2358
     * @param int       $userId           The user ID
2359
     * @param \Category $category         Optional. The gradebook category.
2360
     *                                    To check by the gradebook category
2361
     * @param bool      $recalculateScore Whether recalculate the score
2362
     *
2363
     * @return bool
2364
     */
2365
    public static function userFinishedCourse(
2366
        $userId,
2367
        Category $category,
2368
        $recalculateScore = false
2369
    ) {
2370
        if (empty($category)) {
2371
            return false;
2372
        }
2373
2374
        $currentScore = self::getCurrentScore(
2375
            $userId,
2376
            $category,
2377
            $recalculateScore
2378
        );
2379
2380
        $minCertificateScore = $category->getCertificateMinScore();
2381
2382
        return $currentScore >= $minCertificateScore;
2383
    }
2384
2385
    /**
2386
     * Get the current score (as percentage) on a gradebook category for a user.
2387
     *
2388
     * @param int      $userId      The user id
2389
     * @param Category $category    The gradebook category
2390
     * @param bool     $recalculate
2391
     *
2392
     * @return float The score
2393
     */
2394
    public static function getCurrentScore(
2395
        $userId,
2396
        $category,
2397
        $recalculate = false
2398
    ) {
2399
        if (empty($category)) {
2400
            return 0;
2401
        }
2402
2403
        if ($recalculate) {
2404
            return self::calculateCurrentScore(
2405
                $userId,
2406
                $category
2407
            );
2408
        }
2409
2410
        $resultData = Database::select(
2411
            '*',
2412
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2413
            [
2414
                'where' => [
2415
                    'category_id = ? AND user_id = ?' => [$category->get_id(), $userId],
2416
                ],
2417
                'order' => 'registered_at DESC',
2418
                'limit' => '1',
2419
            ],
2420
            'first'
2421
        );
2422
2423
        if (empty($resultData)) {
2424
            return 0;
2425
        }
2426
2427
        return $resultData['score'];
2428
    }
2429
2430
    /**
2431
     * Register the current score for a user on a category gradebook.
2432
     *
2433
     * @param float $score      The achieved score
2434
     * @param int   $userId     The user id
2435
     * @param int   $categoryId The gradebook category
2436
     *
2437
     * @return int The insert id
2438
     */
2439
    public static function registerCurrentScore($score, $userId, $categoryId)
2440
    {
2441
        return Database::insert(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::insert(...pi_get_utc_datetime())) could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

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

Loading history...
2442
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2443
            [
2444
                'category_id' => intval($categoryId),
2445
                'user_id' => intval($userId),
2446
                'score' => api_float_val($score),
2447
                'registered_at' => api_get_utc_datetime(),
2448
            ]
2449
        );
2450
    }
2451
2452
    /**
2453
     * @return array
2454
     */
2455
    public function getStudentList()
2456
    {
2457
        return $this->studentList;
2458
    }
2459
2460
    /**
2461
     * @param array $list
2462
     */
2463
    public function setStudentList($list)
2464
    {
2465
        $this->studentList = $list;
2466
    }
2467
2468
    /**
2469
     * @return string
2470
     */
2471
    public static function getUrl()
2472
    {
2473
        $url = Session::read('gradebook_dest');
2474
        if (empty($url)) {
2475
            // We guess the link
2476
            $courseInfo = api_get_course_info();
2477
            if (!empty($courseInfo)) {
2478
                return api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2479
            } else {
2480
                return api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2481
            }
2482
        }
2483
2484
        return $url;
2485
    }
2486
2487
    /**
2488
     * Destination is index.php or gradebook.php.
2489
     *
2490
     * @param string $url
2491
     */
2492
    public static function setUrl($url)
2493
    {
2494
        switch ($url) {
2495
            case 'gradebook.php':
2496
                $url = api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2497
                break;
2498
            case 'index.php':
2499
                $url = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2500
                break;
2501
        }
2502
        Session::write('gradebook_dest', $url);
2503
    }
2504
2505
    /**
2506
     * @return int
2507
     */
2508
    public function getGradeBooksToValidateInDependence()
2509
    {
2510
        return $this->gradeBooksToValidateInDependence;
2511
    }
2512
2513
    /**
2514
     * @param int $value
2515
     *
2516
     * @return Category
2517
     */
2518
    public function setGradeBooksToValidateInDependence($value)
2519
    {
2520
        $this->gradeBooksToValidateInDependence = $value;
2521
2522
        return $this;
2523
    }
2524
2525
    /**
2526
     * Return HTML code with links to download and view certificate.
2527
     *
2528
     * @return string
2529
     */
2530
    public static function getDownloadCertificateBlock(array $certificate)
2531
    {
2532
        if (!isset($certificate['pdf_url'])) {
2533
            return '';
2534
        }
2535
2536
        $downloadLink = Display::toolbarButton(
2537
            get_lang('Download certificate in PDF'),
2538
            $certificate['pdf_url'],
2539
            'file-pdf-o'
2540
        );
2541
        $viewLink = $certificate['certificate_link'];
2542
2543
        return "
2544
            <div class='panel panel-default'>
2545
                <div class='panel-body'>
2546
                    <h3 class='text-center'>".get_lang('You can now download your certificate by clicking here')."</h3>
2547
                    <div class='text-center'>$downloadLink $viewLink</div>
2548
                </div>
2549
            </div>
2550
        ";
2551
    }
2552
2553
    /**
2554
     * Find a gradebook category by the certificate ID.
2555
     *
2556
     * @param int $id certificate id
2557
     *
2558
     * @throws \Doctrine\ORM\NonUniqueResultException
2559
     *
2560
     * @return Category|null
2561
     */
2562
    public static function findByCertificate($id)
2563
    {
2564
        $category = Database::getManager()
2565
            ->createQuery('SELECT c.catId FROM ChamiloCoreBundle:GradebookCertificate c WHERE c.id = :id')
2566
            ->setParameters(['id' => $id])
2567
            ->getOneOrNullResult();
2568
2569
        if (empty($category)) {
2570
            return null;
2571
        }
2572
2573
        $category = self::load($category['catId']);
2574
2575
        if (empty($category)) {
2576
            return null;
2577
        }
2578
2579
        return $category[0];
2580
    }
2581
2582
    /**
2583
     * @param int $value
2584
     */
2585
    public function setDocumentId($value)
2586
    {
2587
        $this->documentId = (int) $value;
2588
    }
2589
2590
    /**
2591
     * @return int
2592
     */
2593
    public function getDocumentId()
2594
    {
2595
        return $this->documentId;
2596
    }
2597
2598
    /**
2599
     * Get the remaining weight in root category.
2600
     *
2601
     * @return int
2602
     */
2603
    public function getRemainingWeight()
2604
    {
2605
        $subCategories = $this->get_subcategories();
2606
2607
        $subWeight = 0;
2608
2609
        /** @var Category $subCategory */
2610
        foreach ($subCategories as $subCategory) {
2611
            $subWeight += $subCategory->get_weight();
2612
        }
2613
2614
        return $this->weight - $subWeight;
2615
    }
2616
2617
    /**
2618
     * @return int
2619
     */
2620
    public function getCourseId()
2621
    {
2622
        return $this->courseId;
2623
    }
2624
2625
    /**
2626
     * @param int $courseId
2627
     *
2628
     * @return Category
2629
     */
2630
    public function setCourseId($courseId)
2631
    {
2632
        $this->courseId = $courseId;
2633
2634
        return $this;
2635
    }
2636
2637
    /**
2638
     * @return Category
2639
     */
2640
    private static function create_root_category()
2641
    {
2642
        $cat = new Category();
2643
        $cat->set_id(0);
2644
        $cat->set_name(get_lang('Main folder'));
2645
        $cat->set_description(null);
2646
        $cat->set_user_id(0);
2647
        $cat->set_course_code(null);
2648
        $cat->set_parent_id(null);
2649
        $cat->set_weight(0);
2650
        $cat->set_visible(1);
2651
        $cat->setGenerateCertificates(0);
2652
        $cat->setIsRequirement(false);
2653
2654
        return $cat;
2655
    }
2656
2657
    /**
2658
     * @param Doctrine\DBAL\Driver\Statement|null $result
2659
     *
2660
     * @return array
2661
     */
2662
    private static function create_category_objects_from_sql_result($result)
2663
    {
2664
        $categories = [];
2665
        $allow = api_get_configuration_value('allow_gradebook_stats');
2666
        if ($allow) {
2667
            $em = Database::getManager();
2668
            $repo = $em->getRepository(GradebookCategory::class);
2669
        }
2670
2671
        while ($data = Database::fetch_array($result)) {
2672
            $cat = new Category();
2673
            $cat->set_id($data['id']);
2674
            $cat->set_name($data['name']);
2675
            $cat->set_description($data['description']);
2676
            $cat->set_user_id($data['user_id']);
2677
            $courseInfo = api_get_course_info_by_id($data['c_id']);
2678
            $cat->set_course_code($courseInfo['code']);
2679
            $cat->setCourseId($data['c_id']);
2680
            $cat->set_parent_id($data['parent_id']);
2681
            $cat->set_weight($data['weight']);
2682
            $cat->set_visible($data['visible']);
2683
            $cat->set_session_id($data['session_id']);
2684
            $cat->set_certificate_min_score($data['certif_min_score']);
2685
            $cat->set_grade_model_id($data['grade_model_id']);
2686
            $cat->set_locked($data['locked']);
2687
            $cat->setGenerateCertificates($data['generate_certificates']);
2688
            $cat->setIsRequirement($data['is_requirement']);
2689
            //$cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2690
            $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2691
            $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2692
            $cat->setDocumentId($data['document_id']);
2693
            if ($allow) {
2694
                $cat->entity = $repo->find($data['id']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $repo does not seem to be defined for all execution paths leading up to this point.
Loading history...
2695
            }
2696
2697
            $categories[] = $cat;
2698
        }
2699
2700
        return $categories;
2701
    }
2702
2703
    /**
2704
     * Internal function used by get_target_categories().
2705
     *
2706
     * @param array $targets
2707
     * @param int   $level
2708
     * @param int   $catid
2709
     *
2710
     * @return array
2711
     */
2712
    private function addTargetSubcategories($targets, $level, $catid)
2713
    {
2714
        $subcats = self::load(null, null, null, $catid);
2715
        foreach ($subcats as $cat) {
2716
            if ($this->can_be_moved_to_cat($cat)) {
2717
                $targets[] = [
2718
                    $cat->get_id(),
2719
                    $cat->get_name(),
2720
                    $level + 1,
2721
                ];
2722
                $targets = $this->addTargetSubcategories(
2723
                    $targets,
2724
                    $level + 1,
2725
                    $cat->get_id()
2726
                );
2727
            }
2728
        }
2729
2730
        return $targets;
2731
    }
2732
2733
    /**
2734
     * Internal function used by get_target_categories() and addTargetSubcategories()
2735
     * Can this category be moved to the given category ?
2736
     * Impossible when origin and target are the same... children won't be processed
2737
     * either. (a category can't be moved to one of its own children).
2738
     */
2739
    private function can_be_moved_to_cat($cat)
2740
    {
2741
        return $cat->get_id() != $this->get_id();
2742
    }
2743
2744
    /**
2745
     * Internal function used by move_to_cat().
2746
     */
2747
    private function applyCourseCodeToChildren()
2748
    {
2749
        $cats = self::load(null, null, null, $this->id, null);
2750
        $evals = Evaluation::load(null, null, null, $this->id, null);
2751
        $links = LinkFactory::load(
2752
            null,
2753
            null,
2754
            null,
2755
            null,
2756
            null,
2757
            $this->id,
2758
            null
2759
        );
2760
        /** @var Category $cat */
2761
        foreach ($cats as $cat) {
2762
            $cat->set_course_code($this->get_course_code());
2763
            $cat->save();
2764
            $cat->applyCourseCodeToChildren();
2765
        }
2766
2767
        foreach ($evals as $eval) {
2768
            $eval->set_course_code($this->get_course_code());
2769
            $eval->save();
2770
        }
2771
2772
        foreach ($links as $link) {
2773
            $link->delete();
2774
        }
2775
    }
2776
2777
    /**
2778
     * Internal function used by get_tree().
2779
     *
2780
     * @param int      $level
2781
     * @param int|null $visible
2782
     *
2783
     * @return array
2784
     */
2785
    private function add_subtree($targets, $level, $catid, $visible)
2786
    {
2787
        $subcats = self::load(null, null, null, $catid, $visible);
2788
2789
        if (!empty($subcats)) {
2790
            foreach ($subcats as $cat) {
2791
                $targets[] = [
2792
                    $cat->get_id(),
2793
                    $cat->get_name(),
2794
                    $level + 1,
2795
                ];
2796
                $targets = self::add_subtree(
0 ignored issues
show
Bug Best Practice introduced by
The method Category::add_subtree() is not static, but was called statically. ( Ignorable by Annotation )

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

2796
                /** @scrutinizer ignore-call */ 
2797
                $targets = self::add_subtree(
Loading history...
2797
                    $targets,
2798
                    $level + 1,
2799
                    $cat->get_id(),
2800
                    $visible
2801
                );
2802
            }
2803
        }
2804
2805
        return $targets;
2806
    }
2807
2808
    /**
2809
     * Calculate the current score on a gradebook category for a user.
2810
     *
2811
     * @param int      $userId   The user id
2812
     * @param Category $category The gradebook category
2813
     *
2814
     * @return float The score
2815
     */
2816
    private static function calculateCurrentScore($userId, $category)
2817
    {
2818
        if (empty($category)) {
2819
            return 0;
2820
        }
2821
2822
        $courseEvaluations = $category->get_evaluations($userId, true);
2823
        $courseLinks = $category->get_links($userId, true);
2824
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2825
2826
        $categoryScore = 0;
2827
        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...
2828
            /** @var AbstractLink $item */
2829
            $item = $evaluationsAndLinks[$i];
2830
            // Set session id from category
2831
            $item->set_session_id($category->get_session_id());
2832
            $score = $item->calc_score($userId);
2833
            $itemValue = 0;
2834
            if (!empty($score)) {
2835
                $divider = 0 == $score[1] ? 1 : $score[1];
2836
                $itemValue = $score[0] / $divider * $item->get_weight();
2837
            }
2838
2839
            $categoryScore += $itemValue;
2840
        }
2841
2842
        return api_float_val($categoryScore);
2843
    }
2844
}
2845