Passed
Push — master ( 668bf0...77aaf8 )
by Julito
11:36 queued 12s
created

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

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

2071
            Event::/** @scrutinizer ignore-call */ 
2072
                   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...
2072
        }
2073
    }
2074
2075
    /**
2076
     * Generates a certificate for this user if everything matches.
2077
     *
2078
     * @param int  $category_id      gradebook id
2079
     * @param int  $user_id
2080
     * @param bool $sendNotification
2081
     * @param bool $skipGenerationIfExists
2082
     *
2083
     * @return array
2084
     */
2085
    public static function generateUserCertificate(
2086
        $category_id,
2087
        $user_id,
2088
        $sendNotification = false,
2089
        $skipGenerationIfExists = false
2090
    ) {
2091
        $user_id = (int) $user_id;
2092
        $category_id = (int) $category_id;
2093
2094
        // Generating the total score for a course
2095
        $category = self::load(
2096
            $category_id,
2097
            null,
2098
            null,
2099
            null,
2100
            null,
2101
            null,
2102
            false
2103
        );
2104
2105
        /** @var Category $category */
2106
        $category = $category[0];
2107
2108
        if (empty($category)) {
2109
            return false;
2110
        }
2111
2112
        $sessionId = $category->get_session_id();
2113
        $courseCode = $category->get_course_code();
2114
        $courseInfo = api_get_course_info($courseCode);
2115
        $courseId = $courseInfo['real_id'];
2116
2117
        $userFinishedCourse = self::userFinishedCourse(
2118
            $user_id,
2119
            $category,
2120
            true
2121
        );
2122
2123
        if (!$userFinishedCourse) {
2124
            return false;
2125
        }
2126
2127
        $skillToolEnabled = Skill::hasAccessToUserSkill(
2128
            api_get_user_id(),
2129
            $user_id
2130
        );
2131
2132
        $userHasSkills = false;
2133
        if ($skillToolEnabled) {
2134
            $skill = new Skill();
2135
            $skill->addSkillToUser(
2136
                $user_id,
2137
                $category,
2138
                $courseId,
2139
                $sessionId
2140
            );
2141
2142
            $objSkillRelUser = new SkillRelUser();
2143
            $userSkills = $objSkillRelUser->getUserSkills(
2144
                $user_id,
2145
                $courseId,
2146
                $sessionId
2147
            );
2148
            $userHasSkills = !empty($userSkills);
2149
        }
2150
2151
        // Block certification links depending gradebook configuration (generate certifications)
2152
        if (empty($category->getGenerateCertificates())) {
2153
            if ($userHasSkills) {
2154
                return [
2155
                    'badge_link' => Display::toolbarButton(
2156
                        get_lang('Export badges'),
2157
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2158
                        'external-link'
2159
                    ),
2160
                ];
2161
            }
2162
2163
            return false;
2164
        }
2165
2166
        $scoretotal = $category->calc_score($user_id);
2167
2168
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2169
        // file load this variable as a global
2170
        $scoredisplay = ScoreDisplay::instance();
2171
        $my_score_in_gradebook = $scoredisplay->display_score(
2172
            $scoretotal,
2173
            SCORE_SIMPLE
2174
        );
2175
2176
        $my_certificate = GradebookUtils::get_certificate_by_user_id(
2177
            $category_id,
2178
            $user_id
2179
        );
2180
2181
        if ($skipGenerationIfExists && !empty($my_certificate)) {
2182
            return false;
2183
        }
2184
        if (empty($my_certificate)) {
2185
            GradebookUtils::registerUserInfoAboutCertificate(
2186
                $category_id,
2187
                $user_id,
2188
                $my_score_in_gradebook,
2189
                api_get_utc_datetime()
2190
            );
2191
            $my_certificate = GradebookUtils::get_certificate_by_user_id(
2192
                $category_id,
2193
                $user_id
2194
            );
2195
        }
2196
2197
        $html = [];
2198
        if (!empty($my_certificate)) {
2199
            $certificate_obj = new Certificate(
2200
                $my_certificate['id'],
2201
                0,
2202
                $sendNotification
2203
            );
2204
2205
            $fileWasGenerated = $certificate_obj->isHtmlFileGenerated();
2206
2207
            // Fix when using custom certificate BT#15937
2208
            if ('true' === api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate')) {
2209
                $infoCertificate = CustomCertificatePlugin::getCertificateData($my_certificate['id'], $user_id);
2210
                if (!empty($infoCertificate)) {
2211
                    $fileWasGenerated = true;
2212
                }
2213
            }
2214
2215
            if (!empty($fileWasGenerated)) {
2216
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'].'&user_id='.$user_id;
2217
                $certificates = Display::toolbarButton(
2218
                    get_lang('Display certificate'),
2219
                    $url,
2220
                    'eye',
2221
                    'primary',
2222
                    ['target' => '_blank']
2223
                );
2224
2225
                $exportToPDF = Display::url(
2226
                    Display::return_icon(
2227
                        'pdf.png',
2228
                        get_lang('Export to PDF'),
2229
                        [],
2230
                        ICON_SIZE_MEDIUM
2231
                    ),
2232
                    "$url&action=export"
2233
                );
2234
2235
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2236
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2237
                if ('true' === $hideExportLink || (api_is_student() && 'true' === $hideExportLinkStudent)) {
2238
                    $exportToPDF = null;
2239
                }
2240
2241
                $html = [
2242
                    'certificate_link' => $certificates,
2243
                    'pdf_link' => $exportToPDF,
2244
                    'pdf_url' => "$url&action=export",
2245
                ];
2246
            }
2247
2248
            if ($skillToolEnabled && $userHasSkills) {
2249
                $html['badge_link'] = Display::toolbarButton(
2250
                    get_lang('Export badges'),
2251
                    api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2252
                    'external-link'
2253
                );
2254
            }
2255
2256
            return $html;
2257
        }
2258
    }
