Passed
Push — master ( 8d1a37...25babe )
by Julito
22:45
created

Category::getGenerateCertificates()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\GradebookCategory;
6
use 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
     * Insert this category into the database.
539
     */
540
    public function add()
541
    {
542
        if (isset($this->name) && '-1' == $this->name) {
543
            return false;
544
        }
545
546
        if (isset($this->name) && isset($this->user_id)) {
547
            $em = Database::getManager();
548
549
            $courseInfo = api_get_course_info($this->course_code);
550
            $course = api_get_course_entity($courseInfo['real_id']);
551
            $parent = null;
552
            if (!empty($this->parent)) {
553
                $parent = $em->getRepository(GradebookCategory::class)->find($this->parent);
554
            }
555
556
            $category = new GradebookCategory();
557
            $category->setName($this->name);
558
            $category->setDescription($this->description);
559
            $category->setUser(api_get_user_entity($this->user_id));
560
            $category->setCourse($course);
561
            $category->setParent($parent);
562
            $category->setWeight($this->weight);
563
            $category->setVisible($this->visible ? true : false);
564
            $category->setCertifMinScore($this->certificate_min_score);
565
            $category->setSession(api_get_session_entity($this->session_id));
566
            $category->setGenerateCertificates($this->generateCertificates);
567
            if (!empty($this->grade_model_id)) {
568
                $model = $em->getRepository(\Chamilo\CoreBundle\Entity\GradeModel::class)->find($this->grade_model_id);
569
                $category->setGradeModel($model);
570
            }
571
572
            $category->setIsRequirement($this->isRequirement);
573
            $category->setLocked(0);
574
575
            $em->persist($category);
576
            $em->flush();
577
578
            $id = $category->getId();
579
            $this->set_id($id);
580
581
            if (!empty($id)) {
582
                $parent_id = $this->get_parent_id();
583
                $grade_model_id = $this->get_grade_model_id();
584
                if (0 == $parent_id) {
585
                    //do something
586
                    if (isset($grade_model_id) &&
587
                        !empty($grade_model_id) &&
588
                        '-1' != $grade_model_id
589
                    ) {
590
                        $obj = new GradeModel();
591
                        $components = $obj->get_components($grade_model_id);
592
                        $default_weight_setting = api_get_setting('gradebook_default_weight');
593
                        $default_weight = 100;
594
                        if (isset($default_weight_setting)) {
595
                            $default_weight = $default_weight_setting;
596
                        }
597
                        foreach ($components as $component) {
598
                            $gradebook = new Gradebook();
599
                            $params = [];
600
601
                            $params['name'] = $component['acronym'];
602
                            $params['description'] = $component['title'];
603
                            $params['user_id'] = api_get_user_id();
604
                            $params['parent_id'] = $id;
605
                            $params['weight'] = $component['percentage'] / 100 * $default_weight;
606
                            $params['session_id'] = api_get_session_id();
607
                            $params['course_code'] = $this->get_course_code();
608
609
                            $gradebook->save($params);
610
                        }
611
                    }
612
                }
613
            }
614
615
            $gradebook = new Gradebook();
616
            $gradebook->updateSkillsToGradeBook(
617
                $this->id,
618
                $this->get_skills(false)
619
            );
620
621
            return $id;
622
        }
623
    }
624
625
    /**
626
     * Update the properties of this category in the database.
627
     *
628
     * @todo fix me
629
     */
630
    public function save()
