Passed
Push — master ( eba669...1a8a38 )
by Julito
10:56
created

Category::showAllCategoryInfo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 14
rs 9.9666
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\GradebookCategory;
5
use ChamiloSession as Session;
6
7
/**
8
 * Class Category
9
 * Defines a gradebook Category object.
10
 *
11
 * @package chamilo.gradebook
12
 */
13
class Category implements GradebookItem
14
{
15
    public $studentList;
16
    public $evaluations;
17
    public $links;
18
    public $subCategories;
19
    private $id;
20
    private $name;
21
    private $description;
22
    private $user_id;
23
    private $course_code;
24
    private $courseId;
25
    private $parent;
26
    private $weight;
27
    private $visible;
28
    private $certificate_min_score;
29
    private $session_id;
30
    private $skills = [];
31
    private $grade_model_id;
32
    private $generateCertificates;
33
    private $isRequirement;
34
    private $courseDependency;
35
    private $minimumToValidate;
36
    /** @var int */
37
    private $gradeBooksToValidateInDependence;
38
39
    /**
40
     * Consctructor.
41
     */
42
    public function __construct()
43
    {
44
        $this->id = 0;
45
        $this->name = null;
46
        $this->description = null;
47
        $this->user_id = 0;
48
        $this->course_code = '';
49
        $this->courseId = 0;
50
        $this->parent = 0;
51
        $this->weight = 0;
52
        $this->visible = false;
53
        $this->certificate_min_score = 0;
54
        $this->session_id = 0;
55
        $this->grade_model_id = 0;
56
        $this->generateCertificates = false;
57
        $this->isRequirement = false;
58
        $this->courseDependency = [];
59
        $this->minimumToValidate = null;
60
    }
61
62
    /**
63
     * @return int
64
     */
65
    public function get_id()
66
    {
67
        return $this->id;
68
    }
69
70
    /**
71
     * @return string
72
     */
73
    public function get_name()
74
    {
75
        return $this->name;
76
    }
77
78
    /**
79
     * @return string
80
     */
81
    public function get_description()
82
    {
83
        return $this->description;
84
    }
85
86
    /**
87
     * @return int
88
     */
89
    public function get_user_id()
90
    {
91
        return $this->user_id;
92
    }
93
94
    /**
95
     * @return int|null
96
     */
97
    public function getCertificateMinScore()
98
    {
99
        if (!empty($this->certificate_min_score)) {
100
            return $this->certificate_min_score;
101
        } else {
102
            return null;
103
        }
104
    }
105
106
    /**
107
     * @return string
108
     */
109
    public function get_course_code()
110
    {
111
        return $this->course_code;
112
    }
113
114
    /**
115
     * @return int
116
     */
117
    public function get_parent_id()
118
    {
119
        return $this->parent;
120
    }
121
122
    /**
123
     * @return int
124
     */
125
    public function get_weight()
126
    {
127
        return $this->weight;
128
    }
129
130
    /**
131
     * @return bool
132
     */
133
    public function is_locked()
134
    {
135
        return isset($this->locked) && $this->locked == 1 ? true : false;
136
    }
137
138
    /**
139
     * @return bool
140
     */
141
    public function is_visible()
142
    {
143
        return $this->visible;
144
    }
145
146
    /**
147
     * Get $isRequirement.
148
     *
149
     * @return int
150
     */
151
    public function getIsRequirement()
152
    {
153
        return $this->isRequirement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->isRequirement returns the type boolean which is incompatible with the documented return type integer.
Loading history...
154
    }
155
156
    /**
157
     * @param int $id
158
     */
159
    public function set_id($id)
160
    {
161
        $this->id = $id;
162
    }
163
164
    /**
165
     * @param string $name
166
     */
167
    public function set_name($name)
168
    {
169
        $this->name = $name;
170
    }
171
172
    /**
173
     * @param string $description
174
     */
175
    public function set_description($description)
176
    {
177
        $this->description = $description;
178
    }
179
180
    /**
181
     * @param int $user_id
182
     */
183
    public function set_user_id($user_id)
184
    {
185
        $this->user_id = $user_id;
186
    }
187
188
    /**
189
     * @param string $course_code
190
     */
191
    public function set_course_code($course_code)
192
    {
193
        $this->course_code = $course_code;
194
    }
195
196
    /**
197
     * @param float $min_score
198
     */
199
    public function set_certificate_min_score($min_score = null)
200
    {
201
        $this->certificate_min_score = $min_score;
202
    }
203
204
    /**
205
     * @param int $parent
206
     */
207
    public function set_parent_id($parent)
208
    {
209
        $this->parent = intval($parent);
210
    }
211
212
    /**
213
     * Filters to int and sets the session ID.
214
     *
215
     * @param   int     The session ID from the Dokeos course session
216
     */
217
    public function set_session_id($session_id = 0)
218
    {
219
        $this->session_id = (int) $session_id;
220
    }
221
222
    /**
223
     * @param $weight
224
     */
225
    public function set_weight($weight)
226
    {
227
        $this->weight = $weight;
228
    }
229
230
    /**
231
     * @param $visible
232
     */
233
    public function set_visible($visible)
234
    {
235
        $this->visible = $visible;
236
    }
237
238
    /**
239
     * @param int $id
240
     */
241
    public function set_grade_model_id($id)
242
    {
243
        $this->grade_model_id = $id;
244
    }
245
246
    /**
247
     * @param $locked
248
     */
249
    public function set_locked($locked)
250
    {
251
        $this->locked = $locked;
0 ignored issues
show
Bug Best Practice introduced by
The property locked does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
252
    }
253
254
    /**
255
     * Set $isRequirement.
256
     *
257
     * @param int $isRequirement
258
     */
259
    public function setIsRequirement($isRequirement)
260
    {
261
        $this->isRequirement = $isRequirement;
262
    }
263
264
    /**
265
     * @param $value
266
     */
267
    public function setCourseListDependency($value)
268
    {
269
        $result = [];
270
        if (@unserialize($value) !== false) {
271
            $result = unserialize($value);
272
        }
273
274
        $this->courseDependency = $result;
275
    }
276
277
    /**
278
     * Course id list.
279
     *
280
     * @return array
281
     */
282
    public function getCourseListDependency()
283
    {
284
        return $this->courseDependency;
285
    }
286
287
    /**
288
     * @param int $value
289
     */
290
    public function setMinimumToValidate($value)
291
    {
292
        $this->minimumToValidate = $value;
293
    }
294
295
    public function getMinimumToValidate()
296
    {
297
        return $this->minimumToValidate;
298
    }
299
300
    /**
301
     * @return null|int
302
     */
303
    public function get_grade_model_id()
304
    {
305
        if ($this->grade_model_id < 0) {
306
            return null;
307
        }
308
309
        return $this->grade_model_id;
310
    }
311
312
    /**
313
     * @return string
314
     */
315
    public function get_type()
316
    {
317
        return 'category';
318
    }
319
320
    /**
321
     * @param bool $from_db
322
     *
323
     * @return array|resource
324
     */
325
    public function get_skills($from_db = true)
326
    {
327
        if ($from_db) {
328
            $categoryId = $this->get_id();
329
            $gradebook = new Gradebook();
330
            $skills = $gradebook->getSkillsByGradebook($categoryId);
331
        } else {
332
            $skills = $this->skills;
333
        }
334
335
        return $skills;
336
    }
337
338
    /**
339
     * @return array
340
     */
341
    public function getSkillsForSelect()
342
    {
343
        $skills = $this->get_skills();
344
        $skill_select = [];
345
        if (!empty($skills)) {
346
            foreach ($skills as $skill) {
347
                $skill_select[$skill['id']] = $skill['name'];
348
            }
349
        }
350
351
        return $skill_select;
352
    }
353
354
    /**
355
     * Set the generate_certificates value.
356
     *
357
     * @param int $generateCertificates
358
     */
359
    public function setGenerateCertificates($generateCertificates)
360
    {
361
        $this->generateCertificates = $generateCertificates;
362
    }
363
364
    /**
365
     * Get the generate_certificates value.
366
     *
367
     * @return int
368
     */
369
    public function getGenerateCertificates()
370
    {
371
        return $this->generateCertificates;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->generateCertificates returns the type boolean which is incompatible with the documented return type integer.
Loading history...
372
    }
373
374
    /**
375
     * @param int $id
376
     * @param int $session_id
377
     *
378
     * @return array
379
     */
380
    public static function loadSessionCategories(
381
        $id = null,
382
        $session_id = null
383
    ) {
384
        if (isset($id) && (int) $id === 0) {
385
            $cats = [];
386
            $cats[] = self::create_root_category();
387
388
            return $cats;
389
        }
390
        $courseId = api_get_course_int_id();
391
        $courseInfo = api_get_course_info_by_id($courseId);
392
        $courseCode = $courseInfo['code'];
393
        $session_id = intval($session_id);
394
395
        if (!empty($session_id)) {
396
            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
397
            $sql = 'SELECT id, c_id
398
                    FROM '.$table.'
399
                    WHERE session_id = '.$session_id;
400
            $result_session = Database::query($sql);
401
            if (Database::num_rows($result_session) > 0) {
402
                $categoryList = [];
403
                while ($data_session = Database::fetch_array($result_session)) {
404
                    $parent_id = $data_session['id'];
405
                    if ($data_session['c_id'] == $courseId) {
406
                        $categories = self::load($parent_id);
407
                        $categoryList = array_merge($categoryList, $categories);
408
                    }
409
                }
410
411
                return $categoryList;
412
            }
413
        }
414
    }
415
416
    /**
417
     * Retrieve categories and return them as an array of Category objects.
418
     *
419
     * @param int    $id          category id
420
     * @param int    $user_id     (category owner)
421
     * @param string $course_code
422
     * @param int    $parent_id   parent category
423
     * @param bool   $visible
424
     * @param int    $session_id  (in case we are in a session)
425
     * @param bool   $order_by    Whether to show all "session"
426
     *                            categories (true) or hide them (false) in case there is no session id
427
     *
428
     * @return array
429
     */
430
    public static function load(
431
        $id = null,
432
        $user_id = null,
433
        $course_code = null,
434
        $parent_id = null,
435
        $visible = null,
436
        $session_id = null,
437
        $order_by = null
438
    ) {
439
        //if the category given is explicitly 0 (not null), then create
440
        // a root category object (in memory)
441
        if (isset($id) && (int) $id === 0) {
442
            $cats = [];
443
            $cats[] = self::create_root_category();
444
445
            return $cats;
446
        }
447
448
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
449
        $sql = 'SELECT * FROM '.$tbl_grade_categories;
450
        $paramcount = 0;
451
        if (isset($id)) {
452
            $sql .= ' WHERE id = '.intval($id);
453
            $paramcount++;
454
        }
455
456
        if (isset($user_id)) {
457
            $user_id = intval($user_id);
458
            if ($paramcount != 0) {
459
                $sql .= ' AND';
460
            } else {
461
                $sql .= ' WHERE';
462
            }
463
            $sql .= ' user_id = '.intval($user_id);
464
            $paramcount++;
465
        }
466
467
        if (isset($course_code)) {
468
            if ($paramcount != 0) {
469
                $sql .= ' AND';
470
            } else {
471
                $sql .= ' WHERE';
472
            }
473
474
            if ($course_code == '0') {
475
                $sql .= ' c_id is null ';
476
            } else {
477
                $courseInfo = api_get_course_info($course_code);
478
                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...
479
                    $sql .= " c_id = '".intval($courseInfo['real_id'])."'";
480
                }
481
            }
482
483
            /*if ($show_session_categories !== true) {
484
                // a query on the course should show all
485
                // the categories inside sessions for this course
486
                // otherwise a special parameter is given to ask explicitely
487
                $sql .= " AND (session_id IS NULL OR session_id = 0) ";
488
            } else {*/
489
            if (empty($session_id)) {
490
                $sql .= ' AND (session_id IS NULL OR session_id = 0) ';
491
            } else {
492
                $sql .= ' AND session_id = '.(int) $session_id.' ';
493
            }
494
            //}
495
            $paramcount++;
496
        }
497
498
        if (isset($parent_id)) {
499
            if ($paramcount != 0) {
500
                $sql .= ' AND ';
501
            } else {
502
                $sql .= ' WHERE ';
503
            }
504
            $sql .= ' parent_id = '.intval($parent_id);
505
            $paramcount++;
506
        }
507
508
        if (isset($visible)) {
509
            if ($paramcount != 0) {
510
                $sql .= ' AND';
511
            } else {
512
                $sql .= ' WHERE';
513
            }
514
            $sql .= ' visible = '.intval($visible);
515
        }
516
517
        if (!empty($order_by)) {
518
            if (!empty($order_by) && $order_by != '') {
519
                $sql .= ' '.$order_by;
520
            }
521
        }
522
523
        $result = Database::query($sql);
524
        $categories = [];
525
        if (Database::num_rows($result) > 0) {
526
            $categories = self::create_category_objects_from_sql_result(
527
                $result
528
            );
529
        }
530
531
        return $categories;
532
    }
