Passed
Push — master ( 2f567c...15378b )
by Julito
12:45
created

Category::create_root_category()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
484
                    $sql .= " c_id = '".intval($courseInfo['real_id'])."'";
485
                }
486
            }
487
488
            /*if ($show_session_categories !== true) {
489
                // a query on the course should show all
490
                // the categories inside sessions for this course
491
                // otherwise a special parameter is given to ask explicitely
492
                $sql .= " AND (session_id IS NULL OR session_id = 0) ";
493
            } else {*/
494
            if (empty($session_id)) {
495
                $sql .= ' AND (session_id IS NULL OR session_id = 0) ';
496
            } else {
497
                $sql .= ' AND session_id = '.(int) $session_id.' ';
498
            }
499
            //}
500
            $paramcount++;
501
        }
502
503
        if (isset($parent_id)) {
504
            if ($paramcount != 0) {
505
                $sql .= ' AND ';
506
            } else {
507
                $sql .= ' WHERE ';
508
            }
509
            $sql .= ' parent_id = '.intval($parent_id);
510
            $paramcount++;
511
        }
512
513
        if (isset($visible)) {
514
            if ($paramcount != 0) {
515
                $sql .= ' AND';
516
            } else {
517
                $sql .= ' WHERE';
518
            }
519
            $sql .= ' visible = '.intval($visible);
520
        }
521
522
        if (!empty($order_by)) {
523
            if (!empty($order_by) && $order_by != '') {
524
                $sql .= ' '.$order_by;
525
            }
526
        }
527
528
        $result = Database::query($sql);
529
        $categories = [];
530
        if (Database::num_rows($result) > 0) {
531
            $categories = self::create_category_objects_from_sql_result($result);
532
        }
533
534
        return $categories;
535
    }
536
537
    /**
538
     * Create a category object from a GradebookCategory entity.
539
     *
540
     * @param GradebookCategory $gradebookCategory The entity
541
     *
542
     * @return \Category
543
     */
544
    public static function createCategoryObjectFromEntity(GradebookCategory $gradebookCategory)
545
    {
546
        $category = new Category();
547
        $category->set_id($gradebookCategory->getId());
548
        $category->set_name($gradebookCategory->getName());
549
        $category->set_description($gradebookCategory->getDescription());
550
        $category->set_user_id($gradebookCategory->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

550
        $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...
551
        $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

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

663
        $course = /** @scrutinizer ignore-call */ api_get_user_course_entity();
Loading history...
664
665
        $gradebookCategory->setName($this->name);
666
        $gradebookCategory->setDescription($this->description);
667
        $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

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

1448
            /** @scrutinizer ignore-call */ 
1449
            $cats = self::get_root_categories_for_teacher($user);
Loading history...
1449
            foreach ($cats as $cat) {
1450
                $targets[] = [
1451
                    $cat->get_id(),
1452
                    $cat->get_name(),
1453
                    $level + 1,
1454
                ];
1455
                $targets = $this->add_subtree(
1456
                    $targets,
1457
                    $level + 1,
1458
                    $cat->get_id(),
1459
                    null
1460
                );
1461
            }
1462
        } else {
1463
            // student
1464
            $cats = $this->get_root_categories_for_student(api_get_user_id());
1465
            foreach ($cats as $cat) {
1466
                $targets[] = [
1467
                    $cat->get_id(),
1468
                    $cat->get_name(),
1469
                    $level + 1,
1470
                ];
1471
                $targets = $this->add_subtree(
1472
                    $targets,
1473
                    $level + 1,
1474
                    $cat->get_id(),
1475
                    1
1476
                );
1477
            }
1478
        }
1479
1480
        return $targets;
1481
    }
1482
1483
    /**
1484
     * Generate an array of courses that a teacher hasn't created a category for.
1485
     *
1486
     * @param int $user_id
1487
     *
1488
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1489
     */
1490
    public function get_not_created_course_categories($user_id)