631
    {
632
        $em = Database::getManager();
633
        $repo = $em->getRepository(GradebookCategory::class);
634
635
        /** @var GradebookCategory $category */
636
        $category = $repo->find($this->id);
637
638
        if (null === $category) {
639
            return false;
640
        }
641
642
        $parent = null;
643
        if (!empty($this->parent)) {
644
            $parent = $repo->find($this->parent);
645
        }
646
        $course = api_get_course_entity();
647
648
        $category->setName($this->name);
649
        $category->setDescription($this->description);
650
        $category->setUser(api_get_user_entity($this->user_id));
651
        $category->setCourse($course);
652
        $category->setParent($parent);
653
        $category->setWeight($this->weight);
654
        $category->setVisible($this->visible);
655
        $category->setCertifMinScore($this->certificate_min_score);
656
        $category->setGenerateCertificates($this->generateCertificates);
657
        if (!empty($this->grade_model_id)) {
658
            $model = $em->getRepository(\Chamilo\CoreBundle\Entity\GradeModel::class)->find($this->grade_model_id);
659
            $category->setGradeModel($model);
660
        }
661
662
        $category->setIsRequirement($this->isRequirement);
663
664
        $em->persist($category);
665
        $em->flush();
666
667
        if (!empty($this->id)) {
668
            $parent_id = $this->get_parent_id();
669
            $grade_model_id = $this->get_grade_model_id();
670
            if (0 == $parent_id) {
671
                if (isset($grade_model_id) &&
672
                    !empty($grade_model_id) &&
673
                    '-1' != $grade_model_id
674
                ) {
675
                    $obj = new GradeModel();
676
                    $components = $obj->get_components($grade_model_id);
677
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
678
                    $default_weight = 100;
679
                    if (isset($default_weight_setting)) {
680
                        $default_weight = $default_weight_setting;
681
                    }
682
                    $final_weight = $this->get_weight();
683
                    if (!empty($final_weight)) {
684
                        $default_weight = $this->get_weight();
685
                    }
686
                    foreach ($components as $component) {
687
                        $gradebook = new Gradebook();
688
                        $params = [];
689
                        $params['name'] = $component['acronym'];
690
                        $params['description'] = $component['title'];
691
                        $params['user_id'] = api_get_user_id();
692
                        $params['parent_id'] = $this->id;
693
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
694
                        $params['session_id'] = api_get_session_id();
695
                        $params['course_code'] = $this->get_course_code();
696
                        $gradebook->save($params);
697
                    }
698
                }
699
            }
700
        }
701
702
        $gradebook = new Gradebook();
703
        $gradebook->updateSkillsToGradeBook(
704
            $this->id,
705
            $this->get_skills(false),
706
            true
707
        );
708
    }
709
710
    /**
711
     * Update link weights see #5168.
712
     *
713
     * @param int $new_weight
714
     */
715
    public function updateChildrenWeight($new_weight)
716
    {
717
        $links = $this->get_links();
718
        $old_weight = $this->get_weight();
719
720
        if (!empty($links)) {
721
            foreach ($links as $link_item) {
722
                if (isset($link_item)) {
723
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
724
                    $link_item->set_weight($new_item_weight);
725
                    $link_item->save();
726
                }
727
            }
728
        }
729
    }
730
731
    /**
732
     * Delete this evaluation from the database.
733
     */
734
    public function delete()
735
    {
736
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
737
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
738
        Database::query($sql);
739
    }
740
741
    /**
742
     * @param int $course_id
743
     *
744
     * @return bool|string
745
     */
746
    public function show_message_resource_delete($course_id)
747
    {
748
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
749
        $sql = 'SELECT count(*) AS num
750
                FROM '.$table.'
751
                WHERE
752
                    c_id = "'.Database::escape_string($course_id).'" AND
753
                    visible = 3';
754
        $res = Database::query($sql);
755
        $option = Database::fetch_array($res, 'ASSOC');
756
        if ($option['num'] >= 1) {
757
            return '&nbsp;&nbsp;<span class="resource-deleted">(&nbsp;'.get_lang('The resource has been deleted').'&nbsp;)</span>';
758
        }
759
760
        return false;
761
    }
762
763
    /**
764
     * Shows all information of an category.
765
     *
766
     * @param int $categoryId
767
     *
768
     * @return array
769
     */
770
    public function showAllCategoryInfo($categoryId)
771
    {
772
        $categoryId = (int) $categoryId;
773
        if (empty($categoryId)) {
774
            return [];
775
        }
776
777
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
778
        $sql = 'SELECT * FROM '.$table.'
779
                WHERE id = '.$categoryId;
780
        $result = Database::query($sql);
781
        $row = Database::fetch_array($result, 'ASSOC');
782
783
        return $row;
784
    }
785
786
    /**
787
     * Checks if the certificate is available for the given user in this category.
788
     *
789
     * @param int $user_id User ID
790
     *
791
     * @return bool True if conditions match, false if fails
792
     */
793
    public function is_certificate_available($user_id)
