Passed
Push — master ( 7e3711...784f8c )
by Julito
09:08
created

Category::getSkillsForSelect()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 2
nop 0
dl 0
loc 11
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 static function show_message_resource_delete($course_id)
747
    {
748
        $course_id = (int) $course_id;
749
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
750
        $sql = 'SELECT count(*) AS num
751
                FROM '.$table.'
752
                WHERE
753
                    c_id = "'.$course_id.'" AND
754
                    visible = 3';
755
        $res = Database::query($sql);
756
        $option = Database::fetch_array($res, 'ASSOC');
757
        if ($option['num'] >= 1) {
758
            return '&nbsp;&nbsp;<span class="resource-deleted">
759
                (&nbsp;'.get_lang('The resource has been deleted').'&nbsp;)
760
                </span>';
761
        }
762
763
        return false;
764
    }
765
766
    /**
767
     * Shows all information of an category.
768
     *
769
     * @param int $categoryId
770
     *
771
     * @return array
772
     */
773
    public function showAllCategoryInfo($categoryId)
774
    {
775
        $categoryId = (int) $categoryId;
776
        if (empty($categoryId)) {
777
            return [];
778
        }
779
780
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
781
        $sql = 'SELECT * FROM '.$table.'
782
                WHERE id = '.$categoryId;
783
        $result = Database::query($sql);
784
        $row = Database::fetch_array($result, 'ASSOC');
785
786
        return $row;
787
    }
788
789
    /**
790
     * Checks if the certificate is available for the given user in this category.
791
     *
792
     * @param int $user_id User ID
793
     *
794
     * @return bool True if conditions match, false if fails
795
     */
796
    public function is_certificate_available($user_id)
797
    {
798
        $score = $this->calc_score(
799
            $user_id,
800
            null,
801
            $this->course_code,
802
            $this->session_id
803
        );
804
805
        if (isset($score) && isset($score[0])) {
806
            // Get a percentage score to compare to minimum certificate score
807
            // $certification_score = $score[0] / $score[1] * 100;
808
            // Get real score not a percentage.
809
            $certification_score = $score[0];
810
            if ($certification_score >= $this->certificate_min_score) {
811
                return true;
812
            }
813
        }
814
815
        return false;
816
    }
817
818
    /**
819
     * Is this category a course ?
820
     * A category is a course if it has a course code and no parent category.
821
     */
822
    public function is_course()
823
    {
824
        return isset($this->course_code) && !empty($this->course_code)
825
            && (!isset($this->parent) || 0 == $this->parent);
826
    }
827
828
    /**
829
     * Calculate the score of this category.
830
     *
831
     * @param int    $studentId   (default: all students - then the average is returned)
832
     * @param string $type
833
     * @param string $course_code
834
     * @param int    $session_id
835
     *
836
     * @return array (score sum, weight sum) or null if no scores available
837
     */