2259
2260
    /**
2261
     * @param int   $catId
2262
     * @param array $userList
2263
     */
2264
    public static function generateCertificatesInUserList($catId, $userList)
2265
    {
2266
        if (!empty($userList)) {
2267
            foreach ($userList as $userInfo) {
2268
                self::generateUserCertificate($catId, $userInfo['user_id']);
2269
            }
2270
        }
2271
    }
2272
2273
    /**
2274
     * @param int   $catId
2275
     * @param array $userList
2276
     */
2277
    public static function exportAllCertificates(
2278
        $catId,
2279
        $userList = []
2280
    ) {
2281
        $orientation = api_get_configuration_value('certificate_pdf_orientation');
2282
2283
        $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...
2284
        if (!empty($orientation)) {
2285
            $params['orientation'] = $orientation;
2286
        }
2287
2288
        $params['left'] = 0;
2289
        $params['right'] = 0;
2290
        $params['top'] = 0;
2291
        $params['bottom'] = 0;
2292
        $page_format = 'landscape' == $params['orientation'] ? 'A4-L' : 'A4';
2293
        $pdf = new PDF($page_format, $params['orientation'], $params);
2294
2295
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2296
        $certificate_path_list = [];
2297
2298
        if (!empty($certificate_list)) {
2299
            foreach ($certificate_list as $index => $value) {
2300
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2301
                    $value['user_id'],
2302
                    $catId
2303
                );
2304
                foreach ($list_certificate as $value_certificate) {
2305
                    $certificate_obj = new Certificate($value_certificate['id']);
2306
                    $certificate_obj->generate(['hide_print_button' => true]);
2307
                    if ($certificate_obj->isHtmlFileGenerated()) {
2308
                        $certificate_path_list[] = $certificate_obj->html_file;
2309
                    }
2310
                }
2311
            }
2312
        }
2313
2314
        if (!empty($certificate_path_list)) {
2315
            // Print certificates (without the common header/footer/watermark
2316
            //  stuff) and return as one multiple-pages PDF
2317
            $pdf->html_to_pdf(
2318
                $certificate_path_list,
2319
                get_lang('Certificates'),
2320
                null,
2321
                false,
2322
                false
2323
            );
2324
        }
2325
    }
2326
2327
    /**
2328
     * @param int $catId
2329
     */
2330
    public static function deleteAllCertificates($catId)
2331
    {
2332
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2333
        if (!empty($certificate_list)) {
2334
            foreach ($certificate_list as $index => $value) {
2335
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2336
                    $value['user_id'],
2337
                    $catId
2338
                );
2339
                foreach ($list_certificate as $value_certificate) {
2340
                    $certificate_obj = new Certificate($value_certificate['id']);
2341
                    $certificate_obj->delete(true);
2342
                }
2343
            }
2344
        }