794
    {
795
        $score = $this->calc_score(
796
            $user_id,
797
            null,
798
            $this->course_code,
799
            $this->session_id
800
        );
801
802
        if (isset($score) && isset($score[0])) {
803
            // Get a percentage score to compare to minimum certificate score
804
            // $certification_score = $score[0] / $score[1] * 100;
805
            // Get real score not a percentage.
806
            $certification_score = $score[0];
807
            if ($certification_score >= $this->certificate_min_score) {
808
                return true;
809
            }
810
        }
811
812
        return false;
813
    }
814
815
    /**
816
     * Is this category a course ?
817
     * A category is a course if it has a course code and no parent category.
818
     */
819
    public function is_course()
820
    {
821
        return isset($this->course_code) && !empty($this->course_code)
822
            && (!isset($this->parent) || 0 == $this->parent);
823
    }
824
825
    /**
826
     * Calculate the score of this category.
827
     *
828
     * @param int    $studentId   (default: all students - then the average is returned)
829
     * @param string $type
830
     * @param string $course_code
831
     * @param int    $session_id
832
     *
833
     * @return array (score sum, weight sum) or null if no scores available
834
     */
835
    public function calc_score(
836
        $studentId = null,
837
        $type = null,
838
        $course_code = '',
839
        $session_id = null
840
    ) {
841
        $key = 'category:'.$this->id.'student:'.(int) $studentId.'type:'.$type.'course:'.$course_code.'session:'.(int) $session_id;
842
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
843
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
844
845
        if ($cacheAvailable) {
846
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
847
            if ($cacheDriver->contains($key)) {
848
                return $cacheDriver->fetch($key);
849
            }
850
        }
851
        // Classic
852
        if (!empty($studentId) && '' == $type) {
853
            if (!empty($course_code)) {
854
                $cats = $this->get_subcategories(
855
                    $studentId,
856
                    $course_code,
857
                    $session_id
858
                );
859
                $evals = $this->get_evaluations($studentId, false, $course_code);
860
                $links = $this->get_links($studentId, false, $course_code);
861
            } else {
862
                $cats = $this->get_subcategories($studentId);
863
                $evals = $this->get_evaluations($studentId);
864
                $links = $this->get_links($studentId);
865
            }
866
867
            // Calculate score
868
            $count = 0;
869
            $ressum = 0;
870
            $weightsum = 0;
871
            if (!empty($cats)) {
872
                /** @var Category $cat */
873
                foreach ($cats as $cat) {
874
                    $cat->set_session_id($session_id);
875
                    $cat->set_course_code($course_code);
876
                    $cat->setStudentList($this->getStudentList());
877
                    $score = $cat->calc_score(
878
                        $studentId,
879
                        null,
880
                        $course_code,
881
                        $session_id
882
                    );
883
884
                    $catweight = 0;
885
                    if (0 != $cat->get_weight()) {
886
                        $catweight = $cat->get_weight();
887
                        $weightsum += $catweight;
888
                    }
889
890
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
891
                        $ressum += $score[0] / $score[1] * $catweight;
892
                    }
893
                }
894
            }
895
896
            if (!empty($evals)) {
897
                /** @var Evaluation $eval */
898
                foreach ($evals as $eval) {
899
                    $eval->setStudentList($this->getStudentList());
900
                    $evalres = $eval->calc_score($studentId);
901
                    if (isset($evalres) && 0 != $eval->get_weight()) {
902
                        $evalweight = $eval->get_weight();
903
                        $weightsum += $evalweight;
904
                        if (!empty($evalres[1])) {
905
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
906
                        }
907
                    } else {
908
                        if (0 != $eval->get_weight()) {
909
                            $evalweight = $eval->get_weight();
910
                            $weightsum += $evalweight;
911
                        }
912
                    }
913
                }
914
            }
915
916
            if (!empty($links)) {
917
                /** @var EvalLink|ExerciseLink $link */
918
                foreach ($links as $link) {
919
                    $link->setStudentList($this->getStudentList());
920
921
                    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...
922
                        $link->set_session_id($session_id);
923
                    }
924
925
                    $linkres = $link->calc_score($studentId, null);
926
                    if (!empty($linkres) && 0 != $link->get_weight()) {
927
                        $linkweight = $link->get_weight();
928
                        $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
929
                        $weightsum += $linkweight;
930
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
931
                    } else {
932
                        // Adding if result does not exists
933
                        if (0 != $link->get_weight()) {
934
                            $linkweight = $link->get_weight();
935
                            $weightsum += $linkweight;
936
                        }
937
                    }
938
                }
939
            }