533
534
    /**
535
     * Create a category object from a GradebookCategory entity.
536
     *
537
     * @param GradebookCategory $gradebookCategory The entity
538
     *
539
     * @return \Category
540
     */
541
    public static function createCategoryObjectFromEntity(
542
        GradebookCategory $gradebookCategory
543
    ) {
544
        $category = new Category();
545
        $category->set_id($gradebookCategory->getId());
546
        $category->set_name($gradebookCategory->getName());
547
        $category->set_description($gradebookCategory->getDescription());
548
        $category->set_user_id($gradebookCategory->getUserId());
0 ignored issues
show
Bug introduced by
The method getUserId() does not exist on Chamilo\CoreBundle\Entity\GradebookCategory. Did you maybe mean getUser()? ( Ignorable by Annotation )

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

548
        $category->set_user_id($gradebookCategory->/** @scrutinizer ignore-call */ getUserId());

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...
549
        $category->set_course_code($gradebookCategory->getCourseCode());
0 ignored issues
show
Bug introduced by
The method getCourseCode() does not exist on Chamilo\CoreBundle\Entity\GradebookCategory. Did you maybe mean getCourse()? ( Ignorable by Annotation )

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

549
        $category->set_course_code($gradebookCategory->/** @scrutinizer ignore-call */ getCourseCode());

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...
550
        $category->set_parent_id($gradebookCategory->getParentId());
551
        $category->set_weight($gradebookCategory->getWeight());
552
        $category->set_visible($gradebookCategory->getVisible());
553
        $category->set_session_id($gradebookCategory->getSessionId());
554
        $category->set_certificate_min_score(
555
            $gradebookCategory->getCertifMinScore()
556
        );
557
        $category->set_grade_model_id($gradebookCategory->getGradeModelId());
558
        $category->set_locked($gradebookCategory->getLocked());
559
        $category->setGenerateCertificates(
560
            $gradebookCategory->getGenerateCertificates()
561
        );
562
        $category->setIsRequirement($gradebookCategory->getIsRequirement());
563
564
        return $category;
565
    }
566
567
    /**
568
     * Insert this category into the database.
569
     */
570
    public function add()
571
    {
572
        if (isset($this->name) && '-1' == $this->name) {
573
            return false;
574
        }
575
576
        if (isset($this->name) && isset($this->user_id)) {
577
            $em = Database::getManager();
578
579
            $courseInfo = api_get_course_info($this->course_code);
580
            $course = api_get_course_entity($courseInfo['real_id']);
581
582
            $category = new GradebookCategory();
583
            $category->setName($this->name);
584
            $category->setDescription($this->description);
585
            $category->setUser(api_get_user_entity($this->user_id));
586
            $category->setCourse($course);
587
            $category->setParentId($this->parent);
588
            $category->setWeight($this->weight);
589
            $category->setVisible($this->visible);
590
            $category->setCertifMinScore($this->certificate_min_score);
591
            $category->setSessionId($this->session_id);
592
            $category->setGenerateCertificates($this->generateCertificates);
593
            $category->setGradeModelId($this->grade_model_id);
594
            $category->setIsRequirement($this->isRequirement);
595
            $category->setLocked(false);
596
597
            $em->persist($category);
598
            $em->flush();
599
600
            $id = $category->getId();
601
            $this->set_id($id);
602
603
            if (!empty($id)) {
604
                $parent_id = $this->get_parent_id();
605
                $grade_model_id = $this->get_grade_model_id();
606
                if ($parent_id == 0) {
607
                    //do something
608
                    if (isset($grade_model_id) &&
609
                        !empty($grade_model_id) &&
610
                        $grade_model_id != '-1'
611
                    ) {
612
                        $obj = new GradeModel();
613
                        $components = $obj->get_components($grade_model_id);
614
                        $default_weight_setting = api_get_setting('gradebook_default_weight');
615
                        $default_weight = 100;
616
                        if (isset($default_weight_setting)) {
617
                            $default_weight = $default_weight_setting;
618
                        }
619
                        foreach ($components as $component) {
620
                            $gradebook = new Gradebook();
621
                            $params = [];
622
623
                            $params['name'] = $component['acronym'];
624
                            $params['description'] = $component['title'];
625
                            $params['user_id'] = api_get_user_id();
626
                            $params['parent_id'] = $id;
627
                            $params['weight'] = $component['percentage'] / 100 * $default_weight;
628
                            $params['session_id'] = api_get_session_id();
629
                            $params['course_code'] = $this->get_course_code();
630
631
                            $gradebook->save($params);
632
                        }
633
                    }
634
                }
635
            }
636
637
            $gradebook = new Gradebook();
638
            $gradebook->updateSkillsToGradeBook(
639
                $this->id,
640
                $this->get_skills(false)
641
            );
642
643
            return $id;
644
        }
645
    }
646
647
    /**
648
     * Update the properties of this category in the database.
649
     *
650
     * @todo fix me
651
     */
652
    public function save()