838
    public function calc_score(
839
        $studentId = null,
840
        $type = null,
841
        $course_code = '',
842
        $session_id = null
843
    ) {
844
        $key = 'category:'.$this->id.'student:'.(int) $studentId.'type:'.$type.'course:'.$course_code.'session:'.(int) $session_id;
845
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
846
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
847
848
        if ($cacheAvailable) {
849
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
850
            if ($cacheDriver->contains($key)) {
851
                return $cacheDriver->fetch($key);
852
            }
853
        }
854
        // Classic
855
        if (!empty($studentId) && '' == $type) {
856
            if (!empty($course_code)) {
857
                $cats = $this->get_subcategories(
858
                    $studentId,
859
                    $course_code,
860
                    $session_id
861
                );
862
                $evals = $this->get_evaluations($studentId, false, $course_code);
863
                $links = $this->get_links($studentId, false, $course_code);
864
            } else {
865
                $cats = $this->get_subcategories($studentId);
866
                $evals = $this->get_evaluations($studentId);
867
                $links = $this->get_links($studentId);
868
            }
869
870
            // Calculate score
871
            $count = 0;
872
            $ressum = 0;
873
            $weightsum = 0;
874
            if (!empty($cats)) {
875
                /** @var Category $cat */
876
                foreach ($cats as $cat) {
877
                    $cat->set_session_id($session_id);
878
                    $cat->set_course_code($course_code);
879
                    $cat->setStudentList($this->getStudentList());
880
                    $score = $cat->calc_score(
881
                        $studentId,
882
                        null,
883
                        $course_code,
884
                        $session_id
885
                    );
886
887
                    $catweight = 0;
888
                    if (0 != $cat->get_weight()) {
889
                        $catweight = $cat->get_weight();
890
                        $weightsum += $catweight;
891
                    }
892
893
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
894
                        $ressum += $score[0] / $score[1] * $catweight;
895
                    }
896
                }
897
            }
898
899
            if (!empty($evals)) {
900
                /** @var Evaluation $eval */
901
                foreach ($evals as $eval) {
902
                    $eval->setStudentList($this->getStudentList());
903
                    $evalres = $eval->calc_score($studentId);
904
                    if (isset($evalres) && 0 != $eval->get_weight()) {
905
                        $evalweight = $eval->get_weight();
906
                        $weightsum += $evalweight;
907
                        if (!empty($evalres[1])) {
908
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
909
                        }
910
                    } else {
911
                        if (0 != $eval->get_weight()) {
912
                            $evalweight = $eval->get_weight();
913
                            $weightsum += $evalweight;
914
                        }
915
                    }
916
                }
917
            }
918
919
            if (!empty($links)) {
920
                /** @var EvalLink|ExerciseLink $link */
921
                foreach ($links as $link) {
922
                    $link->setStudentList($this->getStudentList());
923
924
                    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...
925
                        $link->set_session_id($session_id);
926
                    }
927
928
                    $linkres = $link->calc_score($studentId, null);
929
                    if (!empty($linkres) && 0 != $link->get_weight()) {
930
                        $linkweight = $link->get_weight();
931
                        $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
932
                        $weightsum += $linkweight;
933
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
934
                    } else {
935
                        // Adding if result does not exists
936
                        if (0 != $link->get_weight()) {
937
                            $linkweight = $link->get_weight();
938
                            $weightsum += $linkweight;
939
                        }
940
                    }
941
                }
942
            }
943
        } else {
944
            if (!empty($course_code)) {
945
                $cats = $this->get_subcategories(
946
                    null,
947
                    $course_code,
948
                    $session_id
949
                );
950
                $evals = $this->get_evaluations(null, false, $course_code);
951
                $links = $this->get_links(null, false, $course_code);
952
            } else {
953
                $cats = $this->get_subcategories(null);
954
                $evals = $this->get_evaluations(null);
955
                $links = $this->get_links(null);
956
            }
957
958
            // Calculate score
959
            $ressum = 0;
960
            $weightsum = 0;
961
            $bestResult = 0;
962
            $totalScorePerStudent = [];
963
964
            if (!empty($cats)) {
965
                /** @var Category $cat */
966
                foreach ($cats as $cat) {
967
                    $cat->setStudentList($this->getStudentList());
968
                    $score = $cat->calc_score(
969
                        null,
970
                        $type,
971
                        $course_code,
972
                        $session_id
973
                    );
974
975
                    $catweight = 0;
976
                    if (0 != $cat->get_weight()) {
977
                        $catweight = $cat->get_weight();
978
                        $weightsum += $catweight;
979
                    }
980
981
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
982
                        $ressum += $score[0] / $score[1] * $catweight;
983
984
                        if ($ressum > $bestResult) {
985
                            $bestResult = $ressum;
986
                        }
987
                    }
988
                }
989
            }