940
        } else {
941
            if (!empty($course_code)) {
942
                $cats = $this->get_subcategories(
943
                    null,
944
                    $course_code,
945
                    $session_id
946
                );
947
                $evals = $this->get_evaluations(null, false, $course_code);
948
                $links = $this->get_links(null, false, $course_code);
949
            } else {
950
                $cats = $this->get_subcategories(null);
951
                $evals = $this->get_evaluations(null);
952
                $links = $this->get_links(null);
953
            }
954
955
            // Calculate score
956
            $ressum = 0;
957
            $weightsum = 0;
958
            $bestResult = 0;
959
            $totalScorePerStudent = [];
960
961
            if (!empty($cats)) {
962
                /** @var Category $cat */
963
                foreach ($cats as $cat) {
964
                    $cat->setStudentList($this->getStudentList());
965
                    $score = $cat->calc_score(
966
                        null,
967
                        $type,
968
                        $course_code,
969
                        $session_id
970
                    );
971
972
                    $catweight = 0;
973
                    if (0 != $cat->get_weight()) {
974
                        $catweight = $cat->get_weight();
975
                        $weightsum += $catweight;
976
                    }
977
978
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
979
                        $ressum += $score[0] / $score[1] * $catweight;
980
981
                        if ($ressum > $bestResult) {
982
                            $bestResult = $ressum;
983
                        }
984
                    }
985
                }
986
            }
987
988
            if (!empty($evals)) {
989
                if ('best' === $type) {
990
                    $studentList = $this->getStudentList();
991
                    foreach ($studentList as $student) {
992
                        $studentId = $student['user_id'];
993
                        foreach ($evals as $eval) {
994
                            $linkres = $eval->calc_score($studentId, null);
995
                            $linkweight = $eval->get_weight();
996
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
997
                            $ressum = $linkres[0] / $link_res_denom * $linkweight;
998
999
                            if (!isset($totalScorePerStudent[$studentId])) {
1000
                                $totalScorePerStudent[$studentId] = 0;
1001
                            }
1002
                            $totalScorePerStudent[$studentId] += $ressum;
1003
                        }
1004
                    }
1005
                } else {
1006
                    /** @var Evaluation $eval */
1007
                    foreach ($evals as $eval) {
1008
                        $evalres = $eval->calc_score(null, $type);
1009
                        $eval->setStudentList($this->getStudentList());
1010
1011
                        if (isset($evalres) && 0 != $eval->get_weight()) {
1012
                            $evalweight = $eval->get_weight();
1013
                            $weightsum += $evalweight;
1014
                            if (!empty($evalres[1])) {
1015
                                $ressum += $evalres[0] / $evalres[1] * $evalweight;
1016
                            }
1017
1018
                            if ($ressum > $bestResult) {
1019
                                $bestResult = $ressum;
1020
                            }
1021
                        } else {
1022
                            if (0 != $eval->get_weight()) {
1023
                                $evalweight = $eval->get_weight();
1024
                                $weightsum += $evalweight;
1025
                            }
1026
                        }
1027
                    }
1028
                }
1029
            }
1030
1031
            if (!empty($links)) {
1032
                $studentList = $this->getStudentList();
1033
                if ('best' === $type) {
1034
                    foreach ($studentList as $student) {
1035
                        $studentId = $student['user_id'];
1036
                        foreach ($links as $link) {
1037
                            $linkres = $link->calc_score($studentId, null);
1038
                            $linkweight = $link->get_weight();
1039
                            if ($linkres) {
1040
                                $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1041
                                $ressum = $linkres[0] / $link_res_denom * $linkweight;
1042
                            }
1043
1044
                            if (!isset($totalScorePerStudent[$studentId])) {
1045
                                $totalScorePerStudent[$studentId] = 0;
1046
                            }
1047
                            $totalScorePerStudent[$studentId] += $ressum;
1048
                        }
1049
                    }
1050
                } else {
1051
                    /** @var EvalLink|ExerciseLink $link */
1052
                    foreach ($links as $link) {
1053
                        $link->setStudentList($this->getStudentList());
1054
1055
                        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...
1056
                            $link->set_session_id($session_id);
1057
                        }
1058
1059
                        $linkres = $link->calc_score($studentId, $type);
1060
1061
                        if (!empty($linkres) && 0 != $link->get_weight()) {
1062
                            $linkweight = $link->get_weight();
1063
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1064
1065
                            $weightsum += $linkweight;
1066
                            $ressum += $linkres[0] / $link_res_denom * $linkweight;
1067
                            if ($ressum > $bestResult) {
1068
                                $bestResult = $ressum;
1069
                            }
1070
                        } else {
1071
                            // Adding if result does not exists
1072
                            if (0 != $link->get_weight()) {
1073
                                $linkweight = $link->get_weight();
1074
                                $weightsum += $linkweight;
1075
                            }
1076
                        }
1077
                    }
1078
                }