653
    {
654
        $em = Database::getManager();
655
656
        /** @var GradebookCategory $gradebookCategory */
657
        $gradebookCategory = $em
658
            ->getRepository('ChamiloCoreBundle:GradebookCategory')
659
            ->find($this->id);
660
661
        if (empty($gradebookCategory)) {
662
            return false;
663
        }
664
665
        $course = api_get_user_course_entity();
0 ignored issues
show
Bug introduced by
The function api_get_user_course_entity was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

665
        $course = /** @scrutinizer ignore-call */ api_get_user_course_entity();
Loading history...
666
667
        $gradebookCategory->setName($this->name);
668
        $gradebookCategory->setDescription($this->description);
669
        $gradebookCategory->setUserId($this->user_id);
0 ignored issues
show
Bug introduced by
The method setUserId() does not exist on Chamilo\CoreBundle\Entity\GradebookCategory. Did you maybe mean setUser()? ( Ignorable by Annotation )

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

669
        $gradebookCategory->/** @scrutinizer ignore-call */ 
670
                            setUserId($this->user_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...
670
        $gradebookCategory->setCourse($course);
671
        //$gradebookCategory->setCourseCode($this->course_code);
672
        $gradebookCategory->setParentId($this->parent);
673
        $gradebookCategory->setWeight($this->weight);
674
        $gradebookCategory->setVisible($this->visible);
675
        $gradebookCategory->setCertifMinScore($this->certificate_min_score);
676
        $gradebookCategory->setGenerateCertificates(
677
            $this->generateCertificates
678
        );
679
        $gradebookCategory->setGradeModelId($this->grade_model_id);
680
        $gradebookCategory->setIsRequirement($this->isRequirement);
681
682
        $em->merge($gradebookCategory);
683
        $em->flush();
684
685
        if (!empty($this->id)) {
686
            $parent_id = $this->get_parent_id();
687
            $grade_model_id = $this->get_grade_model_id();
688
            if ($parent_id == 0) {
689
                if (isset($grade_model_id) &&
690
                    !empty($grade_model_id) &&
691
                    $grade_model_id != '-1'
692
                ) {
693
                    $obj = new GradeModel();
694
                    $components = $obj->get_components($grade_model_id);
695
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
696
                    $default_weight = 100;
697
                    if (isset($default_weight_setting)) {
698
                        $default_weight = $default_weight_setting;
699
                    }
700
                    $final_weight = $this->get_weight();
701
                    if (!empty($final_weight)) {
702
                        $default_weight = $this->get_weight();
703
                    }
704
                    foreach ($components as $component) {
705
                        $gradebook = new Gradebook();
706
                        $params = [];
707
                        $params['name'] = $component['acronym'];
708
                        $params['description'] = $component['title'];
709
                        $params['user_id'] = api_get_user_id();
710
                        $params['parent_id'] = $this->id;
711
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
712
                        $params['session_id'] = api_get_session_id();
713
                        $params['course_code'] = $this->get_course_code();
714
                        $gradebook->save($params);
715
                    }
716
                }
717
            }
718
        }
719
720
        $gradebook = new Gradebook();
721
        $gradebook->updateSkillsToGradeBook(
722
            $this->id,
723
            $this->get_skills(false),
724
            true
725
        );
726
    }
727
728
    /**
729
     * Update link weights see #5168.
730
     *
731
     * @param type $new_weight
732
     */
733
    public function updateChildrenWeight($new_weight)
734
    {
735
        $links = $this->get_links();
736
        $old_weight = $this->get_weight();
737
738
        if (!empty($links)) {
739
            foreach ($links as $link_item) {
740
                if (isset($link_item)) {
741
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
742
                    $link_item->set_weight($new_item_weight);
743
                    $link_item->save();
744
                }
745
            }
746
        }
747
    }
748
749
    /**
750
     * Delete this evaluation from the database.
751
     */
752
    public function delete()
753
    {
754
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
755
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
756
        Database::query($sql);
757
    }
758
759
    /**
760
     * Not delete this category from the database,when visible=3 is category eliminated.
761
     *
762
     * @param int $courseId
763
     */
764
    public function update_category_delete($courseId)
765
    {
766
        $tbl_grade_categories = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
767
        $sql = 'UPDATE '.$tbl_grade_categories.' SET visible=3
768
                WHERE c_id ="'.intval($courseId).'"';
769
        Database::query($sql);
770
    }
771
772
    /**
773
     * Delete the gradebook categories from a course, including course sessions.
774
     *
775
     * @param string $courseCode
776
     */
777
    public static function deleteFromCourse($courseCode)
778
    {
779
        $em = Database::getManager();
780
        $categories = $em
781
            ->createQuery(
782
                'SELECT DISTINCT gc.sessionId
783
                FROM ChamiloCoreBundle:GradebookCategory gc WHERE gc.courseCode = :code'
784
            )
785
            ->setParameter('code', $courseCode)
786
            ->getResult();
787
788
        foreach ($categories as $category) {
789
            $cats = self::load(
790
                null,
791
                null,
792
                $courseCode,
793
                null,
794
                null,
795
                (int) $category['sessionId']
796
            );
797
798
            if (!empty($cats)) {
799
                /** @var self $cat */
800
                foreach ($cats as $cat) {
801
                    $cat->delete_all();
802
                }
803
            }
804
        }
805
    }
806
807
    /**
808
     * @param int $course_id
809
     *
810
     * @return bool|string
811
     */
812
    public function show_message_resource_delete($course_id)
813
    {
814
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
815
        $sql = 'SELECT count(*) AS num 
816
                FROM '.$table.'
817
                WHERE
818
                    c_id = "'.Database::escape_string($course_id).'" AND
819
                    visible = 3';
820
        $res = Database::query($sql);
821
        $option = Database::fetch_array($res, 'ASSOC');
822
        if ($option['num'] >= 1) {
823
            return '&nbsp;&nbsp;<span class="resource-deleted">(&nbsp;'.get_lang('ResourceDeleted').'&nbsp;)</span>';
824
        } else {
825
            return false;
826
        }
827
    }
828
829
    /**
830
     * Shows all information of an category.
831
     *
832
     * @param int $categoryId
833
     *
834
     * @return array
835
     */
836
    public function showAllCategoryInfo($categoryId = 0)
837
    {
838
        $categoryId = (int) $categoryId;
839
        if (empty($categoryId)) {
840
            return [];
841
        }
842
843
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
844
        $sql = 'SELECT * FROM '.$table.'
845
                WHERE id = '.$categoryId;
846
        $result = Database::query($sql);
847
        $row = Database::fetch_array($result, 'ASSOC');
848
849
        return $row;
850
    }
851
852
    /**
853
     * Check if a category name (with the same parent category) already exists.
854
     *
855
     * @param string $name   name to check (if not given, the name property of this object will be checked)
856
     * @param int    $parent parent category
857
     *
858
     * @return bool
859
     */
860
    public function does_name_exist($name, $parent)
861
    {
862
        if (!isset($name)) {
863
            $name = $this->name;
864
            $parent = $this->parent;
865
        }
866
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
867
        $sql = "SELECT count(id) AS number
868
                FROM $table
869
                WHERE name = '".Database::escape_string($name)."'";
870
871
        if (api_is_allowed_to_edit()) {
872
            $parent = self::load($parent);
873
            $code = $parent[0]->get_course_code();
874
            $courseInfo = api_get_course_info($code);
875
            $courseId = $courseInfo['real_id'];
876
            if (isset($code) && $code != '0') {
877
                $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
878
                $sql .= ' AND user_id IN (
879
                            SELECT user_id FROM '.$main_course_user_table.'
880
                            WHERE c_id = '.$courseId.' AND status = '.COURSEMANAGER.'
881
                        )';
882
            } else {
883
                $sql .= ' AND user_id = '.api_get_user_id();
884
            }
885
        } else {
886
            $sql .= ' AND user_id = '.api_get_user_id();
887
        }
888
889
        if (!isset($parent)) {
890
            $sql .= ' AND parent_id is null';
891
        } else {
892
            $sql .= ' AND parent_id = '.intval($parent);
893
        }
894
        $result = Database::query($sql);
895
        $number = Database::fetch_row($result);
896
897
        return $number[0] != 0;
898
    }
899
900
    /**
901
     * Checks if the certificate is available for the given user in this category.
902
     *
903
     * @param int $user_id User ID
904
     *
905
     * @return bool True if conditions match, false if fails
906
     */
907
    public function is_certificate_available($user_id)
908
    {
909
        $score = $this->calc_score(
910
            $user_id,
911
            null,
912
            $this->course_code,
913
            $this->session_id
914
        );
915
916
        if (isset($score) && isset($score[0])) {
917
            // Get a percentage score to compare to minimum certificate score
918
            // $certification_score = $score[0] / $score[1] * 100;
919
            // Get real score not a percentage.
920
            $certification_score = $score[0];
921
            if ($certification_score >= $this->certificate_min_score) {
922
                return true;
923
            }
924
        }
925
926
        return false;
927
    }
928
929
    /**
930
     * Is this category a course ?
931
     * A category is a course if it has a course code and no parent category.
932
     */
933
    public function is_course()
934
    {
935
        return isset($this->course_code) && !empty($this->course_code)
936
            && (!isset($this->parent) || $this->parent == 0);
937
    }
938
939
    /**
940
     * Calculate the score of this category.
941
     *
942
     * @param int    $stud_id     student id (default: all students - then the average is returned)
943
     * @param        $type
944
     * @param string $course_code
945
     * @param int    $session_id
946
     *
947
     * @return array (score sum, weight sum)
948
     *               or null if no scores available
949
     */
950
    public function calc_score(
951
        $stud_id = null,
952
        $type = null,
953
        $course_code = '',
954
        $session_id = null
955
    ) {
956
        // Classic
957
        if (!empty($stud_id) && $type == '') {
958
            if (!empty($course_code)) {
959
                $cats = $this->get_subcategories(
960
                    $stud_id,
961
                    $course_code,
962
                    $session_id
963
                );
964
                $evals = $this->get_evaluations($stud_id, false, $course_code);
965
                $links = $this->get_links($stud_id, false, $course_code);
966
            } else {
967
                $cats = $this->get_subcategories($stud_id);
968
                $evals = $this->get_evaluations($stud_id);
969
                $links = $this->get_links($stud_id);
970
            }
971
972
            // Calculate score
973
            $count = 0;
974
            $ressum = 0;
975
            $weightsum = 0;
976
977
            if (!empty($cats)) {
978
                /** @var Category $cat */
979
                foreach ($cats as $cat) {
980
                    $cat->set_session_id($session_id);
981
                    $cat->set_course_code($course_code);
982
                    $cat->setStudentList($this->getStudentList());
983
                    $score = $cat->calc_score(
984
                        $stud_id,
985
                        null,
986
                        $course_code,
987
                        $session_id
988
                    );
989
990
                    $catweight = 0;
991
                    if ($cat->get_weight() != 0) {
992
                        $catweight = $cat->get_weight();
993
                        $weightsum += $catweight;
994
                    }
995
996
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
997
                        $ressum += $score[0] / $score[1] * $catweight;
998
                    }
999
                }
1000
            }
1001
1002
            $students = [];
1003
            if (!empty($evals)) {
1004
                /** @var Evaluation $eval */
1005
                foreach ($evals as $eval) {
1006
                    $eval->setStudentList($this->getStudentList());
1007
                    $evalres = $eval->calc_score($stud_id, null);
1008
1009
                    if (isset($evalres) && $eval->get_weight() != 0) {
1010
                        $evalweight = $eval->get_weight();
1011
                        $weightsum += $evalweight;
1012
                        $count++;
1013
                        if (!empty($evalres[1])) {
1014
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
1015
                        }
1016
                    } else {
1017
                        if ($eval->get_weight() != 0) {
1018
                            $evalweight = $eval->get_weight();
1019
                            $weightsum += $evalweight;
1020
                        }
1021
                    }
1022
                }
1023
            }
1024
1025
            if (!empty($links)) {
1026
                /** @var EvalLink|ExerciseLink $link */
1027
                foreach ($links as $link) {
1028
                    $link->setStudentList($this->getStudentList());
1029
1030
                    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...
1031
                        $link->set_session_id($session_id);
1032
                    }
1033
1034
                    $linkres = $link->calc_score($stud_id, null);
1035
                    if (!empty($linkres) && $link->get_weight() != 0) {
1036
                        $students[$stud_id] = $linkres[0];
1037
                        $linkweight = $link->get_weight();
1038
                        $link_res_denom = $linkres[1] == 0 ? 1 : $linkres[1];
1039
                        $count++;
1040
                        $weightsum += $linkweight;
1041
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1042
                    } else {
1043
                        // Adding if result does not exists
1044
                        if ($link->get_weight() != 0) {
1045
                            $linkweight = $link->get_weight();
1046
                            $weightsum += $linkweight;
1047
                        }
1048
                    }
1049
                }
1050
            }