2345
    }
2346
2347
    /**
2348
     * Check whether a user has finished a course by its gradebook.
2349
     *
2350
     * @param int       $userId           The user ID
2351
     * @param \Category $category         Optional. The gradebook category.
2352
     *                                    To check by the gradebook category
2353
     * @param bool      $recalculateScore Whether recalculate the score
2354
     *
2355
     * @return bool
2356
     */
2357
    public static function userFinishedCourse(
2358
        $userId,
2359
        \Category $category,
2360
        $recalculateScore = false
2361
    ) {
2362
        if (empty($category)) {
2363
            return false;
2364
        }
2365
2366
        $currentScore = self::getCurrentScore(
2367
            $userId,
2368
            $category,
2369
            $recalculateScore
2370
        );
2371
2372
        $minCertificateScore = $category->getCertificateMinScore();
2373
2374
        return $currentScore >= $minCertificateScore;
2375
    }
2376
2377
    /**
2378
     * Get the current score (as percentage) on a gradebook category for a user.
2379
     *
2380
     * @param int      $userId      The user id
2381
     * @param Category $category    The gradebook category
2382
     * @param bool     $recalculate
2383
     *
2384
     * @return float The score
2385
     */
2386
    public static function getCurrentScore(
2387
        $userId,
2388
        $category,
2389
        $recalculate = false
2390
    ) {
2391
        if (empty($category)) {
2392
            return 0;
2393
        }
2394
2395
        if ($recalculate) {
2396
            return self::calculateCurrentScore(
2397
                $userId,
2398
                $category
2399
            );
2400
        }
2401
2402
        $resultData = Database::select(
2403
            '*',
2404
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2405
            [
2406
                'where' => [
2407
                    'category_id = ? AND user_id = ?' => [$category->get_id(), $userId],
2408
                ],
2409
                'order' => 'registered_at DESC',
2410
                'limit' => '1',
2411
            ],
2412
            'first'
2413
        );
2414
2415
        if (empty($resultData)) {
2416
            return 0;
2417
        }
2418
2419
        return $resultData['score'];
2420
    }
2421
2422
    /**
2423
     * Register the current score for a user on a category gradebook.
2424
     *
2425
     * @param float $score      The achieved score
2426
     * @param int   $userId     The user id
2427
     * @param int   $categoryId The gradebook category
2428
     *
2429
     * @return int The insert id
2430
     */
2431
    public static function registerCurrentScore($score, $userId, $categoryId)
2432
    {
2433
        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...
2434
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2435
            [
2436
                'category_id' => intval($categoryId),
2437
                'user_id' => intval($userId),
2438
                'score' => api_float_val($score),
2439
                'registered_at' => api_get_utc_datetime(),
2440
            ]
2441
        );
2442
    }
2443
2444
    /**
2445
     * @return array
2446
     */
2447
    public function getStudentList()
2448
    {
2449
        return $this->studentList;
2450
    }
2451
2452
    /**
2453
     * @param array $list
2454
     */
2455
    public function setStudentList($list)
2456
    {
2457
        $this->studentList = $list;
2458
    }
2459
2460
    /**
2461
     * @return string
2462
     */
2463
    public static function getUrl()
2464
    {
2465
        $url = Session::read('gradebook_dest');
2466
        if (empty($url)) {
2467
            // We guess the link
2468
            $courseInfo = api_get_course_info();
2469
            if (!empty($courseInfo)) {
2470
                return api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2471
            } else {
2472
                return api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2473
            }
2474
        }
2475
2476
        return $url;
2477
    }
2478
2479
    /**
2480
     * Destination is index.php or gradebook.php.
2481
     *
2482
     * @param string $url
2483
     */
2484
    public static function setUrl($url)
2485
    {
2486
        switch ($url) {
2487
            case 'gradebook.php':
2488
                $url = api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2489
                break;
2490
            case 'index.php':
2491
                $url = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2492
                break;
2493
        }
2494
        Session::write('gradebook_dest', $url);
2495
    }
2496
2497
    /**
2498
     * @return int
2499
     */
2500
    public function getGradeBooksToValidateInDependence()
2501
    {
2502
        return $this->gradeBooksToValidateInDependence;
2503
    }
2504
2505
    /**
2506
     * @param int $value
2507
     *
2508
     * @return Category
2509
     */