1079
            }
1080
        }
1081
1082
        switch ($type) {
1083
            case 'best':
1084
                arsort($totalScorePerStudent);
1085
                $maxScore = current($totalScorePerStudent);
1086
1087
                return [$maxScore, $this->get_weight()];
1088
                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...
1089
            case 'average':
1090
                if (empty($ressum)) {
1091
                    if ($cacheAvailable) {
1092
                        $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...
1093
                    }
1094
1095
                    return null;
1096
                }
1097
1098
                if ($cacheAvailable) {
1099
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1100
                }
1101
1102
                return [$ressum, $weightsum];
1103
                break;
1104
            case 'ranking':
1105
                // category ranking is calculated in gradebook_data_generator.class.php
1106
                // function get_data
1107
                return null;
1108
1109
                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...
1110
                break;
1111
            default:
1112
                if ($cacheAvailable) {
1113
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1114
                }
1115
1116
                return [$ressum, $weightsum];
1117
                break;
1118
        }
1119
    }
1120
1121
    /**
1122
     * Delete this category and every subcategory, evaluation and result inside.
1123
     */
1124
    public function delete_all()
1125
    {
1126
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1127
        $evals = Evaluation::load(
1128
            null,
1129
            null,
1130
            $this->course_code,
1131
            $this->id,
1132
            null
1133
        );
1134
1135
        $links = LinkFactory::load(
1136
            null,
1137
            null,
1138
            null,
1139
            null,
1140
            $this->course_code,
1141
            $this->id,
1142
            null
1143
        );
1144
1145
        if (!empty($cats)) {
1146
            /** @var Category $cat */
1147
            foreach ($cats as $cat) {
1148
                $cat->delete_all();
1149
                $cat->delete();
1150
            }
1151
        }
1152
1153
        if (!empty($evals)) {
1154
            /** @var Evaluation $eval */
1155
            foreach ($evals as $eval) {
1156
                $eval->delete_with_results();
1157
            }
1158
        }
1159
1160
        if (!empty($links)) {
1161
            /** @var AbstractLink $link */
1162
            foreach ($links as $link) {
1163
                $link->delete();
1164
            }
1165
        }
1166
1167
        $this->delete();
1168
    }
1169
1170
    /**
1171
     * Return array of Category objects where a student is subscribed to.
1172
     *
1173
     * @param int    $stud_id
1174
     * @param string $course_code
1175
     * @param int    $session_id
1176
     *
1177
     * @return array
1178
     */
