Passed
Push — master ( 251179...3b9d17 )
by Julito
09:58
created

Category::get_grade_model_id()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\GradebookCategory;
6
use Chamilo\CoreBundle\Entity\Course;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Course. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

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

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

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

2766
                /** @scrutinizer ignore-call */ 
2767
                $targets = self::add_subtree(
Loading history...
2767
                    $targets,
2768
                    $level + 1,
2769
                    $cat->get_id(),
2770
                    $visible
2771
                );
2772
            }
2773
        }
2774
2775
        return $targets;
2776
    }
2777
2778
    /**
2779
     * Calculate the current score on a gradebook category for a user.
2780
     *
2781
     * @param int      $userId   The user id
2782
     * @param Category $category The gradebook category
2783
     *
2784
     * @return float The score
2785
     */
2786
    private static function calculateCurrentScore($userId, $category)
2787
    {
2788
        if (empty($category)) {
2789
            return 0;
2790
        }
2791
2792
        $courseEvaluations = $category->get_evaluations($userId, true);
2793
        $courseLinks = $category->get_links($userId, true);
2794
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2795
2796
        $categoryScore = 0;
2797
        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...
2798
            /** @var AbstractLink $item */
2799
            $item = $evaluationsAndLinks[$i];
2800
            // Set session id from category
2801
            $item->set_session_id($category->get_session_id());
2802
            $score = $item->calc_score($userId);
2803
            $itemValue = 0;
2804
            if (!empty($score)) {
2805
                $divider = 0 == $score[1] ? 1 : $score[1];
2806
                $itemValue = $score[0] / $divider * $item->get_weight();
2807
            }
2808
2809
            $categoryScore += $itemValue;
2810
        }
2811
2812
        return api_float_val($categoryScore);
2813
    }
2814
}
2815