1051
        } else {
1052
            if (!empty($course_code)) {
1053
                $cats = $this->get_subcategories(
1054
                    null,
1055
                    $course_code,
1056
                    $session_id
1057
                );
1058
                $evals = $this->get_evaluations(null, false, $course_code);
1059
                $links = $this->get_links(null, false, $course_code);
1060
            } else {
1061
                $cats = $this->get_subcategories(null);
1062
                $evals = $this->get_evaluations(null);
1063
                $links = $this->get_links(null);
1064
            }
1065
1066
            // Calculate score
1067
            $count = 0;
1068
            $ressum = 0;
1069
            $weightsum = 0;
1070
            $bestResult = 0;
1071
1072
            if (!empty($cats)) {
1073
                /** @var Category $cat */
1074
                foreach ($cats as $cat) {
1075
                    $cat->setStudentList($this->getStudentList());
1076
                    $score = $cat->calc_score(
1077
                        null,
1078
                        $type,
1079
                        $course_code,
1080
                        $session_id
1081
                    );
1082
1083
                    $catweight = 0;
1084
                    if ($cat->get_weight() != 0) {
1085
                        $catweight = $cat->get_weight();
1086
                        $weightsum += $catweight;
1087
                    }
1088
1089
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
1090
                        $ressum += $score[0] / $score[1] * $catweight;
1091
1092
                        if ($ressum > $bestResult) {
1093
                            $bestResult = $ressum;
1094
                        }
1095
                    }
1096
                }
1097
            }
1098
1099
            if (!empty($evals)) {
1100
                /** @var Evaluation $eval */
1101
                foreach ($evals as $eval) {
1102
                    $evalres = $eval->calc_score(null, $type);
1103
                    $eval->setStudentList($this->getStudentList());
1104
1105
                    if (isset($evalres) && $eval->get_weight() != 0) {
1106
                        $evalweight = $eval->get_weight();
1107
                        $weightsum += $evalweight;
1108
                        $count++;
1109
                        if (!empty($evalres[1])) {
1110
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
1111
                        }
1112
1113
                        if ($ressum > $bestResult) {
1114
                            $bestResult = $ressum;
1115
                        }
1116
                    } else {
1117
                        if ($eval->get_weight() != 0) {
1118
                            $evalweight = $eval->get_weight();
1119
                            $weightsum += $evalweight;
1120
                        }
1121
                    }
1122
                }
1123
            }
1124
            if (!empty($links)) {
1125
                /** @var EvalLink|ExerciseLink $link */
1126
                foreach ($links as $link) {
1127
                    $link->setStudentList($this->getStudentList());
1128
1129
                    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...
1130
                        $link->set_session_id($session_id);
1131
                    }
1132
1133
                    $linkres = $link->calc_score($stud_id, $type);
1134
                    if (!empty($linkres) && $link->get_weight() != 0) {
1135
                        $students[$stud_id] = $linkres[0];
1136
                        $linkweight = $link->get_weight();
1137
                        $link_res_denom = $linkres[1] == 0 ? 1 : $linkres[1];
1138
1139
                        $count++;
1140
                        $weightsum += $linkweight;
1141
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1142
1143
                        if ($ressum > $bestResult) {
1144
                            $bestResult = $ressum;
1145
                        }
1146
                    } else {
1147
                        // Adding if result does not exists
1148
                        if ($link->get_weight() != 0) {
1149
                            $linkweight = $link->get_weight();
1150
                            $weightsum += $linkweight;
1151
                        }
1152
                    }
1153
                }
1154
            }
1155
        }
1156
1157
        switch ($type) {
1158
            case 'best':
1159
                if (empty($bestResult)) {
1160
                    return null;
1161
                }
1162
1163
                return [$bestResult, $weightsum];
1164
                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...
1165
            case 'average':
1166
                if (empty($ressum)) {
1167
                    return null;
1168
                }
1169
1170
                return [$ressum, $weightsum];
1171
                break;
1172
            case 'ranking':
1173
                // category ranking is calculated in gradebook_data_generator.class.php
1174
                // function get_data
1175
                return null;
1176
1177
                return AbstractLink::getCurrentUserRanking($stud_id, []);
0 ignored issues
show
Unused Code introduced by
return AbstractLink::get...king($stud_id, 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...
1178
                break;
1179
            default:
1180
                return [$ressum, $weightsum];
1181
                break;
1182
        }
1183
    }
1184
1185
    /**
1186
     * Delete this category and every subcategory, evaluation and result inside.
1187
     */
1188
    public function delete_all()
1189
    {
1190
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1191
        $evals = Evaluation::load(
1192
            null,
1193
            null,
1194
            $this->course_code,
1195
            $this->id,
1196
            null
1197
        );
1198
1199
        $links = LinkFactory::load(
1200
            null,
1201
            null,
1202
            null,
1203
            null,
1204
            $this->course_code,
1205
            $this->id,
1206
            null
1207
        );
1208
1209
        if (!empty($cats)) {
1210
            /** @var Category $cat */
1211
            foreach ($cats as $cat) {
1212
                $cat->delete_all();
1213
                $cat->delete();
1214
            }
1215
        }
1216
1217
        if (!empty($evals)) {
1218
            /** @var Evaluation $eval */
1219
            foreach ($evals as $eval) {
1220
                $eval->delete_with_results();
1221
            }
1222
        }
1223
1224
        if (!empty($links)) {
1225
            /** @var AbstractLink $link */
1226
            foreach ($links as $link) {
1227
                $link->delete();
1228
            }
1229
        }
1230
1231
        $this->delete();
1232
    }
1233
1234
    /**
1235
     * Return array of Category objects where a student is subscribed to.
1236
     *
1237
     * @param int    $stud_id
1238
     * @param string $course_code
1239
     * @param int    $session_id
1240
     *
1241
     * @return array
1242
     */
1243
    public function get_root_categories_for_student(
1244
        $stud_id,
1245
        $course_code = null,
1246
        $session_id = null
1247
    ) {
1248
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1249
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1250
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1251
1252
        $course_code = Database::escape_string($course_code);
1253
        $session_id = (int) $session_id;
1254
1255
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1256
1257
        if (!api_is_allowed_to_edit()) {
1258
            $sql .= ' AND visible = 1';
1259
            //proceed with checks on optional parameters course & session
1260
            if (!empty($course_code)) {
1261
                // TODO: considering it highly improbable that a user would get here
1262
                // if he doesn't have the rights to view this course and this
1263
                // session, we don't check his registration to these, but this
1264
                // could be an improvement
1265
                if (!empty($session_id)) {
1266
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1267
                } else {
1268
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1269
                }
1270
            } else {
1271
                //no optional parameter, proceed as usual
1272
                $sql .= ' AND course_code in
1273
                     (
1274
                        SELECT c.code
1275
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1276
                        ON (cu.c_id = c.id)
1277
                        WHERE cu.user_id = '.intval($stud_id).'
1278
                        AND cu.status = '.STUDENT.'
1279
                    )';
1280
            }
1281
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1282
            //proceed with checks on optional parameters course & session
1283
            if (!empty($course_code)) {
1284
                // TODO: considering it highly improbable that a user would get here
1285
                // if he doesn't have the rights to view this course and this
1286
                // session, we don't check his registration to these, but this
1287
                // could be an improvement
1288
                $sql .= " AND course_code  = '".$course_code."'";
1289
                if (!empty($session_id)) {
1290
                    $sql .= " AND session_id = ".$session_id;
1291
                } else {
1292
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1293
                }
1294
            } else {
1295
                $sql .= ' AND course_code IN
1296
                     (
1297
                        SELECT c.code
1298
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1299
                        ON (cu.c_id = c.id)
1300
                        WHERE
1301
                            cu.user_id = '.api_get_user_id().' AND
1302
                            cu.status = '.COURSEMANAGER.'
1303
                    )';
1304
            }
1305
        } elseif (api_is_platform_admin()) {
1306
            if (isset($session_id) && $session_id != 0) {
1307
                $sql .= ' AND session_id='.$session_id;
1308
            } else {
1309
                $sql .= ' AND coalesce(session_id,0)=0';
1310
            }
1311
        }
1312
        $result = Database::query($sql);
1313
        $cats = self::create_category_objects_from_sql_result($result);
1314
1315
        // course independent categories
1316
        if (empty($course_code)) {
1317
            $cats = $this->getIndependentCategoriesWithStudentResult(
1318
                0,
1319
                $stud_id,
1320
                $cats
1321
            );
1322
        }
1323
1324
        return $cats;
1325
    }
1326
1327
    /**
1328
     * Return array of Category objects where a teacher is admin for.
1329
     *
1330
     * @param int    $user_id     (to return everything, use 'null' here)
1331
     * @param string $course_code (optional)
1332
     * @param int    $session_id  (optional)
1333
     *
1334
     * @return array
1335
     */
1336
    public function get_root_categories_for_teacher(
1337
        $user_id,
1338
        $course_code = null,
1339
        $session_id = null
1340
    ) {
1341
        if ($user_id == null) {
1342
            return self::load(null, null, $course_code, 0, null, $session_id);
1343
        }
1344
1345
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1346
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1347
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1348
1349
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1350
                WHERE parent_id = 0 ';
1351
        if (!empty($course_code)) {
1352
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1353
            if (!empty($session_id)) {
1354
                $sql .= " AND session_id = ".(int) $session_id;
1355
            }
1356
        } else {
1357
            $sql .= ' AND course_code in
1358
                 (
1359
                    SELECT c.code
1360
                    FROM '.$main_course_user_table.' cu
1361
                    INNER JOIN '.$courseTable.' c
1362
                    ON (cu.c_id = c.id)
1363
                    WHERE user_id = '.intval($user_id).'
1364
                )';
1365
        }
1366
        $result = Database::query($sql);
1367
        $cats = self::create_category_objects_from_sql_result($result);
1368
        // course independent categories
1369
        if (isset($course_code)) {
1370
            $indcats = self::load(
1371
                null,
1372
                $user_id,
1373
                $course_code,
1374
                0,
1375
                null,
1376
                $session_id
1377
            );
1378
            $cats = array_merge($cats, $indcats);
1379
        }
1380
1381
        return $cats;
1382
    }