1179
    public function get_root_categories_for_student(
1180
        $stud_id,
1181
        $course_code = null,
1182
        $session_id = null
1183
    ) {
1184
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1185
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1186
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1187
1188
        $course_code = Database::escape_string($course_code);
1189
        $session_id = (int) $session_id;
1190
1191
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1192
1193
        if (!api_is_allowed_to_edit()) {
1194
            $sql .= ' AND visible = 1';
1195
            //proceed with checks on optional parameters course & session
1196
            if (!empty($course_code)) {
1197
                // TODO: considering it highly improbable that a user would get here
1198
                // if he doesn't have the rights to view this course and this
1199
                // session, we don't check his registration to these, but this
1200
                // could be an improvement
1201
                if (!empty($session_id)) {
1202
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1203
                } else {
1204
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1205
                }
1206
            } else {
1207
                //no optional parameter, proceed as usual
1208
                $sql .= ' AND course_code in
1209
                     (
1210
                        SELECT c.code
1211
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1212
                        ON (cu.c_id = c.id)
1213
                        WHERE cu.user_id = '.intval($stud_id).'
1214
                        AND cu.status = '.STUDENT.'
1215
                    )';
1216
            }
1217
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1218
            //proceed with checks on optional parameters course & session
1219
            if (!empty($course_code)) {
1220
                // TODO: considering it highly improbable that a user would get here
1221
                // if he doesn't have the rights to view this course and this
1222
                // session, we don't check his registration to these, but this
1223
                // could be an improvement
1224
                $sql .= " AND course_code  = '".$course_code."'";
1225
                if (!empty($session_id)) {
1226
                    $sql .= " AND session_id = ".$session_id;
1227
                } else {
1228
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1229
                }
1230
            } else {
1231
                $sql .= ' AND course_code IN
1232
                     (
1233
                        SELECT c.code
1234
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1235
                        ON (cu.c_id = c.id)
1236
                        WHERE
1237
                            cu.user_id = '.api_get_user_id().' AND
1238
                            cu.status = '.COURSEMANAGER.'
1239
                    )';
1240
            }
1241
        } elseif (api_is_platform_admin()) {
1242
            if (isset($session_id) && 0 != $session_id) {
1243
                $sql .= ' AND session_id='.$session_id;
1244
            } else {
1245
                $sql .= ' AND coalesce(session_id,0)=0';
1246
            }
1247
        }
1248
        $result = Database::query($sql);
1249
        $cats = self::create_category_objects_from_sql_result($result);
1250
1251
        // course independent categories
1252
        if (empty($course_code)) {
1253
            $cats = $this->getIndependentCategoriesWithStudentResult(
1254
                0,
1255
                $stud_id,
1256
                $cats
1257
            );
1258
        }
1259
1260
        return $cats;
1261
    }
1262
1263
    /**
1264
     * Return array of Category objects where a teacher is admin for.
1265
     *
1266
     * @param int    $user_id     (to return everything, use 'null' here)
1267
     * @param string $course_code (optional)
1268
     * @param int    $session_id  (optional)
1269
     *
1270
     * @return array
1271
     */
1272
    public function get_root_categories_for_teacher(
1273
        $user_id,
1274
        $course_code = null,
1275
        $session_id = null
1276
    ) {
1277
        if (null == $user_id) {
1278
            return self::load(null, null, $course_code, 0, null, $session_id);
1279
        }
1280
1281
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1282
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1283
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1284
1285
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1286
                WHERE parent_id = 0 ';
1287
        if (!empty($course_code)) {
1288
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1289
            if (!empty($session_id)) {
1290
                $sql .= " AND session_id = ".(int) $session_id;
1291
            }
1292
        } else {
1293
            $sql .= ' AND course_code in
1294
                 (
1295
                    SELECT c.code
1296
                    FROM '.$main_course_user_table.' cu
1297
                    INNER JOIN '.$courseTable.' c
1298
                    ON (cu.c_id = c.id)
1299
                    WHERE user_id = '.intval($user_id).'
1300
                )';
1301
        }
1302
        $result = Database::query($sql);
1303
        $cats = self::create_category_objects_from_sql_result($result);
1304
        // course independent categories
1305
        if (isset($course_code)) {
1306
            $indcats = self::load(
1307
                null,
1308
                $user_id,
1309
                $course_code,
1310
                0,
1311
                null,
1312
                $session_id
1313
            );
1314
            $cats = array_merge($cats, $indcats);
1315
        }
1316
1317
        return $cats;
1318
    }
1319
1320
    /**
1321
     * Can this category be moved to somewhere else ?
1322
     * The root and courses cannot be moved.
1323
     *
1324
     * @return bool
1325
     */
1326
    public function is_movable()
1327
    {
1328
        return !(!isset($this->id) || 0 == $this->id || $this->is_course());
1329
    }
1330
1331
    /**
1332
     * Generate an array of possible categories where this category can be moved to.
1333
     * Notice: its own parent will be included in the list: it's up to the frontend
1334
     * to disable this element.
1335
     *
1336
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1337
     */
1338
    public function get_target_categories()