2510
    public function setGradeBooksToValidateInDependence($value)
2511
    {
2512
        $this->gradeBooksToValidateInDependence = $value;
2513
2514
        return $this;
2515
    }
2516
2517
    /**
2518
     * Return HTML code with links to download and view certificate.
2519
     *
2520
     * @return string
2521
     */
2522
    public static function getDownloadCertificateBlock(array $certificate)
2523
    {
2524
        if (!isset($certificate['pdf_url'])) {
2525
            return '';
2526
        }
2527
2528
        $downloadLink = Display::toolbarButton(
2529
            get_lang('Download certificate in PDF'),
2530
            $certificate['pdf_url'],
2531
            'file-pdf-o'
2532
        );
2533
        $viewLink = $certificate['certificate_link'];
2534
2535
        return "
2536
            <div class='panel panel-default'>
2537
                <div class='panel-body'>
2538
                    <h3 class='text-center'>".get_lang('You can now download your certificate by clicking here')."</h3>
2539
                    <div class='text-center'>$downloadLink $viewLink</div>
2540
                </div>
2541
            </div>
2542
        ";
2543
    }
2544
2545
    /**
2546
     * Find a gradebook category by the certificate ID.
2547
     *
2548
     * @param int $id certificate id
2549
     *
2550
     * @throws \Doctrine\ORM\NonUniqueResultException
2551
     *
2552
     * @return Category|null
2553
     */
2554
    public static function findByCertificate($id)
2555
    {
2556
        $category = Database::getManager()
2557
            ->createQuery('SELECT c.catId FROM ChamiloCoreBundle:GradebookCertificate c WHERE c.id = :id')
2558
            ->setParameters(['id' => $id])
2559
            ->getOneOrNullResult();
2560
2561
        if (empty($category)) {
2562
            return null;
2563
        }
2564
2565
        $category = self::load($category['catId']);
2566
2567
        if (empty($category)) {
2568
            return null;
2569
        }
2570
2571
        return $category[0];
2572
    }
2573
2574
    /**
2575
     * @param int $value
2576
     */
2577
    public function setDocumentId($value)
2578
    {
2579
        $this->documentId = (int) $value;
2580
    }
2581
2582
    /**
2583
     * @return int
2584
     */
2585
    public function getDocumentId()
2586
    {
2587
        return $this->documentId;
2588
    }
2589
2590
    /**
2591
     * Get the remaining weight in root category.
2592
     *
2593
     * @return int
2594
     */
2595
    public function getRemainingWeight()
2596
    {
2597
        $subCategories = $this->get_subcategories();
2598
2599
        $subWeight = 0;
2600
2601
        /** @var Category $subCategory */
2602
        foreach ($subCategories as $subCategory) {
2603
            $subWeight += $subCategory->get_weight();
2604
        }
2605
2606
        return $this->weight - $subWeight;
2607
    }
2608
2609
    /**
2610
     * @return int
2611
     */
2612
    public function getCourseId()
2613
    {
2614
        return $this->courseId;
2615
    }
2616
2617
    /**
2618
     * @param int $courseId
2619
     *
2620
     * @return Category
2621
     */
2622
    public function setCourseId($courseId)
2623
    {
2624
        $this->courseId = $courseId;
2625
2626
        return $this;
2627
    }
2628
2629
    /**
2630
     * @return Category
2631
     */
2632
    private static function create_root_category()
2633
    {
2634
        $cat = new Category();
2635
        $cat->set_id(0);
2636
        $cat->set_name(get_lang('Main folder'));
2637
        $cat->set_description(null);
2638
        $cat->set_user_id(0);
2639
        $cat->set_course_code(null);
2640
        $cat->set_parent_id(null);
2641
        $cat->set_weight(0);
2642
        $cat->set_visible(1);
2643
        $cat->setGenerateCertificates(0);
2644
        $cat->setIsRequirement(false);
2645
2646
        return $cat;
2647
    }
2648
2649
    /**
2650
     * @param Doctrine\DBAL\Driver\Statement|null $result
2651
     *
2652
     * @return array
2653
     */
2654
    private static function create_category_objects_from_sql_result($result)