1383
1384
    /**
1385
     * Can this category be moved to somewhere else ?
1386
     * The root and courses cannot be moved.
1387
     *
1388
     * @return bool
1389
     */
1390
    public function is_movable()
1391
    {
1392
        return !(!isset($this->id) || $this->id == 0 || $this->is_course());
1393
    }
1394
1395
    /**
1396
     * Generate an array of possible categories where this category can be moved to.
1397
     * Notice: its own parent will be included in the list: it's up to the frontend
1398
     * to disable this element.
1399
     *
1400
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1401
     */
1402
    public function get_target_categories()
1403
    {
1404
        // the root or a course -> not movable
1405
        if (!$this->is_movable()) {
1406
            return null;
1407
        } else {
1408
            // otherwise:
1409
            // - course independent category
1410
            //   -> movable to root or other independent categories
1411
            // - category inside a course
1412
            //   -> movable to root, independent categories or categories inside the course
1413
1414
            $user = api_is_platform_admin() ? null : api_get_user_id();
1415
            $targets = [];
1416
            $level = 0;
1417
1418
            $root = [0, get_lang('RootCat'), $level];
1419
            $targets[] = $root;
1420
1421
            if (isset($this->course_code) && !empty($this->course_code)) {
1422
                $crscats = self::load(null, null, $this->course_code, 0);
1423
                foreach ($crscats as $cat) {
1424
                    if ($this->can_be_moved_to_cat($cat)) {
1425
                        $targets[] = [
1426
                            $cat->get_id(),
1427
                            $cat->get_name(),
1428
                            $level + 1,
1429
                        ];
1430
                        $targets = $this->addTargetSubcategories(
1431
                            $targets,
1432
                            $level + 1,
1433
                            $cat->get_id()
1434
                        );
1435
                    }
1436
                }
1437
            }
1438
1439
            $indcats = self::load(null, $user, 0, 0);
1440
            foreach ($indcats as $cat) {
1441
                if ($this->can_be_moved_to_cat($cat)) {
1442
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1443
                    $targets = $this->addTargetSubcategories(
1444
                        $targets,
1445
                        $level + 1,
1446
                        $cat->get_id()
1447
                    );
1448
                }
1449
            }
1450
1451
            return $targets;
1452
        }
1453
    }
1454
1455
    /**
1456
     * Move this category to the given category.
1457
     * If this category moves from inside a course to outside,
1458
     * its course code must be changed, as well as the course code
1459
     * of all underlying categories and evaluations. All links will
1460
     * be deleted as well !
1461
     */
1462
    public function move_to_cat($cat)
1463
    {
1464
        $this->set_parent_id($cat->get_id());
1465
        if ($this->get_course_code() != $cat->get_course_code()) {
1466
            $this->set_course_code($cat->get_course_code());
1467
            $this->applyCourseCodeToChildren();
1468
        }
1469
        $this->save();
1470
    }
1471
1472
    /**
1473
     * Generate an array of all categories the user can navigate to.
1474
     */
1475
    public function get_tree()
1476
    {
1477
        $targets = [];
1478
        $level = 0;
1479
        $root = [0, get_lang('RootCat'), $level];
1480
        $targets[] = $root;
1481
1482
        // course or platform admin
1483
        if (api_is_allowed_to_edit()) {
1484
            $user = api_is_platform_admin() ? null : api_get_user_id();
1485
            $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

1485
            /** @scrutinizer ignore-call */ 
1486
            $cats = self::get_root_categories_for_teacher($user);
Loading history...
1486
            foreach ($cats as $cat) {
1487
                $targets[] = [
1488
                    $cat->get_id(),
1489
                    $cat->get_name(),
1490
                    $level + 1,
1491
                ];
1492
                $targets = $this->add_subtree(
1493
                    $targets,
1494
                    $level + 1,
1495
                    $cat->get_id(),
1496
                    null
1497
                );
1498
            }
1499
        } else {
1500
            // student
1501
            $cats = $this->get_root_categories_for_student(api_get_user_id());
1502
            foreach ($cats as $cat) {
1503
                $targets[] = [
1504
                    $cat->get_id(),
1505
                    $cat->get_name(),
1506
                    $level + 1,
1507
                ];
1508
                $targets = $this->add_subtree(
1509
                    $targets,
1510
                    $level + 1,
1511
                    $cat->get_id(),
1512
                    1
1513
                );
1514
            }
1515
        }
1516
1517
        return $targets;
1518
    }
1519
1520
    /**
1521
     * Generate an array of courses that a teacher hasn't created a category for.
1522
     *
1523
     * @param int $user_id
1524
     *
1525
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1526
     */
1527
    public function get_not_created_course_categories($user_id)
1528
    {
1529
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1530
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1531
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1532
1533
        $sql = 'SELECT DISTINCT(code), title
1534
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1535
                WHERE 
1536
                    cc.id = cu.c_id AND 
1537
                    cu.status = '.COURSEMANAGER;
1538
1539
        if (!api_is_platform_admin()) {
1540
            $sql .= ' AND cu.user_id = '.$user_id;
1541
        }
1542
        $sql .= ' AND cc.code NOT IN
1543
             (
1544
                SELECT course_code FROM '.$tbl_grade_categories.'
1545
                WHERE
1546
                    parent_id = 0 AND
1547
                    course_code IS NOT NULL
1548
                )';
1549
        $result = Database::query($sql);
1550
1551
        $cats = [];
1552
        while ($data = Database::fetch_array($result)) {
1553
            $cats[] = [$data['code'], $data['title']];
1554
        }
1555
1556
        return $cats;
1557
    }
1558
1559
    /**
1560
     * Generate an array of all courses that a teacher is admin of.
1561
     *
1562
     * @param int $user_id
1563
     *
1564
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1565
     */
1566
    public function get_all_courses($user_id)
1567
    {
1568
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1569
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1570
        $sql = 'SELECT DISTINCT(code), title
1571
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1572
                WHERE cc.id = cu.c_id AND cu.status = '.COURSEMANAGER;
1573
        if (!api_is_platform_admin()) {
1574
            $sql .= ' AND cu.user_id = '.intval($user_id);
1575
        }
1576
1577
        $result = Database::query($sql);
1578
        $cats = [];
1579
        while ($data = Database::fetch_array($result)) {
1580
            $cats[] = [$data['code'], $data['title']];
1581
        }
1582
1583
        return $cats;
1584
    }
1585
1586
    /**
1587
     * Apply the same visibility to every subcategory, evaluation and link.
1588
     */
1589
    public function apply_visibility_to_children()
1590
    {
1591
        $cats = self::load(null, null, null, $this->id, null);
1592
        $evals = Evaluation::load(null, null, null, $this->id, null);
1593
        $links = LinkFactory::load(
1594
            null,
1595
            null,
1596
            null,
1597
            null,
1598
            null,
1599
            $this->id,
1600
            null
1601
        );
1602
        if (!empty($cats)) {
1603
            foreach ($cats as $cat) {
1604
                $cat->set_visible($this->is_visible());
1605
                $cat->save();
1606
                $cat->apply_visibility_to_children();
1607
            }
1608
        }
1609
        if (!empty($evals)) {
1610
            foreach ($evals as $eval) {
1611
                $eval->set_visible($this->is_visible());
1612
                $eval->save();
1613
            }
1614
        }
1615
        if (!empty($links)) {
1616
            foreach ($links as $link) {
1617
                $link->set_visible($this->is_visible());
1618
                $link->save();
1619
            }
1620
        }
1621
    }
1622
1623
    /**
1624
     * Check if a category contains evaluations with a result for a given student.
1625
     *
1626
     * @param int $studentId
1627
     *
1628
     * @return bool
1629
     */
1630
    public function hasEvaluationsWithStudentResults($studentId)
1631
    {
1632
        $evals = Evaluation::get_evaluations_with_result_for_student(
1633
            $this->id,
1634
            $studentId
1635
        );
1636
        if (count($evals) != 0) {
1637
            return true;
1638
        } else {
1639
            $cats = self::load(
1640
                null,
1641
                null,
1642
                null,
1643
                $this->id,
1644
                api_is_allowed_to_edit() ? null : 1
0 ignored issues
show
Bug introduced by
It seems like api_is_allowed_to_edit() ? null : 1 can also be of type integer; however, parameter $visible of Category::load() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

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

1644
                /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1
Loading history...
1645
            );
1646
1647
            /** @var Category $cat */
1648
            foreach ($cats as $cat) {
1649
                if ($cat->hasEvaluationsWithStudentResults($studentId)) {
1650
                    return true;
1651
                }
1652
            }
1653
1654
            return false;
1655
        }
1656
    }
1657
1658
    /**
1659
     * Retrieve all categories inside a course independent category
1660
     * that should be visible to a student.
1661
     *
1662
     * @param int   $categoryId parent category
1663
     * @param int   $studentId
1664
     * @param array $cats       optional: if defined, the categories will be added to this array
1665
     *
1666
     * @return array
1667
     */
1668
    public function getIndependentCategoriesWithStudentResult(
1669
        $categoryId,
1670
        $studentId,
1671
        $cats = []
1672
    ) {
1673
        $creator = api_is_allowed_to_edit() && !api_is_platform_admin() ? api_get_user_id() : null;
1674
1675
        $categories = self::load(
1676
            null,
1677
            $creator,
1678
            '0',
1679
            $categoryId,
1680
            api_is_allowed_to_edit() ? null : 1
0 ignored issues
show
Bug introduced by
It seems like api_is_allowed_to_edit() ? null : 1 can also be of type integer; however, parameter $visible of Category::load() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

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

1680
            /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1
Loading history...
1681
        );
1682
1683
        if (!empty($categories)) {
1684
            /** @var Category $category */
1685
            foreach ($categories as $category) {
1686
                if ($category->hasEvaluationsWithStudentResults($studentId)) {
1687
                    $cats[] = $category;
1688
                }
1689
            }
1690
        }
1691
1692
        return $cats;
1693
    }
1694
1695
    /**
1696
     * Return the session id (in any case, even if it's null or 0).
1697
     *
1698
     * @return int Session id (can be null)
1699
     */
1700
    public function get_session_id()
1701
    {
1702
        return $this->session_id;
1703
    }