1491
    {
1492
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1493
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1494
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1495
1496
        $user_id = (int) $user_id;
1497
1498
        $sql = 'SELECT DISTINCT(code), title
1499
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1500
                WHERE 
1501
                    cc.id = cu.c_id AND 
1502
                    cu.status = '.COURSEMANAGER;
1503
1504
        if (!api_is_platform_admin()) {
1505
            $sql .= ' AND cu.user_id = '.$user_id;
1506
        }
1507
        $sql .= ' AND cc.code NOT IN
1508
             (
1509
                SELECT course_code FROM '.$tbl_grade_categories.'
1510
                WHERE
1511
                    parent_id = 0 AND
1512
                    course_code IS NOT NULL
1513
                )';
1514
        $result = Database::query($sql);
1515
1516
        $cats = [];
1517
        while ($data = Database::fetch_array($result)) {
1518
            $cats[] = [$data['code'], $data['title']];
1519
        }
1520
1521
        return $cats;
1522
    }
1523
1524
    /**
1525
     * Generate an array of all courses that a teacher is admin of.
1526
     *
1527
     * @param int $user_id
1528
     *
1529
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1530
     */
1531
    public function get_all_courses($user_id)
1532
    {
1533
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1534
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1535
        $sql = 'SELECT DISTINCT(code), title
1536
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1537
                WHERE cc.id = cu.c_id AND cu.status = '.COURSEMANAGER;
1538
        if (!api_is_platform_admin()) {
1539
            $sql .= ' AND cu.user_id = '.intval($user_id);
1540
        }
1541
1542
        $result = Database::query($sql);
1543
        $cats = [];
1544
        while ($data = Database::fetch_array($result)) {
1545
            $cats[] = [$data['code'], $data['title']];
1546
        }
1547
1548
        return $cats;
1549
    }
1550
1551
    /**
1552
     * Apply the same visibility to every subcategory, evaluation and link.
1553
     */
1554
    public function apply_visibility_to_children()
1555
    {
1556
        $cats = self::load(null, null, null, $this->id, null);
1557
        $evals = Evaluation::load(null, null, null, $this->id, null);
1558
        $links = LinkFactory::load(
1559
            null,
1560
            null,
1561
            null,
1562
            null,
1563
            null,
1564
            $this->id,
1565
            null
1566
        );
1567
        if (!empty($cats)) {
1568
            foreach ($cats as $cat) {
1569
                $cat->set_visible($this->is_visible());
1570
                $cat->save();
1571
                $cat->apply_visibility_to_children();
1572
            }
1573
        }
1574
        if (!empty($evals)) {
1575
            foreach ($evals as $eval) {
1576
                $eval->set_visible($this->is_visible());
1577
                $eval->save();
1578
            }
1579
        }
1580
        if (!empty($links)) {
1581
            foreach ($links as $link) {
1582
                $link->set_visible($this->is_visible());
1583
                $link->save();
1584
            }
1585
        }
1586
    }
1587
1588
    /**
1589
     * Check if a category contains evaluations with a result for a given student.
1590
     *
1591
     * @param int $studentId
1592
     *
1593
     * @return bool
1594
     */
1595
    public function hasEvaluationsWithStudentResults($studentId)
1596
    {
1597
        $evals = Evaluation::get_evaluations_with_result_for_student(
1598
            $this->id,
1599
            $studentId
1600
        );
1601
        if (count($evals) != 0) {
1602
            return true;
1603
        } else {
1604
            $cats = self::load(
1605
                null,
1606
                null,
1607
                null,
1608
                $this->id,
1609
                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

1609
                /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1
Loading history...
1610
            );
1611
1612
            /** @var Category $cat */
1613
            foreach ($cats as $cat) {
1614
                if ($cat->hasEvaluationsWithStudentResults($studentId)) {
1615
                    return true;
1616
                }
1617
            }
1618
1619
            return false;
1620
        }
1621
    }
1622
1623
    /**
1624
     * Retrieve all categories inside a course independent category
1625
     * that should be visible to a student.
1626
     *
1627
     * @param int   $categoryId parent category
1628
     * @param int   $studentId
1629
     * @param array $cats       optional: if defined, the categories will be added to this array
1630
     *
1631
     * @return array
1632
     */