2655
    {
2656
        $categories = [];
2657
        $allow = api_get_configuration_value('allow_gradebook_stats');
2658
        if ($allow) {
2659
            $em = Database::getManager();
2660
            $repo = $em->getRepository(GradebookCategory::class);
2661
        }
2662
2663
        while ($data = Database::fetch_array($result)) {
2664
            $cat = new Category();
2665
            $cat->set_id($data['id']);
2666
            $cat->set_name($data['name']);
2667
            $cat->set_description($data['description']);
2668
            $cat->set_user_id($data['user_id']);
2669
            $courseInfo = api_get_course_info_by_id($data['c_id']);
2670
            $cat->set_course_code($courseInfo['code']);
2671
            $cat->setCourseId($data['c_id']);
2672
            $cat->set_parent_id($data['parent_id']);
2673
            $cat->set_weight($data['weight']);
2674
            $cat->set_visible($data['visible']);
2675
            $cat->set_session_id($data['session_id']);
2676
            $cat->set_certificate_min_score($data['certif_min_score']);
2677
            $cat->set_grade_model_id($data['grade_model_id']);
2678
            $cat->set_locked($data['locked']);
2679
            $cat->setGenerateCertificates($data['generate_certificates']);
2680
            $cat->setIsRequirement($data['is_requirement']);
2681
            //$cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2682
            $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2683
            $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2684
            $cat->setDocumentId($data['document_id']);
2685
            if ($allow) {
2686
                $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...
2687
            }
2688
2689
            $categories[] = $cat;
2690
        }
2691
2692
        return $categories;
2693
    }
2694
2695
    /**
2696
     * Internal function used by get_target_categories().
2697
     *
2698
     * @param array $targets
2699
     * @param int   $level
2700
     * @param int   $catid
2701
     *
2702
     * @return array
2703
     */
2704
    private function addTargetSubcategories($targets, $level, $catid)
2705
    {
2706
        $subcats = self::load(null, null, null, $catid);
2707
        foreach ($subcats as $cat) {
2708
            if ($this->can_be_moved_to_cat($cat)) {
2709
                $targets[] = [
2710
                    $cat->get_id(),
2711
                    $cat->get_name(),
2712
                    $level + 1,
2713
                ];
2714
                $targets = $this->addTargetSubcategories(
2715
                    $targets,
2716
                    $level + 1,
2717
                    $cat->get_id()
2718
                );
2719
            }
2720
        }
2721
2722
        return $targets;
2723
    }
2724
2725
    /**
2726
     * Internal function used by get_target_categories() and addTargetSubcategories()
2727
     * Can this category be moved to the given category ?
2728
     * Impossible when origin and target are the same... children won't be processed
2729
     * either. (a category can't be moved to one of its own children).
2730
     */
2731
    private function can_be_moved_to_cat($cat)
2732
    {
2733
        return $cat->get_id() != $this->get_id();
2734
    }
2735
2736
    /**
2737
     * Internal function used by move_to_cat().
2738
     */
2739
    private function applyCourseCodeToChildren()
2740
    {
2741
        $cats = self::load(null, null, null, $this->id, null);
2742
        $evals = Evaluation::load(null, null, null, $this->id, null);
2743
        $links = LinkFactory::load(
2744
            null,
2745
            null,
2746
            null,
2747
            null,
2748
            null,
2749
            $this->id,
2750
            null
2751
        );
2752
        /** @var Category $cat */
2753
        foreach ($cats as $cat) {
2754
            $cat->set_course_code($this->get_course_code());
2755
            $cat->save();
2756
            $cat->applyCourseCodeToChildren();
2757
        }
2758
2759
        foreach ($evals as $eval) {
2760
            $eval->set_course_code($this->get_course_code());
2761
            $eval->save();
2762
        }
2763
2764
        foreach ($links as $link) {
2765
            $link->delete();
2766
        }
2767
    }
2768
2769
    /**
2770
     * Internal function used by get_tree().
2771
     *
2772
     * @param int      $level
2773
     * @param int|null $visible
2774
     *
2775
     * @return array
2776
     */
2777
    private function add_subtree($targets, $level, $catid, $visible)
2778
    {
2779
        $subcats = self::load(null, null, null, $catid, $visible);
2780
2781
        if (!empty($subcats)) {
2782
            foreach ($subcats as $cat) {
2783
                $targets[] = [
2784
                    $cat->get_id(),
2785
                    $cat->get_name(),
2786
                    $level + 1,
2787
                ];
2788
                $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

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