1339
    {
1340
        // the root or a course -> not movable
1341
        if (!$this->is_movable()) {
1342
            return null;
1343
        } else {
1344
            // otherwise:
1345
            // - course independent category
1346
            //   -> movable to root or other independent categories
1347
            // - category inside a course
1348
            //   -> movable to root, independent categories or categories inside the course
1349
            $user = api_is_platform_admin() ? null : api_get_user_id();
1350
            $targets = [];
1351
            $level = 0;
1352
1353
            $root = [0, get_lang('Main folder'), $level];
1354
            $targets[] = $root;
1355
1356
            if (isset($this->course_code) && !empty($this->course_code)) {
1357
                $crscats = self::load(null, null, $this->course_code, 0);
1358
                foreach ($crscats as $cat) {
1359
                    if ($this->can_be_moved_to_cat($cat)) {
1360
                        $targets[] = [
1361
                            $cat->get_id(),
1362
                            $cat->get_name(),
1363
                            $level + 1,
1364
                        ];
1365
                        $targets = $this->addTargetSubcategories(
1366
                            $targets,
1367
                            $level + 1,
1368
                            $cat->get_id()
1369
                        );
1370
                    }
1371
                }
1372
            }
1373
1374
            $indcats = self::load(null, $user, 0, 0);
1375
            foreach ($indcats as $cat) {
1376
                if ($this->can_be_moved_to_cat($cat)) {
1377
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1378
                    $targets = $this->addTargetSubcategories(
1379
                        $targets,
1380
                        $level + 1,
1381
                        $cat->get_id()
1382
                    );
1383
                }
1384
            }
1385
1386
            return $targets;
1387
        }
1388
    }
1389
1390
    /**
1391
     * Move this category to the given category.
1392
     * If this category moves from inside a course to outside,
1393
     * its course code must be changed, as well as the course code
1394
     * of all underlying categories and evaluations. All links will
1395
     * be deleted as well !
1396
     */
1397
    public function move_to_cat($cat)
1398
    {
1399
        $this->set_parent_id($cat->get_id());
1400
        if ($this->get_course_code() != $cat->get_course_code()) {
1401
            $this->set_course_code($cat->get_course_code());
1402
            $this->applyCourseCodeToChildren();
1403
        }
1404
        $this->save();
1405
    }
1406
1407
    /**
1408
     * Generate an array of all categories the user can navigate to.
1409
     */
1410
    public function get_tree()
1411
    {
1412
        $targets = [];
1413
        $level = 0;
1414
        $root = [0, get_lang('Main folder'), $level];
1415
        $targets[] = $root;
1416
1417
        // course or platform admin
1418
        if (api_is_allowed_to_edit()) {
1419
            $user = api_is_platform_admin() ? null : api_get_user_id();
1420
            $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

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

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

2732
                /** @scrutinizer ignore-call */ 
2733
                $targets = self::add_subtree(
Loading history...
2733
                    $targets,
2734
                    $level + 1,
2735
                    $cat->get_id(),
2736
                    $visible
2737
                );
2738
            }
2739
        }
2740
2741
        return $targets;
2742
    }
2743
2744
    /**
2745
     * Calculate the current score on a gradebook category for a user.
2746
     * @return float The score
2747
     */
2748
    private static function calculateCurrentScore(int $userId, GradebookCategory $category)
2749
    {
2750
        if (null === $category) {
2751
            return 0;
2752
        }
2753
2754
        $categoryList = self::load($category->getId());
2755
        $category = $categoryList[0] ?? null;
2756
2757
        if (null === $category) {
2758
            return 0;
2759
        }
2760
2761
        $courseEvaluations = $category->get_evaluations($userId, true);
2762
        $courseLinks = $category->get_links($userId, true);
2763
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2764
        $count = count($evaluationsAndLinks);
2765
        if (empty($count)) {
2766
            return 0;
2767
        }
2768
2769
        $categoryScore = 0;
2770
        for ($i = 0; $i < $count; $i++) {
2771
            /** @var AbstractLink $item */
2772
            $item = $evaluationsAndLinks[$i];
2773
            // Set session id from category
2774
            $item->set_session_id($category->get_session_id());
2775
            $score = $item->calc_score($userId);
2776
            $itemValue = 0;
2777
            if (!empty($score)) {
2778
                $divider = 0 == $score[1] ? 1 : $score[1];
2779
                $itemValue = $score[0] / $divider * $item->get_weight();
2780
            }
2781
2782
            $categoryScore += $itemValue;
2783
        }
2784
2785
        return api_float_val($categoryScore);
2786
    }
2787
}
2788