1633
    public function getIndependentCategoriesWithStudentResult(
1634
        $categoryId,
1635
        $studentId,
1636
        $cats = []
1637
    ) {
1638
        $creator = api_is_allowed_to_edit() && !api_is_platform_admin() ? api_get_user_id() : null;
1639
1640
        $categories = self::load(
1641
            null,
1642
            $creator,
1643
            '0',
1644
            $categoryId,
1645
            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

1645
            /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1
Loading history...
1646
        );
1647
1648
        if (!empty($categories)) {
1649
            /** @var Category $category */
1650
            foreach ($categories as $category) {
1651
                if ($category->hasEvaluationsWithStudentResults($studentId)) {
1652
                    $cats[] = $category;
1653
                }
1654
            }
1655
        }
1656
1657
        return $cats;
1658
    }
1659
1660
    /**
1661
     * Return the session id (in any case, even if it's null or 0).
1662
     *
1663
     * @return int Session id (can be null)
1664
     */
1665
    public function get_session_id()
1666
    {
1667
        return $this->session_id;
1668
    }
1669
1670
    /**
1671
     * Get appropriate subcategories visible for the user (and optionally the course and session).
1672
     *
1673
     * @param int    $studentId   student id (default: all students)
1674
     * @param string $course_code Course code (optional)
1675
     * @param int    $session_id  Session ID (optional)
1676
     * @param bool   $order
1677
     *
1678
     * @return array Array of subcategories
1679
     */
1680
    public function get_subcategories(
1681
        $studentId = null,
1682
        $course_code = null,
1683
        $session_id = null,
1684
        $order = null
1685
    ) {
1686
        // 1 student
1687
        if (isset($studentId)) {
1688
            // Special case: this is the root
1689
            if ($this->id == 0) {
1690
                return $this->get_root_categories_for_student($studentId, $course_code, $session_id);
1691
            } else {
1692
                return self::load(
1693
                    null,
1694
                    null,
1695
                    $course_code,
1696
                    $this->id,
1697
                    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

1697
                    /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1,
Loading history...
1698
                    $session_id,
1699
                    $order
1700
                );
1701
            }
1702
        } else {
1703
            // All students
1704
            // Course admin
1705
            if (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1706
                // root
1707
                if ($this->id == 0) {
1708
                    // inside a course
1709
                    return $this->get_root_categories_for_teacher(
1710
                        api_get_user_id(),
1711
                        $course_code,
1712
                        $session_id,
1713
                        false
1714
                    );
1715
                } elseif (!empty($this->course_code)) {
1716
                    return self::load(
1717
                        null,
1718
                        null,
1719
                        $this->course_code,
1720
                        $this->id,
1721
                        null,
1722
                        $session_id,
1723
                        $order
1724
                    );
1725
                } elseif (!empty($course_code)) {
1726
                    // course independent
1727
                    return self::load(
1728
                        null,
1729
                        null,
1730
                        $course_code,
1731
                        $this->id,
1732
                        null,
1733
                        $session_id,
1734
                        $order
1735
                    );
1736
                } else {
1737
                    return self::load(
1738
                        null,
1739
                        api_get_user_id(),
1740
                        0,
1741
                        $this->id,
1742
                        null
1743
                    );
1744
                }
1745
            } elseif (api_is_platform_admin()) {
1746
                // platform admin
1747
                // we explicitly avoid listing subcats from another session
1748
                return self::load(
1749
                    null,
1750
                    null,
1751
                    $course_code,
1752
                    $this->id,
1753
                    null,
1754
                    $session_id,
1755
                    $order
1756
                );
1757
            }
1758
        }
1759
1760
        return [];
1761
    }
1762
1763
    /**
1764
     * Get appropriate evaluations visible for the user.
1765
     *
1766
     * @param int    $studentId   student id (default: all students)
1767
     * @param bool   $recursive   process subcategories (default: no recursion)
1768
     * @param string $course_code
1769
     * @param int    $sessionId
1770
     *
1771
     * @return array
1772
     */