1704
1705
    /**
1706
     * Get appropriate subcategories visible for the user (and optionally the course and session).
1707
     *
1708
     * @param int    $studentId   student id (default: all students)
1709
     * @param string $course_code Course code (optional)
1710
     * @param int    $session_id  Session ID (optional)
1711
     * @param bool   $order
1712
     *
1713
     * @return array Array of subcategories
1714
     */
1715
    public function get_subcategories(
1716
        $studentId = null,
1717
        $course_code = null,
1718
        $session_id = null,
1719
        $order = null
1720
    ) {
1721
        // 1 student
1722
        if (isset($studentId)) {
1723
            // Special case: this is the root
1724
            if ($this->id == 0) {
1725
                return $this->get_root_categories_for_student($studentId, $course_code, $session_id);
1726
            } else {
1727
                return self::load(
1728
                    null,
1729
                    null,
1730
                    $course_code,
1731
                    $this->id,
1732
                    api_is_allowed_to_edit() ? null : 1,
0 ignored issues
show
Bug introduced by
It seems like api_is_allowed_to_edit() ? null : 1 can also be of type integer; however, parameter $visible of Category::load() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

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

1732
                    /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1,
Loading history...
1733
                    $session_id,
1734
                    $order
1735
                );
1736
            }
1737
        } else {
1738
            // All students
1739
            // Course admin
1740
            if (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1741
                // root
1742
                if ($this->id == 0) {
1743
                    // inside a course
1744
                    return $this->get_root_categories_for_teacher(
1745
                        api_get_user_id(),
1746
                        $course_code,
1747
                        $session_id,
1748
                        false
1749
                    );
1750
                } elseif (!empty($this->course_code)) {
1751
                    return self::load(
1752
                        null,
1753
                        null,
1754
                        $this->course_code,
1755
                        $this->id,
1756
                        null,
1757
                        $session_id,
1758
                        $order
1759
                    );
1760
                } elseif (!empty($course_code)) {
1761
                    // course independent
1762
                    return self::load(
1763
                        null,
1764
                        null,
1765
                        $course_code,
1766
                        $this->id,
1767
                        null,
1768
                        $session_id,
1769
                        $order
1770
                    );
1771
                } else {
1772
                    return self::load(
1773
                        null,
1774
                        api_get_user_id(),
1775
                        0,
1776
                        $this->id,
1777
                        null
1778
                    );
1779
                }
1780
            } elseif (api_is_platform_admin()) {
1781
                // platform admin
1782
                // we explicitly avoid listing subcats from another session
1783
                return self::load(
1784
                    null,
1785
                    null,
1786
                    $course_code,
1787
                    $this->id,
1788
                    null,
1789
                    $session_id,
1790
                    $order
1791
                );
1792
            }
1793
        }
1794
1795
        return [];
1796
    }
1797
1798
    /**
1799
     * Get appropriate evaluations visible for the user.
1800
     *
1801
     * @param int    $studentId   student id (default: all students)
1802
     * @param bool   $recursive   process subcategories (default: no recursion)
1803
     * @param string $course_code
1804
     * @param int    $sessionId
1805
     *
1806
     * @return array
1807
     */
1808
    public function get_evaluations(
1809
        $studentId = null,
1810
        $recursive = false,
1811
        $course_code = '',
1812
        $sessionId = 0
1813
    ) {
1814
        $evals = [];
1815
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1816
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1817
1818
        // 1 student
1819
        if (isset($studentId) && !empty($studentId)) {
1820
            // Special case: this is the root
1821
            if ($this->id == 0) {
1822
                $evals = Evaluation::get_evaluations_with_result_for_student(
1823
                    0,
1824
                    $studentId
1825
                );
1826
            } else {
1827
                $evals = Evaluation::load(
1828
                    null,
1829
                    null,
1830
                    $course_code,
1831
                    $this->id,
1832
                    api_is_allowed_to_edit() ? null : 1
1833
                );
1834
            }
1835
        } else {
1836
            // All students
1837
            // course admin
1838
            if ((api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) &&
1839
                !api_is_platform_admin()
1840
            ) {
1841
                // root
1842
                if ($this->id == 0) {
1843
                    $evals = Evaluation::load(
1844
                        null,
1845
                        api_get_user_id(),
1846
                        null,
1847
                        $this->id,
1848
                        null
1849
                    );
1850
                } elseif (isset($this->course_code) &&
1851
                    !empty($this->course_code)
1852
                ) {
1853
                    // inside a course
1854
                    $evals = Evaluation::load(
1855
                        null,
1856
                        null,
1857
                        $course_code,
1858
                        $this->id,
1859
                        null
1860
                    );
1861
                } else {
1862
                    // course independent
1863
                    $evals = Evaluation::load(
1864
                        null,
1865
                        api_get_user_id(),
1866
                        null,
1867
                        $this->id,
1868
                        null
1869
                    );
1870
                }
1871
            } else {
1872
                $evals = Evaluation::load(
1873
                    null,
1874
                    null,
1875
                    $course_code,
1876
                    $this->id,
1877
                    null
1878
                );
1879
            }
1880
        }
1881
1882
        if ($recursive) {
1883
            $subcats = $this->get_subcategories(
1884
                $studentId,
1885
                $course_code,
1886
                $sessionId
1887
            );
1888
1889
            if (!empty($subcats)) {
1890
                foreach ($subcats as $subcat) {
1891
                    $subevals = $subcat->get_evaluations(
1892
                        $studentId,
1893
                        true,
1894
                        $course_code
1895
                    );
1896
                    $evals = array_merge($evals, $subevals);
1897
                }
1898
            }
1899
        }
1900
1901
        return $evals;
1902
    }
1903
1904
    /**
1905
     * Get appropriate links visible for the user.
1906
     *
1907
     * @param int    $studentId   student id (default: all students)
1908
     * @param bool   $recursive   process subcategories (default: no recursion)
1909
     * @param string $course_code
1910
     * @param int    $sessionId
1911
     *
1912
     * @return array
1913
     */
1914
    public function get_links(
1915
        $studentId = null,
1916
        $recursive = false,
1917
        $course_code = '',
1918
        $sessionId = 0
1919
    ) {
1920
        $links = [];
1921
1922
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1923
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1924
1925
        // no links in root or course independent categories
1926
        if ($this->id == 0) {
1927
        } elseif (isset($studentId)) {
1928
            // 1 student $studentId
1929
            $links = LinkFactory::load(
1930
                null,
1931
                null,
1932
                null,
1933
                null,
1934
                empty($this->course_code) ? null : $course_code,
1935
                $this->id,
1936
                api_is_allowed_to_edit() ? null : 1
1937
            );
1938
        } else {
1939
            // All students -> only for course/platform admin
1940
            $links = LinkFactory::load(
1941
                null,
1942
                null,
1943
                null,
1944
                null,
1945
                empty($this->course_code) ? null : $this->course_code,
1946
                $this->id,
1947
                null
1948
            );
1949
        }
1950
1951
        if ($recursive) {
1952
            $subcats = $this->get_subcategories(
1953
                $studentId,
1954
                $course_code,
1955
                $sessionId
1956
            );
1957
            if (!empty($subcats)) {
1958
                /** @var Category $subcat */
1959
                foreach ($subcats as $subcat) {
1960
                    $sublinks = $subcat->get_links(
1961
                        $studentId,
1962
                        false,
1963
                        $course_code,
1964
                        $sessionId
1965
                    );
1966
                    $links = array_merge($links, $sublinks);
1967
                }
1968
            }
1969
        }
1970
1971
        return $links;
1972
    }
1973
1974
    /**
1975
     * Get all the categories from with the same given direct parent.
1976
     *
1977
     * @param int $catId Category parent ID
1978
     *
1979
     * @return array Array of Category objects
1980
     */
1981
    public function getCategories($catId)
1982
    {
1983
        $tblGradeCategories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1984
        $sql = 'SELECT * FROM '.$tblGradeCategories.'
1985
                WHERE parent_id = '.intval($catId);
1986
1987
        $result = Database::query($sql);
1988
        $categories = self::create_category_objects_from_sql_result($result);
1989
1990
        return $categories;
1991
    }
1992
1993
    /**
1994
     * Gets the type for the current object.
1995
     *
1996
     * @return string 'C' to represent "Category" object type
1997
     */
1998
    public function get_item_type()
1999
    {
2000
        return 'C';
2001
    }
2002
2003
    /**
2004
     * @param array $skills
2005
     */
2006
    public function set_skills($skills)
2007
    {
2008
        $this->skills = $skills;
2009
    }
2010
2011
    public function get_date()
2012
    {
2013
        return null;
2014
    }
2015
2016
    /**
2017
     * @return string
2018
     */
2019
    public function get_icon_name()
2020
    {
2021
        return 'cat';
2022
    }
2023
2024
    /**
2025
     * Find category by name.
2026
     *
2027
     * @param string $name_mask search string
2028
     *
2029
     * @return array category objects matching the search criterium
2030
     */
2031
    public function find_category($name_mask, $allcat)
2032
    {
2033
        $categories = [];
2034
        foreach ($allcat as $search_cat) {
2035
            if (!(strpos(strtolower($search_cat->get_name()), strtolower($name_mask)) === false)) {
2036
                $categories[] = $search_cat;
2037
            }
2038
        }
2039
2040
        return $categories;
2041
    }
2042
2043
    /**
2044
     * This function, locks a category , only one who can unlock it is
2045
     * the platform administrator.
2046
     *
2047
     * @param int locked 1 or unlocked 0
2048
2049
     *
2050
     * @return bool|null
2051
     * */
2052
    public function lock($locked)
2053
    {
2054
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
2055
        $sql = "UPDATE $table SET locked = '".intval($locked)."'
2056
                WHERE id='".intval($this->id)."'";
2057
        Database::query($sql);
2058
    }
2059
2060
    /**
2061
     * @param $locked
2062
     */
2063
    public function lockAllItems($locked)
2064
    {
2065
        if (api_get_setting('gradebook_locking_enabled') == 'true') {
2066
            $this->lock($locked);
2067
            $evals_to_lock = $this->get_evaluations();
2068
            if (!empty($evals_to_lock)) {
2069
                foreach ($evals_to_lock as $item) {
2070
                    $item->lock($locked);
2071
                }
2072
            }
2073
2074
            $link_to_lock = $this->get_links();
2075
            if (!empty($link_to_lock)) {
2076
                foreach ($link_to_lock as $item) {
2077
                    $item->lock($locked);
2078
                }
2079
            }
2080
2081
            $event_type = LOG_GRADEBOOK_UNLOCKED;
2082
            if ($locked == 1) {
2083
                $event_type = LOG_GRADEBOOK_LOCKED;
2084
            }
2085
            Event::addEvent($event_type, LOG_GRADEBOOK_ID, $this->id);
2086
        }
2087
    }