990
991
            if (!empty($evals)) {
992
                if ('best' === $type) {
993
                    $studentList = $this->getStudentList();
994
                    foreach ($studentList as $student) {
995
                        $studentId = $student['user_id'];
996
                        foreach ($evals as $eval) {
997
                            $linkres = $eval->calc_score($studentId, null);
998
                            $linkweight = $eval->get_weight();
999
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1000
                            $ressum = $linkres[0] / $link_res_denom * $linkweight;
1001
1002
                            if (!isset($totalScorePerStudent[$studentId])) {
1003
                                $totalScorePerStudent[$studentId] = 0;
1004
                            }
1005
                            $totalScorePerStudent[$studentId] += $ressum;
1006
                        }
1007
                    }
1008
                } else {
1009
                    /** @var Evaluation $eval */
1010
                    foreach ($evals as $eval) {
1011
                        $evalres = $eval->calc_score(null, $type);
1012
                        $eval->setStudentList($this->getStudentList());
1013
1014
                        if (isset($evalres) && 0 != $eval->get_weight()) {
1015
                            $evalweight = $eval->get_weight();
1016
                            $weightsum += $evalweight;
1017
                            if (!empty($evalres[1])) {
1018
                                $ressum += $evalres[0] / $evalres[1] * $evalweight;
1019
                            }
1020
1021
                            if ($ressum > $bestResult) {
1022
                                $bestResult = $ressum;
1023
                            }
1024
                        } else {
1025
                            if (0 != $eval->get_weight()) {
1026
                                $evalweight = $eval->get_weight();
1027
                                $weightsum += $evalweight;
1028
                            }
1029
                        }
1030
                    }
1031
                }
1032
            }
1033
1034
            if (!empty($links)) {
1035
                $studentList = $this->getStudentList();
1036
                if ('best' === $type) {
1037
                    foreach ($studentList as $student) {
1038
                        $studentId = $student['user_id'];
1039
                        foreach ($links as $link) {
1040
                            $linkres = $link->calc_score($studentId, null);
1041
                            $linkweight = $link->get_weight();
1042
                            if ($linkres) {
1043
                                $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1044
                                $ressum = $linkres[0] / $link_res_denom * $linkweight;
1045
                            }
1046
1047
                            if (!isset($totalScorePerStudent[$studentId])) {
1048
                                $totalScorePerStudent[$studentId] = 0;
1049
                            }
1050
                            $totalScorePerStudent[$studentId] += $ressum;
1051
                        }
1052
                    }
1053
                } else {
1054
                    /** @var EvalLink|ExerciseLink $link */
1055
                    foreach ($links as $link) {
1056
                        $link->setStudentList($this->getStudentList());
1057
1058
                        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...
1059
                            $link->set_session_id($session_id);
1060
                        }
1061
1062
                        $linkres = $link->calc_score($studentId, $type);
1063
1064
                        if (!empty($linkres) && 0 != $link->get_weight()) {
1065
                            $linkweight = $link->get_weight();
1066
                            $link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
1067
1068
                            $weightsum += $linkweight;
1069
                            $ressum += $linkres[0] / $link_res_denom * $linkweight;
1070
                            if ($ressum > $bestResult) {
1071
                                $bestResult = $ressum;
1072
                            }
1073
                        } else {
1074
                            // Adding if result does not exists
1075
                            if (0 != $link->get_weight()) {
1076
                                $linkweight = $link->get_weight();
1077
                                $weightsum += $linkweight;
1078
                            }
1079
                        }
1080
                    }
1081
                }
1082
            }
1083
        }