1773
    public function get_evaluations(
1774
        $studentId = null,
1775
        $recursive = false,
1776
        $course_code = '',
1777
        $sessionId = 0
1778
    ) {
1779
        $evals = [];
1780
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1781
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1782
1783
        // 1 student
1784
        if (isset($studentId) && !empty($studentId)) {
1785
            // Special case: this is the root
1786
            if ($this->id == 0) {
1787
                $evals = Evaluation::get_evaluations_with_result_for_student(
1788
                    0,
1789
                    $studentId
1790
                );
1791
            } else {
1792
                $evals = Evaluation::load(
1793
                    null,
1794
                    null,
1795
                    $course_code,
1796
                    $this->id,
1797
                    api_is_allowed_to_edit() ? null : 1
1798
                );
1799
            }
1800
        } else {
1801
            // All students
1802
            // course admin
1803
            if ((api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) &&
1804
                !api_is_platform_admin()
1805
            ) {
1806
                // root
1807
                if ($this->id == 0) {
1808
                    $evals = Evaluation::load(
1809
                        null,
1810
                        api_get_user_id(),
1811
                        null,
1812
                        $this->id,
1813
                        null
1814
                    );
1815
                } elseif (isset($this->course_code) &&
1816
                    !empty($this->course_code)
1817
                ) {
1818
                    // inside a course
1819
                    $evals = Evaluation::load(
1820
                        null,
1821
                        null,
1822
                        $course_code,
1823
                        $this->id,
1824
                        null
1825
                    );
1826
                } else {
1827
                    // course independent
1828
                    $evals = Evaluation::load(
1829
                        null,
1830
                        api_get_user_id(),
1831
                        null,
1832
                        $this->id,
1833
                        null
1834
                    );
1835
                }
1836
            } else {
1837
                $evals = Evaluation::load(
1838
                    null,
1839
                    null,
1840
                    $course_code,
1841
                    $this->id,
1842
                    null
1843
                );
1844
            }
1845
        }
1846
1847
        if ($recursive) {
1848
            $subcats = $this->get_subcategories(
1849
                $studentId,
1850
                $course_code,
1851
                $sessionId
1852
            );
1853
1854
            if (!empty($subcats)) {
1855
                foreach ($subcats as $subcat) {
1856
                    $subevals = $subcat->get_evaluations(
1857
                        $studentId,
1858
                        true,
1859
                        $course_code
1860
                    );
1861
                    $evals = array_merge($evals, $subevals);
1862
                }
1863
            }
1864
        }
1865
1866
        return $evals;
1867
    }
1868
1869
    /**
1870
     * Get appropriate links visible for the user.
1871
     *
1872
     * @param int    $studentId   student id (default: all students)
1873
     * @param bool   $recursive   process subcategories (default: no recursion)
1874
     * @param string $course_code
1875
     * @param int    $sessionId
1876
     *
1877
     * @return array
1878
     */
1879
    public function get_links(
1880
        $studentId = null,
1881
        $recursive = false,
1882
        $course_code = '',
1883
        $sessionId = 0
1884
    ) {
1885
        $links = [];
1886
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1887
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1888
1889
        // no links in root or course independent categories
1890
        if ($this->id == 0) {
1891
        } elseif (isset($studentId)) {
1892
            // 1 student $studentId
1893
            $links = LinkFactory::load(
1894
                null,
1895
                null,
1896
                null,
1897
                null,
1898
                empty($this->course_code) ? null : $course_code,
1899
                $this->id,
1900
                api_is_allowed_to_edit() ? null : 1
1901
            );
1902
        } else {
1903
            // All students -> only for course/platform admin
1904
            $links = LinkFactory::load(
1905
                null,
1906
                null,
1907
                null,
1908
                null,
1909
                empty($this->course_code) ? null : $this->course_code,
1910
                $this->id,
1911
                null
1912
            );
1913
        }
1914
1915
        if ($recursive) {
1916
            $subcats = $this->get_subcategories(
1917
                $studentId,
1918
                $course_code,
1919
                $sessionId
1920
            );
1921
            if (!empty($subcats)) {
1922
                /** @var Category $subcat */
1923
                foreach ($subcats as $subcat) {
1924
                    $sublinks = $subcat->get_links(
1925
                        $studentId,
1926
                        false,
1927
                        $course_code,
1928
                        $sessionId
1929
                    );
1930
                    $links = array_merge($links, $sublinks);
1931
                }
1932
            }
1933
        }
1934
1935
        return $links;
1936
    }