2088
2089
    /**
2090
     * Generates a certificate for this user if everything matches.
2091
     *
2092
     * @param int  $category_id      gradebook id
2093
     * @param int  $user_id
2094
     * @param bool $sendNotification
2095
     *
2096
     * @return array
2097
     */
2098
    public static function generateUserCertificate(
2099
        $category_id,
2100
        $user_id,
2101
        $sendNotification = false
2102
    ) {
2103
        // Generating the total score for a course
2104
        $cats_course = self::load(
2105
            $category_id,
2106
            null,
2107
            null,
2108
            null,
2109
            null,
2110
            null,
2111
            false
2112
        );
2113
2114
        /** @var Category $category */
2115
        $category = $cats_course[0];
2116
2117
        if (empty($category)) {
2118
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
2119
        }
2120
2121
        // Block certification links depending gradebook configuration (generate certifications)
2122
        if (empty($category->getGenerateCertificates())) {
2123
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
2124
        }
2125
2126
        $sessionId = $category->get_session_id();
2127
        $courseCode = $category->get_course_code();
2128
        $courseInfo = api_get_course_info($courseCode);
2129
        $courseId = $courseInfo['real_id'];
2130
2131
        //@todo move these in a function
2132
        $sum_categories_weight_array = [];
2133
        if (isset($cats_course) && !empty($cats_course)) {
2134
            $categories = self::load(null, null, null, $category_id);
2135
            if (!empty($categories)) {
2136
                foreach ($categories as $subCategory) {
2137
                    $sum_categories_weight_array[$subCategory->get_id()] = $subCategory->get_weight();
2138
                }
2139
            } else {
2140
                $sum_categories_weight_array[$category_id] = $cats_course[0]->get_weight();
2141
            }
2142
        }
2143
2144
        $cattotal = self::load($category_id);
2145
        $scoretotal = $cattotal[0]->calc_score($user_id);
2146
2147
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2148
        // file load this variable as a global
2149
        $scoredisplay = ScoreDisplay::instance();
2150
        $my_score_in_gradebook = $scoredisplay->display_score(
2151
            $scoretotal,
2152
            SCORE_SIMPLE
2153
        );
2154
        $userFinishedCourse = self::userFinishedCourse(
2155
            $user_id,
2156
            $cats_course[0],
2157
            true
2158
        );
2159
2160
        if (!$userFinishedCourse) {
2161
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
2162
        }
2163
2164
        $skillToolEnabled = Skill::hasAccessToUserSkill(
2165
            api_get_user_id(),
2166
            $user_id
2167
        );
2168
        $userHasSkills = false;
2169
2170
        if ($skillToolEnabled) {
2171
            $skill = new Skill();
2172
            $skill->addSkillToUser(
2173
                $user_id,
2174
                $category_id,
2175
                $courseId,
2176
                $sessionId
2177
            );
2178
2179
            $objSkillRelUser = new SkillRelUser();
2180
            $userSkills = $objSkillRelUser->getUserSkills(
2181
                $user_id,
2182
                $courseId,
2183
                $sessionId
2184
            );
2185
            $userHasSkills = !empty($userSkills);
2186
2187
            if (!$category->getGenerateCertificates() && $userHasSkills) {
2188
                return [
2189
                    'badge_link' => Display::toolbarButton(
2190
                        get_lang('ExportBadges'),
2191
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2192
                        'external-link'
2193
                    ),
2194
                ];
2195
            }
2196
        }
2197
2198
        $my_certificate = GradebookUtils::get_certificate_by_user_id(
2199
            $cats_course[0]->get_id(),
2200
            $user_id
2201
        );
2202
2203
        if (empty($my_certificate)) {
2204
            GradebookUtils::registerUserInfoAboutCertificate(
2205
                $category_id,
2206
                $user_id,
2207
                $my_score_in_gradebook,
2208
                api_get_utc_datetime()
2209
            );
2210
            $my_certificate = GradebookUtils::get_certificate_by_user_id(
2211
                $cats_course[0]->get_id(),
2212
                $user_id
2213
            );
2214
        }
2215
2216
        $html = [];
2217
        if (!empty($my_certificate)) {
2218
            $certificate_obj = new Certificate(
2219
                $my_certificate['id'],
2220
                0,
2221
                $sendNotification
2222
            );
2223
2224
            $fileWasGenerated = $certificate_obj->isHtmlFileGenerated();
2225
2226
            if (!empty($fileWasGenerated)) {
2227
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'];
2228
                $certificates = Display::toolbarButton(
2229
                    get_lang('DisplayCertificate'),
2230
                    $url,
2231
                    'eye',
2232
                    'primary'
2233
                );
2234
2235
                $exportToPDF = Display::url(
2236
                    Display::return_icon(
2237
                        'pdf.png',
2238
                        get_lang('ExportToPDF'),
2239
                        [],
2240
                        ICON_SIZE_MEDIUM
2241
                    ),
2242
                    "$url&action=export"
2243
                );
2244
2245
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2246
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2247
                if ($hideExportLink === 'true' || (api_is_student() && $hideExportLinkStudent === 'true')) {
2248
                    $exportToPDF = null;
2249
                }
2250
2251
                $html = [
2252
                    'certificate_link' => $certificates,
2253
                    'pdf_link' => $exportToPDF,
2254
                    'pdf_url' => "$url&action=export",
2255
                ];
2256
2257
                if ($skillToolEnabled && $userHasSkills) {
2258
                    $html['badge_link'] = Display::toolbarButton(
2259
                        get_lang('ExportBadges'),
2260
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2261
                        'external-link'
2262
                    );
2263
                }
2264
            }
2265
2266
            return $html;
2267
        }
2268
    }
2269
2270
    /**
2271
     * @param int   $catId
2272
     * @param array $userList
2273
     */
2274
    public static function generateCertificatesInUserList($catId, $userList)
2275
    {
2276
        if (!empty($userList)) {
2277
            foreach ($userList as $userInfo) {
2278
                self::generateUserCertificate($catId, $userInfo['user_id']);
2279
            }
2280
        }
2281
    }
2282
2283
    /**
2284
     * @param int   $catId
2285
     * @param array $userList
2286
     */
2287
    public static function exportAllCertificates(
2288
        $catId,
2289
        $userList = []
2290
    ) {
2291
        $orientation = api_get_configuration_value('certificate_pdf_orientation');
2292
2293
        $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...
2294
        if (!empty($orientation)) {
2295
            $params['orientation'] = $orientation;
2296
        }
2297
2298
        $params['left'] = 0;
2299
        $params['right'] = 0;
2300
        $params['top'] = 0;
2301
        $params['bottom'] = 0;
2302
        $page_format = $params['orientation'] == 'landscape' ? 'A4-L' : 'A4';
2303
        $pdf = new PDF($page_format, $params['orientation'], $params);
2304
2305
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2306
        $certificate_path_list = [];
2307
2308
        if (!empty($certificate_list)) {
2309
            foreach ($certificate_list as $index => $value) {
2310
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2311
                    $value['user_id'],
2312
                    $catId
2313
                );
2314
                foreach ($list_certificate as $value_certificate) {
2315
                    $certificate_obj = new Certificate($value_certificate['id']);
2316
                    $certificate_obj->generate(['hide_print_button' => true]);
2317
                    if ($certificate_obj->isHtmlFileGenerated()) {
2318
                        $certificate_path_list[] = $certificate_obj->html_file;
2319
                    }
2320
                }
2321
            }
2322
        }
2323
2324
        if (!empty($certificate_path_list)) {
2325
            // Print certificates (without the common header/footer/watermark
2326
            //  stuff) and return as one multiple-pages PDF
2327
            $pdf->html_to_pdf(
2328
                $certificate_path_list,
2329
                get_lang('Certificates'),
2330
                null,
2331
                false,
2332
                false
2333
            );
2334
        }
2335
    }
2336
2337
    /**
2338
     * @param int $catId
2339
     */
2340
    public static function deleteAllCertificates($catId)
2341
    {
2342
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2343
        if (!empty($certificate_list)) {
2344
            foreach ($certificate_list as $index => $value) {
2345
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2346
                    $value['user_id'],
2347
                    $catId
2348
                );
2349
                foreach ($list_certificate as $value_certificate) {
2350
                    $certificate_obj = new Certificate($value_certificate['id']);
2351
                    $certificate_obj->delete(true);
2352
                }
2353
            }
2354
        }
2355
    }
2356
2357
    /**
2358
     * Check whether a user has finished a course by its gradebook.
2359
     *
2360
     * @param int       $userId           The user ID
2361
     * @param \Category $category         Optional. The gradebook category.
2362
     *                                    To check by the gradebook category
2363
     * @param bool      $recalculateScore Whether recalculate the score
2364
     *
2365
     * @return bool
2366
     */
2367
    public static function userFinishedCourse(
2368
        $userId,
2369
        \Category $category,
2370
        $recalculateScore = false
2371
    ) {
2372
        if (empty($category)) {
2373
            return false;
2374
        }
2375
2376
        $currentScore = self::getCurrentScore(
2377
            $userId,
2378
            $category,
2379
            $recalculateScore
2380
        );
2381
2382
        $minCertificateScore = $category->getCertificateMinScore();
2383
        $passedCourse = $currentScore >= $minCertificateScore;
2384
2385
        return $passedCourse;
2386
    }
2387
2388
    /**
2389
     * Get the current score (as percentage) on a gradebook category for a user.
2390
     *
2391
     * @param int      $userId      The user id
2392
     * @param Category $category    The gradebook category
2393
     * @param bool     $recalculate
2394
     *
2395
     * @return float The score
2396
     */