1084
1085
        switch ($type) {
1086
            case 'best':
1087
                arsort($totalScorePerStudent);
1088
                $maxScore = current($totalScorePerStudent);
1089
1090
                return [$maxScore, $this->get_weight()];
1091
                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...
1092
            case 'average':
1093
                if (empty($ressum)) {
1094
                    if ($cacheAvailable) {
1095
                        $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...
1096
                    }
1097
1098
                    return null;
1099
                }
1100
1101
                if ($cacheAvailable) {
1102
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1103
                }
1104
1105
                return [$ressum, $weightsum];
1106
                break;
1107
            case 'ranking':
1108
                // category ranking is calculated in gradebook_data_generator.class.php
1109
                // function get_data
1110
                return null;
1111
1112
                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...
1113
                break;
1114
            default:
1115
                if ($cacheAvailable) {
1116
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1117
                }
1118
1119
                return [$ressum, $weightsum];
1120
                break;
1121
        }
1122
    }
1123
1124
    /**
1125
     * Delete this category and every subcategory, evaluation and result inside.
1126
     */
1127
    public function delete_all()
1128
    {
1129
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1130
        $evals = Evaluation::load(
1131
            null,
1132
            null,
1133
            $this->course_code,
1134
            $this->id,
1135
            null
1136
        );
1137
1138
        $links = LinkFactory::load(
1139
            null,
1140
            null,
1141
            null,
1142
            null,
1143
            $this->course_code,
1144
            $this->id,
1145
            null
1146
        );
1147
1148
        if (!empty($cats)) {
1149
            /** @var Category $cat */
1150
            foreach ($cats as $cat) {
1151
                $cat->delete_all();
1152
                $cat->delete();
1153
            }
1154
        }
1155
1156
        if (!empty($evals)) {
1157
            /** @var Evaluation $eval */
1158
            foreach ($evals as $eval) {
1159
                $eval->delete_with_results();
1160
            }
1161
        }
1162
1163
        if (!empty($links)) {
1164
            /** @var AbstractLink $link */
1165
            foreach ($links as $link) {
1166
                $link->delete();
1167
            }
1168
        }
1169
1170
        $this->delete();
1171
    }
1172
1173
    /**
1174
     * Return array of Category objects where a student is subscribed to.
1175
     *
1176
     * @param int    $stud_id
1177
     * @param string $course_code
1178
     * @param int    $session_id
1179
     *
1180
     * @return array
1181
     */
1182
    public function get_root_categories_for_student(
1183
        $stud_id,
1184
        $course_code = null,
1185
        $session_id = null
1186
    ) {
1187
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1188
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1189
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1190
1191
        $course_code = Database::escape_string($course_code);
1192
        $session_id = (int) $session_id;
1193
1194
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1195
1196
        if (!api_is_allowed_to_edit()) {
1197
            $sql .= ' AND visible = 1';
1198
            //proceed with checks on optional parameters course & session
1199
            if (!empty($course_code)) {
1200
                // TODO: considering it highly improbable that a user would get here
1201
                // if he doesn't have the rights to view this course and this
1202
                // session, we don't check his registration to these, but this
1203
                // could be an improvement
1204
                if (!empty($session_id)) {
1205
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1206
                } else {
1207
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1208
                }
1209
            } else {
1210
                //no optional parameter, proceed as usual
1211
                $sql .= ' AND course_code in
1212
                     (
1213
                        SELECT c.code
1214
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1215
                        ON (cu.c_id = c.id)
1216
                        WHERE cu.user_id = '.intval($stud_id).'
1217
                        AND cu.status = '.STUDENT.'
1218
                    )';
1219
            }
1220
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1221
            //proceed with checks on optional parameters course & session
1222
            if (!empty($course_code)) {
1223
                // TODO: considering it highly improbable that a user would get here
1224
                // if he doesn't have the rights to view this course and this
1225
                // session, we don't check his registration to these, but this
1226
                // could be an improvement
1227
                $sql .= " AND course_code  = '".$course_code."'";
1228
                if (!empty($session_id)) {
1229
                    $sql .= " AND session_id = ".$session_id;
1230
                } else {
1231
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1232
                }
1233
            } else {
1234
                $sql .= ' AND course_code IN
1235
                     (
1236
                        SELECT c.code
1237
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1238
                        ON (cu.c_id = c.id)
1239
                        WHERE
1240
                            cu.user_id = '.api_get_user_id().' AND
1241
                            cu.status = '.COURSEMANAGER.'
1242
                    )';
1243
            }
1244
        } elseif (api_is_platform_admin()) {
1245
            if (isset($session_id) && 0 != $session_id) {
1246
                $sql .= ' AND session_id='.$session_id;
1247
            } else {
1248
                $sql .= ' AND coalesce(session_id,0)=0';
1249
            }
1250
        }
1251
        $result = Database::query($sql);
1252
        $cats = self::create_category_objects_from_sql_result($result);
1253
1254
        // course independent categories
1255
        if (empty($course_code)) {
1256
            $cats = $this->getIndependentCategoriesWithStudentResult(
1257
                0,
1258
                $stud_id,
1259
                $cats
1260
            );
1261
        }
1262
1263
        return $cats;
1264
    }