1937
1938
    /**
1939
     * Get all the categories from with the same given direct parent.
1940
     *
1941
     * @param int $catId Category parent ID
1942
     *
1943
     * @return array Array of Category objects
1944
     */
1945
    public function getCategories($catId)
1946
    {
1947
        $catId = (int) $catId;
1948
        $tblGradeCategories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1949
        $sql = 'SELECT * FROM '.$tblGradeCategories.'
1950
                WHERE parent_id = '.$catId;
1951
1952
        $result = Database::query($sql);
1953
        $categories = self::create_category_objects_from_sql_result($result);
1954
1955
        return $categories;
1956
    }
1957
1958
    /**
1959
     * Gets the type for the current object.
1960
     *
1961
     * @return string 'C' to represent "Category" object type
1962
     */
1963
    public function get_item_type()
1964
    {
1965
        return 'C';
1966
    }
1967
1968
    /**
1969
     * @param array $skills
1970
     */
1971
    public function set_skills($skills)
1972
    {
1973
        $this->skills = $skills;
1974
    }
1975
1976
    public function get_date()
1977
    {
1978
        return null;
1979
    }
1980
1981
    /**
1982
     * @return string
1983
     */
1984
    public function get_icon_name()
1985
    {
1986
        return 'cat';
1987
    }
1988
1989
    /**
1990
     * Find category by name.
1991
     *
1992
     * @param string $name_mask search string
1993
     *
1994
     * @return array category objects matching the search criterium
1995
     */
1996
    public function find_category($name_mask, $allcat)
1997
    {
1998
        $categories = [];
1999
        foreach ($allcat as $search_cat) {
2000
            if (!(strpos(strtolower($search_cat->get_name()), strtolower($name_mask)) === false)) {
2001
                $categories[] = $search_cat;
2002
            }
2003
        }
2004
2005
        return $categories;
2006
    }
2007
2008
    /**
2009
     * This function, locks a category , only one who can unlock it is
2010
     * the platform administrator.
2011
     *
2012
     * @param int locked 1 or unlocked 0
2013
2014
     *
2015
     * @return bool|null
2016
     * */
2017
    public function lock($locked)
2018
    {
2019
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
2020
        $sql = "UPDATE $table SET locked = '".intval($locked)."'
2021
                WHERE id='".intval($this->id)."'";
2022
        Database::query($sql);
2023
    }
2024
2025
    /**
2026
     * @param $locked
2027
     */
2028
    public function lockAllItems($locked)