2397
    public static function getCurrentScore(
2398
        $userId,
2399
        $category,
2400
        $recalculate = false
2401
    ) {
2402
        if (empty($category)) {
2403
            return 0;
2404
        }
2405
2406
        if ($recalculate) {
2407
            return self::calculateCurrentScore(
2408
                $userId,
2409
                $category
2410
            );
2411
        }
2412
2413
        $resultData = Database::select(
2414
            '*',
2415
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2416
            [
2417
                'where' => [
2418
                    'category_id = ? AND user_id = ?' => [$category->get_id(), $userId],
2419
                ],
2420
                'order' => 'registered_at DESC',
2421
                'limit' => '1',
2422
            ],
2423
            'first'
2424
        );
2425
2426
        if (empty($resultData)) {
2427
            return 0;
2428
        }
2429
2430
        return $resultData['score'];
2431
    }
2432
2433
    /**
2434
     * Register the current score for a user on a category gradebook.
2435
     *
2436
     * @param float $score      The achieved score
2437
     * @param int   $userId     The user id
2438
     * @param int   $categoryId The gradebook category
2439
     *
2440
     * @return int The insert id
2441
     */
2442
    public static function registerCurrentScore($score, $userId, $categoryId)
2443
    {
2444
        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...
2445
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2446
            [
2447
                'category_id' => intval($categoryId),
2448
                'user_id' => intval($userId),
2449
                'score' => api_float_val($score),
2450
                'registered_at' => api_get_utc_datetime(),
2451
            ]
2452
        );
2453
    }
2454
2455
    /**
2456
     * @return array
2457
     */
2458
    public function getStudentList()
2459
    {
2460
        return $this->studentList;
2461
    }
2462
2463
    /**
2464
     * @param array $list
2465
     */
2466
    public function setStudentList($list)
2467
    {
2468
        $this->studentList = $list;
2469
    }
2470
2471
    /**
2472
     * @return string
2473
     */
2474
    public static function getUrl()
2475
    {
2476
        $url = Session::read('gradebook_dest');
2477
        if (empty($url)) {
2478
            // We guess the link
2479
            $courseInfo = api_get_course_info();
2480
            if (!empty($courseInfo)) {
2481
                return api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2482
            } else {
2483
                return api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2484
            }
2485
        }
2486
2487
        return $url;
2488
    }
2489
2490
    /**
2491
     * Destination is index.php or gradebook.php.
2492
     *
2493
     * @param string $url
2494
     */
2495
    public static function setUrl($url)
2496
    {
2497
        switch ($url) {
2498
            case 'gradebook.php':
2499
                $url = api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2500
                break;
2501
            case 'index.php':
2502
                $url = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2503
                break;
2504
        }
2505
        Session::write('gradebook_dest', $url);
2506
    }
2507
2508
    /**
2509
     * @return int
2510
     */
2511
    public function getCourseId()
2512
    {
2513
        return $this->courseId;
2514
    }
2515
2516
    /**
2517
     * @param int $courseId
2518
     *
2519
     * @return Category
2520
     */
2521
    public function setCourseId($courseId)
2522
    {
2523
        $this->courseId = $courseId;
2524
2525
        return $this;
2526
    }
2527
2528
    /**
2529
     * @return int
2530
     */
2531
    public function getGradeBooksToValidateInDependence()
2532
    {
2533
        return $this->gradeBooksToValidateInDependence;
2534
    }
2535
2536
    /**
2537
     * @param int $value
2538
     *
2539
     * @return Category
2540
     */
2541
    public function setGradeBooksToValidateInDependence($value)
2542
    {
2543
        $this->gradeBooksToValidateInDependence = $value;
2544
2545
        return $this;
2546
    }
2547
2548
    /**
2549
     * @return Category
2550
     */
2551
    private static function create_root_category()
2552
    {
2553
        $cat = new Category();
2554
        $cat->set_id(0);
2555
        $cat->set_name(get_lang('RootCat'));
2556
        $cat->set_description(null);
2557
        $cat->set_user_id(0);
2558
        $cat->set_course_code(null);
2559
        $cat->set_parent_id(null);
2560
        $cat->set_weight(0);
2561
        $cat->set_visible(1);
2562
        $cat->setGenerateCertificates(0);
2563
        $cat->setIsRequirement(false);
2564
2565
        return $cat;
2566
    }
2567
2568
    /**
2569
     * @param Doctrine\DBAL\Driver\Statement|null $result
2570
     *
2571
     * @return array
2572
     */
2573
    private static function create_category_objects_from_sql_result($result)
2574
    {
2575
        $categories = [];
2576
        while ($data = Database::fetch_array($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type null; however, parameter $result of Database::fetch_array() does only seem to accept Doctrine\DBAL\Driver\Statement, maybe add an additional type check? ( Ignorable by Annotation )

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

2576
        while ($data = Database::fetch_array(/** @scrutinizer ignore-type */ $result)) {
Loading history...
2577
            $cat = new Category();
2578
            $cat->set_id($data['id']);
2579
            $cat->set_name($data['name']);
2580
            $cat->set_description($data['description']);
2581
            $cat->set_user_id($data['user_id']);
2582
            $courseInfo = api_get_course_info_by_id($data['c_id']);
2583
            $cat->set_course_code($courseInfo['code']);
2584
            $cat->setCourseId($data['c_id']);
2585
            $cat->set_parent_id($data['parent_id']);
2586
            $cat->set_weight($data['weight']);
2587
            $cat->set_visible($data['visible']);
2588
            $cat->set_session_id($data['session_id']);
2589
            $cat->set_certificate_min_score($data['certif_min_score']);
2590
            $cat->set_grade_model_id($data['grade_model_id']);
2591
            $cat->set_locked($data['locked']);
2592
            $cat->setGenerateCertificates($data['generate_certificates']);
2593
            $cat->setIsRequirement($data['is_requirement']);
2594
            $cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2595
            $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2596
            $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2597
2598
            $categories[] = $cat;
2599
        }
2600
2601
        return $categories;
2602
    }
2603
2604
    /**
2605
     * Internal function used by get_target_categories().
2606
     *
2607
     * @param array $targets
2608
     * @param int   $level
2609
     * @param int   $catid
2610
     *
2611
     * @return array
2612
     */
2613
    private function addTargetSubcategories($targets, $level, $catid)
2614
    {
2615
        $subcats = self::load(null, null, null, $catid);
2616
        foreach ($subcats as $cat) {
2617
            if ($this->can_be_moved_to_cat($cat)) {
2618
                $targets[] = [
2619
                    $cat->get_id(),
2620
                    $cat->get_name(),
2621
                    $level + 1,
2622
                ];
2623
                $targets = $this->addTargetSubcategories(
2624
                    $targets,
2625
                    $level + 1,
2626
                    $cat->get_id()
2627
                );
2628
            }
2629
        }
2630
2631
        return $targets;
2632
    }
2633
2634
    /**
2635
     * Internal function used by get_target_categories() and addTargetSubcategories()
2636
     * Can this category be moved to the given category ?
2637
     * Impossible when origin and target are the same... children won't be processed
2638
     * either. (a category can't be moved to one of its own children).
2639
     */
2640
    private function can_be_moved_to_cat($cat)
2641
    {
2642
        return $cat->get_id() != $this->get_id();
2643
    }
2644
2645
    /**
2646
     * Internal function used by move_to_cat().
2647
     */
2648
    private function applyCourseCodeToChildren()
2649
    {
2650
        $cats = self::load(null, null, null, $this->id, null);
2651
        $evals = Evaluation::load(null, null, null, $this->id, null);
2652
        $links = LinkFactory::load(
2653
            null,
2654
            null,
2655
            null,
2656
            null,
2657
            null,
2658
            $this->id,
2659
            null
2660
        );
2661
        /** @var Category $cat */
2662
        foreach ($cats as $cat) {
2663
            $cat->set_course_code($this->get_course_code());
2664
            $cat->save();
2665
            $cat->applyCourseCodeToChildren();
2666
        }
2667
2668
        foreach ($evals as $eval) {
2669
            $eval->set_course_code($this->get_course_code());
2670
            $eval->save();
2671
        }
2672
2673
        foreach ($links as $link) {
2674
            $link->delete();
2675
        }
2676
    }
2677
2678
    /**
2679
     * Internal function used by get_tree().
2680
     *
2681
     * @param int      $level
2682
     * @param null|int $visible
2683
     *
2684
     * @return array
2685
     */
2686
    private function add_subtree($targets, $level, $catid, $visible)
2687
    {
2688
        $subcats = self::load(null, null, null, $catid, $visible);
0 ignored issues
show
Bug introduced by
It seems like $visible can also be of type integer; however, parameter $visible of Category::load() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

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

2688
        $subcats = self::load(null, null, null, $catid, /** @scrutinizer ignore-type */ $visible);
Loading history...
2689
2690
        if (!empty($subcats)) {
2691
            foreach ($subcats as $cat) {
2692
                $targets[] = [
2693
                    $cat->get_id(),
2694
                    $cat->get_name(),
2695
                    $level + 1,
2696
                ];
2697
                $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

2697
                /** @scrutinizer ignore-call */ 
2698
                $targets = self::add_subtree(
Loading history...
2698
                    $targets,
2699
                    $level + 1,
2700
                    $cat->get_id(),
2701
                    $visible
2702
                );
2703
            }
2704
        }
2705
2706
        return $targets;
2707
    }
2708
2709
    /**
2710
     * Calculate the current score on a gradebook category for a user.
2711
     *
2712
     * @param int      $userId   The user id
2713
     * @param Category $category The gradebook category
2714
     *
2715
     * @return float The score
2716
     */
2717
    private static function calculateCurrentScore(
2718
        $userId,
2719
        $category
2720
    ) {
2721
        if (empty($category)) {
2722
            return 0;
2723
        }
2724
        $courseEvaluations = $category->get_evaluations(
2725
            $userId,
2726
            true
2727
        );
2728
        $courseLinks = $category->get_links($userId, true);
2729
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2730
        $categoryScore = 0;
2731
        for ($i = 0; $i < count($evaluationsAndLinks); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
2732
            $item = $evaluationsAndLinks[$i];
2733
            $score = $item->calc_score($userId);
2734
            $itemValue = 0;
2735
            if (!empty($score)) {
2736
                $divider = $score[1] == 0 ? 1 : $score[1];
2737
                $itemValue = $score[0] / $divider * $item->get_weight();
2738
            }
2739
2740
            $categoryScore += $itemValue;
2741
        }
2742
2743
        return api_float_val($categoryScore);
2744
    }
2745
}
2746