1265
1266
    /**
1267
     * Return array of Category objects where a teacher is admin for.
1268
     *
1269
     * @param int    $user_id     (to return everything, use 'null' here)
1270
     * @param string $course_code (optional)
1271
     * @param int    $session_id  (optional)
1272
     *
1273
     * @return array
1274
     */
1275
    public function get_root_categories_for_teacher(
1276
        $user_id,
1277
        $course_code = null,
1278
        $session_id = null
1279
    ) {
1280
        if (null == $user_id) {
1281
            return self::load(null, null, $course_code, 0, null, $session_id);
1282
        }
1283
1284
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1285
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1286
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1287
1288
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1289
                WHERE parent_id = 0 ';
1290
        if (!empty($course_code)) {
1291
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1292
            if (!empty($session_id)) {
1293
                $sql .= " AND session_id = ".(int) $session_id;
1294
            }
1295
        } else {
1296
            $sql .= ' AND course_code in
1297
                 (
1298
                    SELECT c.code
1299
                    FROM '.$main_course_user_table.' cu
1300
                    INNER JOIN '.$courseTable.' c
1301
                    ON (cu.c_id = c.id)
1302
                    WHERE user_id = '.intval($user_id).'
1303
                )';
1304
        }
1305
        $result = Database::query($sql);
1306
        $cats = self::create_category_objects_from_sql_result($result);
1307
        // course independent categories
1308
        if (isset($course_code)) {
1309
            $indcats = self::load(
1310
                null,
1311
                $user_id,
1312
                $course_code,
1313
                0,
1314
                null,
1315
                $session_id
1316
            );
1317
            $cats = array_merge($cats, $indcats);
1318
        }
1319
1320
        return $cats;
1321
    }
1322
1323
    /**
1324
     * Can this category be moved to somewhere else ?
1325
     * The root and courses cannot be moved.
1326
     *
1327
     * @return bool
1328
     */
1329
    public function is_movable()
1330
    {
1331
        return !(!isset($this->id) || 0 == $this->id || $this->is_course());
1332
    }
1333
1334
    /**
1335
     * Generate an array of possible categories where this category can be moved to.
1336
     * Notice: its own parent will be included in the list: it's up to the frontend
1337
     * to disable this element.
1338
     *
1339
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1340
     */
1341
    public function get_target_categories()