2029
    {
2030
        if (api_get_setting('gradebook_locking_enabled') == 'true') {
2031
            $this->lock($locked);
2032
            $evals_to_lock = $this->get_evaluations();
2033
            if (!empty($evals_to_lock)) {
2034
                foreach ($evals_to_lock as $item) {
2035
                    $item->lock($locked);
2036
                }
2037
            }
2038
2039
            $link_to_lock = $this->get_links();
2040
            if (!empty($link_to_lock)) {
2041
                foreach ($link_to_lock as $item) {
2042
                    $item->lock($locked);
2043
                }
2044
            }
2045
2046
            $event_type = LOG_GRADEBOOK_UNLOCKED;
2047
            if ($locked == 1) {
2048
                $event_type = LOG_GRADEBOOK_LOCKED;
2049
            }
2050
            Event::addEvent($event_type, LOG_GRADEBOOK_ID, $this->id);
0 ignored issues
show
Bug introduced by
The method addEvent() does not exist on Event. ( Ignorable by Annotation )

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

2050
            Event::/** @scrutinizer ignore-call */ 
2051
                   addEvent($event_type, LOG_GRADEBOOK_ID, $this->id);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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

2641
        while ($data = Database::fetch_array(/** @scrutinizer ignore-type */ $result)) {
Loading history...
2642
            $cat = new Category();
2643
            $cat->set_id($data['id']);
2644
            $cat->set_name($data['name']);
2645
            $cat->set_description($data['description']);
2646
            $cat->set_user_id($data['user_id']);
2647
            $courseInfo = api_get_course_info_by_id($data['c_id']);
2648
            $cat->set_course_code($courseInfo['code']);
2649
            $cat->setCourseId($data['c_id']);
2650
            $cat->set_parent_id($data['parent_id']);
2651
            $cat->set_weight($data['weight']);
2652
            $cat->set_visible($data['visible']);
2653
            $cat->set_session_id($data['session_id']);
2654
            $cat->set_certificate_min_score($data['certif_min_score']);
2655
            $cat->set_grade_model_id($data['grade_model_id']);
2656
            $cat->set_locked($data['locked']);
2657
            $cat->setGenerateCertificates($data['generate_certificates']);
2658
            $cat->setIsRequirement($data['is_requirement']);
2659
            $cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2660
            $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2661
            $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2662
            $cat->setDocumentId($data['document_id']);
2663
            if ($allow) {
2664
                $cat->entity = $repo->find($data['id']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $repo does not seem to be defined for all execution paths leading up to this point.
Loading history...
2665
            }
2666
2667
            $categories[] = $cat;
2668
        }
2669
2670
        return $categories;
2671
    }
2672
2673
    /**
2674
     * Internal function used by get_target_categories().
2675
     *
2676
     * @param array $targets
2677
     * @param int   $level
2678
     * @param int   $catid
2679
     *
2680
     * @return array
2681
     */
2682
    private function addTargetSubcategories($targets, $level, $catid)
2683
    {
2684
        $subcats = self::load(null, null, null, $catid);
2685
        foreach ($subcats as $cat) {
2686
            if ($this->can_be_moved_to_cat($cat)) {
2687
                $targets[] = [
2688
                    $cat->get_id(),
2689
                    $cat->get_name(),
2690
                    $level + 1,
2691
                ];
2692
                $targets = $this->addTargetSubcategories(
2693
                    $targets,
2694
                    $level + 1,
2695
                    $cat->get_id()
2696
                );
2697
            }
2698
        }
2699
2700
        return $targets;
2701
    }
2702
2703
    /**
2704
     * Internal function used by get_target_categories() and addTargetSubcategories()
2705
     * Can this category be moved to the given category ?
2706
     * Impossible when origin and target are the same... children won't be processed
2707
     * either. (a category can't be moved to one of its own children).
2708
     */
2709
    private function can_be_moved_to_cat($cat)
2710
    {
2711
        return $cat->get_id() != $this->get_id();
2712
    }
2713
2714
    /**
2715
     * Internal function used by move_to_cat().
2716
     */
2717
    private function applyCourseCodeToChildren()
2718
    {
2719
        $cats = self::load(null, null, null, $this->id, null);
2720
        $evals = Evaluation::load(null, null, null, $this->id, null);
2721
        $links = LinkFactory::load(
2722
            null,
2723
            null,
2724
            null,
2725
            null,
2726
            null,
2727
            $this->id,
2728
            null
2729
        );
2730
        /** @var Category $cat */
2731
        foreach ($cats as $cat) {
2732
            $cat->set_course_code($this->get_course_code());
2733
            $cat->save();
2734
            $cat->applyCourseCodeToChildren();
2735
        }
2736
2737
        foreach ($evals as $eval) {
2738
            $eval->set_course_code($this->get_course_code());
2739
            $eval->save();
2740
        }
2741
2742
        foreach ($links as $link) {
2743
            $link->delete();
2744
        }
2745
    }
2746
2747
    /**
2748
     * Internal function used by get_tree().
2749
     *
2750
     * @param int      $level
2751
     * @param int|null $visible
2752
     *
2753
     * @return array
2754
     */
2755
    private function add_subtree($targets, $level, $catid, $visible)
2756
    {
2757
        $subcats = self::load(null, null, null, $catid, $visible);
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

2757
        $subcats = self::load(null, null, null, $catid, /** @scrutinizer ignore-type */ $visible);
Loading history...
2758
2759
        if (!empty($subcats)) {
2760
            foreach ($subcats as $cat) {
2761
                $targets[] = [
2762
                    $cat->get_id(),
2763
                    $cat->get_name(),
2764
                    $level + 1,
2765
                ];
2766
                $targets = self::add_subtree(
0 ignored issues
show
Bug Best Practice introduced by
The method Category::add_subtree() is not static, but was called statically. ( Ignorable by Annotation )

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

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