1342
    {
1343
        // the root or a course -> not movable
1344
        if (!$this->is_movable()) {
1345
            return null;
1346
        } else {
1347
            // otherwise:
1348
            // - course independent category
1349
            //   -> movable to root or other independent categories
1350
            // - category inside a course
1351
            //   -> movable to root, independent categories or categories inside the course
1352
            $user = api_is_platform_admin() ? null : api_get_user_id();
1353
            $targets = [];
1354
            $level = 0;
1355
1356
            $root = [0, get_lang('Main folder'), $level];
1357
            $targets[] = $root;
1358
1359
            if (isset($this->course_code) && !empty($this->course_code)) {
1360
                $crscats = self::load(null, null, $this->course_code, 0);
1361
                foreach ($crscats as $cat) {
1362
                    if ($this->can_be_moved_to_cat($cat)) {
1363
                        $targets[] = [
1364
                            $cat->get_id(),
1365
                            $cat->get_name(),
1366
                            $level + 1,
1367
                        ];
1368
                        $targets = $this->addTargetSubcategories(
1369
                            $targets,
1370
                            $level + 1,
1371
                            $cat->get_id()
1372
                        );
1373
                    }
1374
                }
1375
            }
1376
1377
            $indcats = self::load(null, $user, 0, 0);
1378
            foreach ($indcats as $cat) {
1379
                if ($this->can_be_moved_to_cat($cat)) {
1380
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1381
                    $targets = $this->addTargetSubcategories(
1382
                        $targets,
1383
                        $level + 1,
1384
                        $cat->get_id()
1385
                    );
1386
                }
1387
            }
1388
1389
            return $targets;
1390
        }
1391
    }
1392
1393
    /**
1394
     * Move this category to the given category.
1395
     * If this category moves from inside a course to outside,
1396
     * its course code must be changed, as well as the course code
1397
     * of all underlying categories and evaluations. All links will
1398
     * be deleted as well !
1399
     */
1400
    public function move_to_cat($cat)
1401
    {
1402
        $this->set_parent_id($cat->get_id());
1403
        if ($this->get_course_code() != $cat->get_course_code()) {
1404
            $this->set_course_code($cat->get_course_code());
1405
            $this->applyCourseCodeToChildren();
1406
        }
1407
        $this->save();
1408
    }
1409
1410
    /**
1411
     * Generate an array of all categories the user can navigate to.
1412
     */
1413
    public function get_tree()
1414
    {
1415
        $targets = [];
1416
        $level = 0;
1417
        $root = [0, get_lang('Main folder'), $level];
1418
        $targets[] = $root;
1419
1420
        // course or platform admin
1421
        if (api_is_allowed_to_edit()) {
1422
            $user = api_is_platform_admin() ? null : api_get_user_id();
1423
            $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

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

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

2670
                /** @scrutinizer ignore-call */ 
2671
                $targets = self::add_subtree(
Loading history...
2671
                    $targets,
2672
                    $level + 1,
2673
                    $cat->get_id(),
2674
                    $visible
2675
                );
2676
            }
2677
        }
2678
2679
        return $targets;
2680
    }
2681
2682
    /**
2683
     * Calculate the current score on a gradebook category for a user.
2684
     * @return float The score
2685
     */
2686
    private static function calculateCurrentScore(int $userId, GradebookCategory $category)
2687
    {
2688
        if (null === $category) {
2689
            return 0;
2690
        }
2691
2692
        $categoryList = self::load($category->getId());
2693
        $category = $categoryList[0] ?? null;
2694
2695
        if (null === $category) {
2696
            return 0;
2697
        }
2698
2699
        $courseEvaluations = $category->get_evaluations($userId, true);
2700
        $courseLinks = $category->get_links($userId, true);
2701
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2702
        $count = count($evaluationsAndLinks);
2703
        if (empty($count)) {
2704
            return 0;
2705
        }
2706
2707
        $categoryScore = 0;
2708
        for ($i = 0; $i < $count; $i++) {
2709
            /** @var AbstractLink $item */
2710
            $item = $evaluationsAndLinks[$i];
2711
            // Set session id from category
2712
            $item->set_session_id($category->get_session_id());
2713
            $score = $item->calc_score($userId);
2714
            $itemValue = 0;
2715
            if (!empty($score)) {
2716
                $divider = 0 == $score[1] ? 1 : $score[1];
2717
                $itemValue = $score[0] / $divider * $item->get_weight();
2718
            }
2719
2720
            $categoryScore += $itemValue;
2721
        }
2722
2723
        return api_float_val($categoryScore);
2724
    }
2725
}
2726