Completed
Push — master ( 27e209...a08afa )
by Julito
186:04 queued 150:53
created

Category::getMinimumToValidate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

549
        while ($data = Database::fetch_array(/** @scrutinizer ignore-type */ $result)) {
Loading history...
550
            $cat = new Category();
551
            $cat->set_id($data['id']);
552
            $cat->set_name($data['name']);
553
            $cat->set_description($data['description']);
554
            $cat->set_user_id($data['user_id']);
555
            $cat->set_course_code($data['course_code']);
556
            $cat->set_parent_id($data['parent_id']);
557
            $cat->set_weight($data['weight']);
558
            $cat->set_visible($data['visible']);
559
            $cat->set_session_id($data['session_id']);
560
            $cat->set_certificate_min_score($data['certif_min_score']);
561
            $cat->set_grade_model_id($data['grade_model_id']);
562
            $cat->set_locked($data['locked']);
563
            $cat->setGenerateCertificates($data['generate_certificates']);
564
            $cat->setIsRequirement($data['is_requirement']);
565
            $cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
566
            $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
567
            $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
568
569
            $categories[] = $cat;
570
        }
571
572
        return $categories;
573
    }
574
575
    /**
576
     * Create a category object from a GradebookCategory entity
577
     * @param GradebookCategory $gradebookCategory  The entity
578
     * @return \Category
579
     */
580
    public static function createCategoryObjectFromEntity(
581
        GradebookCategory $gradebookCategory
582
    ) {
583
        $category = new Category();
584
        $category->set_id($gradebookCategory->getId());
585
        $category->set_name($gradebookCategory->getName());
586
        $category->set_description($gradebookCategory->getDescription());
587
        $category->set_user_id($gradebookCategory->getUserId());
588
        $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

588
        $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...
589
        $category->set_parent_id($gradebookCategory->getParentId());
590
        $category->set_weight($gradebookCategory->getWeight());
591
        $category->set_visible($gradebookCategory->getVisible());
592
        $category->set_session_id($gradebookCategory->getSessionId());
593
        $category->set_certificate_min_score(
594
            $gradebookCategory->getCertifMinScore()
595
        );
596
        $category->set_grade_model_id($gradebookCategory->getGradeModelId());
597
        $category->set_locked($gradebookCategory->getLocked());
598
        $category->setGenerateCertificates(
599
            $gradebookCategory->getGenerateCertificates()
600
        );
601
        $category->setIsRequirement($gradebookCategory->getIsRequirement());
602
603
        return $category;
604
    }
605
606
    /**
607
     * Insert this category into the database
608
     */
609
    public function add()
610
    {
611
        if (isset($this->name) && '-1' == $this->name) {
612
            return false;
613
        }
614
615
        if (isset($this->name) && isset($this->user_id)) {
616
            $em = Database::getManager();
617
618
            $category = new GradebookCategory();
619
            $category->setName($this->name);
620
            $category->setDescription($this->description);
621
            $category->setUserId($this->user_id);
622
            $category->setCourseCode($this->course_code);
0 ignored issues
show
Bug introduced by
The method setCourseCode() does not exist on Chamilo\CoreBundle\Entity\GradebookCategory. Did you maybe mean setCourse()? ( Ignorable by Annotation )

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

622
            $category->/** @scrutinizer ignore-call */ 
623
                       setCourseCode($this->course_code);

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...
623
            $category->setParentId($this->parent);
624
            $category->setWeight($this->weight);
625
            $category->setVisible($this->visible);
626
            $category->setCertifMinScore($this->certificate_min_score);
627
            $category->setSessionId($this->session_id);
628
            $category->setGenerateCertificates($this->generateCertificates);
629
            $category->setGradeModelId($this->grade_model_id);
630
            $category->setIsRequirement($this->isRequirement);
631
            $category->setLocked(false);
632
633
            $em->persist($category);
634
            $em->flush();
635
636
            $id = $category->getId();
637
            $this->set_id($id);
638
639
            if (!empty($id)) {
640
                $parent_id = $this->get_parent_id();
641
                $grade_model_id = $this->get_grade_model_id();
642
                if ($parent_id == 0) {
643
                    //do something
644
                    if (isset($grade_model_id) &&
645
                        !empty($grade_model_id) &&
646
                        $grade_model_id != '-1'
647
                    ) {
648
                        $obj = new GradeModel();
649
                        $components = $obj->get_components($grade_model_id);
650
                        $default_weight_setting = api_get_setting('gradebook_default_weight');
651
                        $default_weight = 100;
652
                        if (isset($default_weight_setting)) {
653
                            $default_weight = $default_weight_setting;
654
                        }
655
                        foreach ($components as $component) {
656
                            $gradebook = new Gradebook();
657
                            $params = [];
658
659
                            $params['name'] = $component['acronym'];
660
                            $params['description'] = $component['title'];
661
                            $params['user_id'] = api_get_user_id();
662
                            $params['parent_id'] = $id;
663
                            $params['weight'] = $component['percentage'] / 100 * $default_weight;
664
                            $params['session_id'] = api_get_session_id();
665
                            $params['course_code'] = $this->get_course_code();
666
667
                            $gradebook->save($params);
668
                        }
669
                    }
670
                }
671
            }
672
673
            $gradebook = new Gradebook();
674
            $gradebook->updateSkillsToGradeBook(
675
                $this->id,
676
                $this->get_skills(false)
677
            );
678
679
            return $id;
680
        }
681
    }
682
683
    /**
684
     * Update the properties of this category in the database
685
     * @todo fix me
686
     */
687
    public function save()
688
    {
689
        $em = Database::getManager();
690
691
        $gradebookCategory = $em
692
            ->getRepository('ChamiloCoreBundle:GradebookCategory')
693
            ->find($this->id);
694
695
        if (empty($gradebookCategory)) {
696
            return false;
697
        }
698
699
        $gradebookCategory->setName($this->name);
700
        $gradebookCategory->setDescription($this->description);
701
        $gradebookCategory->setUserId($this->user_id);
702
        $gradebookCategory->setCourseCode($this->course_code);
703
        $gradebookCategory->setParentId($this->parent);
704
        $gradebookCategory->setWeight($this->weight);
705
        $gradebookCategory->setVisible($this->visible);
706
        $gradebookCategory->setCertifMinScore($this->certificate_min_score);
707
        $gradebookCategory->setGenerateCertificates(
708
            $this->generateCertificates
709
        );
710
        $gradebookCategory->setGradeModelId($this->grade_model_id);
711
        $gradebookCategory->setIsRequirement($this->isRequirement);
712
713
        $em->merge($gradebookCategory);
714
        $em->flush();
715
716
        if (!empty($this->id)) {
717
            $parent_id = $this->get_parent_id();
718
            $grade_model_id = $this->get_grade_model_id();
719
            if ($parent_id == 0) {
720
                if (isset($grade_model_id) &&
721
                    !empty($grade_model_id) &&
722
                    $grade_model_id != '-1'
723
                ) {
724
                    $obj = new GradeModel();
725
                    $components = $obj->get_components($grade_model_id);
726
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
727
                    $default_weight = 100;
728
                    if (isset($default_weight_setting)) {
729
                        $default_weight = $default_weight_setting;
730
                    }
731
                    $final_weight = $this->get_weight();
732
                    if (!empty($final_weight)) {
733
                        $default_weight = $this->get_weight();
734
                    }
735
                    foreach ($components as $component) {
736
                        $gradebook = new Gradebook();
737
                        $params = [];
738
                        $params['name'] = $component['acronym'];
739
                        $params['description'] = $component['title'];
740
                        $params['user_id'] = api_get_user_id();
741
                        $params['parent_id'] = $this->id;
742
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
743
                        $params['session_id'] = api_get_session_id();
744
                        $params['course_code'] = $this->get_course_code();
745
                        $gradebook->save($params);
746
                    }
747
                }
748
            }
749
        }
750
751
        $gradebook = new Gradebook();
752
        $gradebook->updateSkillsToGradeBook(
753
            $this->id,
754
            $this->get_skills(false),
755
            true
756
        );
757
    }
758
759
    /**
760
     * Update link weights see #5168
761
     * @param type $new_weight
762
     */
763
    public function updateChildrenWeight($new_weight)
764
    {
765
        $links = $this->get_links();
766
        $old_weight = $this->get_weight();
767
768
        if (!empty($links)) {
769
            foreach ($links as $link_item) {
770
                if (isset($link_item)) {
771
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
772
                    $link_item->set_weight($new_item_weight);
773
                    $link_item->save();
774
                }
775
            }
776
        }
777
    }
778
779
    /**
780
     * Delete this evaluation from the database
781
     */
782
    public function delete()
783
    {
784
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
785
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
786
        Database::query($sql);
787
    }
788
789
    /**
790
     * Not delete this category from the database,when visible=3 is category eliminated
791
     */
792
    public function update_category_delete($course_id)
793
    {
794
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
795
        $sql = 'UPDATE '.$table.' SET 
796
                    visible = 3
797
                WHERE course_code ="'.Database::escape_string($course_id).'"';
798
        Database::query($sql);
799
    }
800
801
    /**
802
     * Show message resource delete
803
     */
804
    public function show_message_resource_delete($course_id)
805
    {
806
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
807
        $sql = 'SELECT count(*) AS num 
808
                FROM '.$table.'
809
                WHERE
810
                    course_code = "'.Database::escape_string($course_id).'" AND
811
                    visible = 3';
812
        $res = Database::query($sql);
813
        $option = Database::fetch_array($res, 'ASSOC');
814
        if ($option['num'] >= 1) {
815
            return '&nbsp;&nbsp;<span class="resource-deleted">(&nbsp;'.get_lang('ResourceDeleted').'&nbsp;)</span>';
816
        } else {
817
            return false;
818
        }
819
    }
820
821
    /**
822
     * Shows all information of an category
823
     */
824
    public function showAllCategoryInfo($categoryId = '')
825
    {
826
        if ($categoryId == '') {
827
            return null;
828
        } else {
829
            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
830
            $sql = 'SELECT * FROM '.$table.'
831
                    WHERE id = '.intval($categoryId);
832
            $result = Database::query($sql);
833
            $row = Database::fetch_array($result, 'ASSOC');
834
835
            return $row;
836
        }
837
    }
838
839
    /**
840
     * Check if a category name (with the same parent category) already exists
841
     * @param string $name name to check (if not given, the name property of this object will be checked)
842
     * @param int $parent parent category
843
     *
844
     * @return bool
845
     */
846
    public function does_name_exist($name, $parent)
847
    {
848
        if (!isset($name)) {
849
            $name = $this->name;
850
            $parent = $this->parent;
851
        }
852
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
853
        $sql = "SELECT count(id) AS number
854
                FROM $tbl_grade_categories
855
                WHERE name = '".Database::escape_string($name)."'";
856
857
        if (api_is_allowed_to_edit()) {
858
            $parent = self::load($parent);
859
            $code = $parent[0]->get_course_code();
860
            $courseInfo = api_get_course_info($code);
861
            $courseId = $courseInfo['real_id'];
862
            if (isset($code) && $code != '0') {
863
                $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
864
                $sql .= ' AND user_id IN (
865
                            SELECT user_id FROM '.$main_course_user_table.'
866
                            WHERE c_id = '.$courseId.' AND status = '.COURSEMANAGER.'
867
                        )';
868
            } else {
869
                $sql .= ' AND user_id = '.api_get_user_id();
870
            }
871
        } else {
872
            $sql .= ' AND user_id = '.api_get_user_id();
873
        }
874
875
        if (!isset($parent)) {
876
            $sql .= ' AND parent_id is null';
877
        } else {
878
            $sql .= ' AND parent_id = '.intval($parent);
879
        }
880
881
        $result = Database::query($sql);
882
        $number = Database::fetch_row($result);
883
884
        return $number[0] != 0;
885
    }
886
887
    /**
888
     * Checks if the certificate is available for the given user in this category
889
     * @param   integer    $user_id User ID
890
     * @return  boolean    True if conditions match, false if fails
891
     */
892
    public function is_certificate_available($user_id)
893
    {
894
        $score = $this->calc_score(
895
            $user_id,
896
            null,
897
            $this->course_code,
898
            $this->session_id
899
        );
900
901
        if (isset($score) && isset($score[0])) {
902
            // Get a percentage score to compare to minimum certificate score
903
            // $certification_score = $score[0] / $score[1] * 100;
904
            // Get real score not a percentage.
905
            $certification_score = $score[0];
906
            if ($certification_score >= $this->certificate_min_score) {
907
                return true;
908
            }
909
        }
910
911
        return false;
912
    }
913
914
    /**
915
     * Is this category a course ?
916
     * A category is a course if it has a course code and no parent category.
917
     */
918
    public function is_course()
919
    {
920
        return (isset($this->course_code) && !empty($this->course_code)
921
            && (!isset($this->parent) || $this->parent == 0));
922
    }
923
924
    /**
925
     * Calculate the score of this category
926
     * @param integer $stud_id student id (default: all students - then the average is returned)
927
     * @param integer $session_id
928
     * @param string $course_code
929
     * @param int $session_id
930
     * @return    array (score sum, weight sum)
931
     *             or null if no scores available
932
     */
933
    public function calc_score(
934
        $stud_id = null,
935
        $type = null,
936
        $course_code = '',
937
        $session_id = null
938
    ) {
939
        // Classic
940
        if (!empty($stud_id) && $type == '') {
941
            if (!empty($course_code)) {
942
                $cats = $this->get_subcategories(
943
                    $stud_id,
944
                    $course_code,
945
                    $session_id
946
                );
947
                $evals = $this->get_evaluations($stud_id, false, $course_code);
948
                $links = $this->get_links($stud_id, false, $course_code);
949
            } else {
950
                $cats = $this->get_subcategories($stud_id);
951
                $evals = $this->get_evaluations($stud_id);
952
                $links = $this->get_links($stud_id);
953
            }
954
955
            // Calculate score
956
            $count = 0;
957
            $ressum = 0;
958
            $weightsum = 0;
959
960
            if (!empty($cats)) {
961
                /** @var Category $cat */
962
                foreach ($cats as $cat) {
963
                    $cat->set_session_id($session_id);
964
                    $cat->set_course_code($course_code);
965
                    $cat->setStudentList($this->getStudentList());
966
                    $score = $cat->calc_score(
967
                        $stud_id,
968
                        null,
969
                        $course_code,
970
                        $session_id
971
                    );
972
973
                    $catweight = 0;
974
                    if ($cat->get_weight() != 0) {
975
                        $catweight = $cat->get_weight();
976
                        $weightsum += $catweight;
977
                    }
978
979
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
980
                        $ressum += $score[0] / $score[1] * $catweight;
981
                    }
982
                }
983
            }
984
985
            $students = [];
986
            if (!empty($evals)) {
987
                /** @var Evaluation $eval */
988
                foreach ($evals as $eval) {
989
                    $eval->setStudentList($this->getStudentList());
990
                    $evalres = $eval->calc_score($stud_id, null);
991
992
                    if (isset($evalres) && $eval->get_weight() != 0) {
993
                        $evalweight = $eval->get_weight();
994
                        $weightsum += $evalweight;
995
                        $count++;
996
                        if (!empty($evalres[1])) {
997
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
998
                        }
999
                    } else {
1000
                        if ($eval->get_weight() != 0) {
1001
                            $evalweight = $eval->get_weight();
1002
                            $weightsum += $evalweight;
1003
                        }
1004
                    }
1005
                }
1006
            }
1007
1008
            if (!empty($links)) {
1009
                /** @var EvalLink|ExerciseLink $link */
1010
                foreach ($links as $link) {
1011
                    $link->setStudentList($this->getStudentList());
1012
1013
                    if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type null|integer 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...
1014
                        $link->set_session_id($session_id);
1015
                    }
1016
1017
                    $linkres = $link->calc_score($stud_id, null);
1018
                    if (!empty($linkres) && $link->get_weight() != 0) {
1019
                        $students[$stud_id] = $linkres[0];
1020
                        $linkweight = $link->get_weight();
1021
                        $link_res_denom = $linkres[1] == 0 ? 1 : $linkres[1];
1022
                        $count++;
1023
                        $weightsum += $linkweight;
1024
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1025
                    } else {
1026
                        // Adding if result does not exists
1027
                        if ($link->get_weight() != 0) {
1028
                            $linkweight = $link->get_weight();
1029
                            $weightsum += $linkweight;
1030
                        }
1031
                    }
1032
                }
1033
            }
1034
        } else {
1035
            if (!empty($course_code)) {
1036
                $cats = $this->get_subcategories(
1037
                    null,
1038
                    $course_code,
1039
                    $session_id
1040
                );
1041
                $evals = $this->get_evaluations(null, false, $course_code);
1042
                $links = $this->get_links(null, false, $course_code);
1043
            } else {
1044
                $cats = $this->get_subcategories(null);
1045
                $evals = $this->get_evaluations(null);
1046
                $links = $this->get_links(null);
1047
            }
1048
1049
            // Calculate score
1050
            $count = 0;
1051
            $ressum = 0;
1052
            $weightsum = 0;
1053
            $bestResult = 0;
1054
1055
            if (!empty($cats)) {
1056
                /** @var Category $cat */
1057
                foreach ($cats as $cat) {
1058
                    $cat->setStudentList($this->getStudentList());
1059
                    $score = $cat->calc_score(
1060
                        null,
1061
                        $type,
1062
                        $course_code,
1063
                        $session_id
1064
                    );
1065
1066
                    $catweight = 0;
1067
                    if ($cat->get_weight() != 0) {
1068
                        $catweight = $cat->get_weight();
1069
                        $weightsum += $catweight;
1070
                    }
1071
1072
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
1073
                        $ressum += $score[0] / $score[1] * $catweight;
1074
1075
                        if ($ressum > $bestResult) {
1076
                            $bestResult = $ressum;
1077
                        }
1078
                    }
1079
                }
1080
            }
1081
1082
            if (!empty($evals)) {
1083
                /** @var Evaluation $eval */
1084
                foreach ($evals as $eval) {
1085
                    $evalres = $eval->calc_score(null, $type);
1086
                    $eval->setStudentList($this->getStudentList());
1087
1088
                    if (isset($evalres) && $eval->get_weight() != 0) {
1089
                        $evalweight = $eval->get_weight();
1090
                        $weightsum += $evalweight;
1091
                        $count++;
1092
                        if (!empty($evalres[1])) {
1093
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
1094
                        }
1095
1096
                        if ($ressum > $bestResult) {
1097
                            $bestResult = $ressum;
1098
                        }
1099
                    } else {
1100
                        if ($eval->get_weight() != 0) {
1101
                            $evalweight = $eval->get_weight();
1102
                            $weightsum += $evalweight;
1103
                        }
1104
                    }
1105
                }
1106
            }
1107
            if (!empty($links)) {
1108
                /** @var EvalLink|ExerciseLink $link */
1109
                foreach ($links as $link) {
1110
                    $link->setStudentList($this->getStudentList());
1111
1112
                    if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type null|integer 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...
1113
                        $link->set_session_id($session_id);
1114
                    }
1115
1116
                    $linkres = $link->calc_score($stud_id, $type);
1117
                    if (!empty($linkres) && $link->get_weight() != 0) {
1118
                        $students[$stud_id] = $linkres[0];
1119
                        $linkweight = $link->get_weight();
1120
                        $link_res_denom = $linkres[1] == 0 ? 1 : $linkres[1];
1121
1122
                        $count++;
1123
                        $weightsum += $linkweight;
1124
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1125
1126
                        if ($ressum > $bestResult) {
1127
                            $bestResult = $ressum;
1128
                        }
1129
                    } else {
1130
                        // Adding if result does not exists
1131
                        if ($link->get_weight() != 0) {
1132
                            $linkweight = $link->get_weight();
1133
                            $weightsum += $linkweight;
1134
                        }
1135
                    }
1136
                }
1137
            }
1138
        }
1139
1140
        switch ($type) {
1141
            case 'best':
1142
                if (empty($bestResult)) {
1143
                    return null;
1144
                }
1145
                return [$bestResult, $weightsum];
1146
                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...
1147
            case 'average':
1148
                if (empty($ressum)) {
1149
                    return null;
1150
                }
1151
                return [$ressum, $weightsum];
1152
                break;
1153
            case 'ranking':
1154
                // category ranking is calculated in gradebook_data_generator.class.php
1155
                // function get_data
1156
                return null;
1157
                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...
1158
                break;
1159
            default:
1160
                return [$ressum, $weightsum];
1161
                break;
1162
        }
1163
    }
1164
1165
    /**
1166
     * Delete this category and every subcategory, evaluation and result inside
1167
     */
1168
    public function delete_all()
1169
    {
1170
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1171
        $evals = Evaluation::load(
1172
            null,
1173
            null,
1174
            $this->course_code,
1175
            $this->id,
1176
            null
1177
        );
1178
1179
        $links = LinkFactory::load(
1180
            null,
1181
            null,
1182
            null,
1183
            null,
1184
            $this->course_code,
1185
            $this->id,
1186
            null
1187
        );
1188
1189
        if (!empty($cats)) {
1190
            /** @var Category $cat */
1191
            foreach ($cats as $cat) {
1192
                $cat->delete_all();
1193
                $cat->delete();
1194
            }
1195
        }
1196
1197
        if (!empty($evals)) {
1198
            /** @var Evaluation $eval */
1199
            foreach ($evals as $eval) {
1200
                $eval->delete_with_results();
1201
            }
1202
        }
1203
1204
        if (!empty($links)) {
1205
            /** @var AbstractLink $link */
1206
            foreach ($links as $link) {
1207
                $link->delete();
1208
            }
1209
        }
1210
1211
        $this->delete();
1212
    }
1213
1214
    /**
1215
     * Return array of Category objects where a student is subscribed to.
1216
     *
1217
     * @param integer $stud_id
1218
     * @param string $course_code
1219
     * @param integer $session_id
1220
     * @return array
1221
     */
1222
    public function get_root_categories_for_student(
1223
        $stud_id,
1224
        $course_code = null,
1225
        $session_id = null
1226
    ) {
1227
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1228
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1229
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1230
1231
        $course_code = Database::escape_string($course_code);
1232
        $session_id = (int) $session_id;
1233
1234
        $sql = "SELECT * FROM $tbl_grade_categories WHERE parent_id = 0";
1235
1236
        if (!api_is_allowed_to_edit()) {
1237
            $sql .= ' AND visible = 1';
1238
            //proceed with checks on optional parameters course & session
1239
            if (!empty($course_code)) {
1240
                // TODO: considering it highly improbable that a user would get here
1241
                // if he doesn't have the rights to view this course and this
1242
                // session, we don't check his registration to these, but this
1243
                // could be an improvement
1244
                if (!empty($session_id)) {
1245
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1246
                } else {
1247
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1248
                }
1249
            } else {
1250
                //no optional parameter, proceed as usual
1251
                $sql .= ' AND course_code in
1252
                     (
1253
                        SELECT c.code
1254
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1255
                        ON (cu.c_id = c.id)
1256
                        WHERE cu.user_id = '.intval($stud_id).'
1257
                        AND cu.status = '.STUDENT.'
1258
                    )';
1259
            }
1260
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1261
            //proceed with checks on optional parameters course & session
1262
            if (!empty($course_code)) {
1263
                // TODO: considering it highly improbable that a user would get here
1264
                // if he doesn't have the rights to view this course and this
1265
                // session, we don't check his registration to these, but this
1266
                // could be an improvement
1267
                $sql .= " AND course_code  = '".$course_code."'";
1268
                if (!empty($session_id)) {
1269
                    $sql .= " AND session_id = ".$session_id;
1270
                } else {
1271
                    $sql .= "AND session_id IS NULL OR session_id=0";
1272
                }
1273
            } else {
1274
                $sql .= ' AND course_code IN
1275
                     (
1276
                        SELECT c.code
1277
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1278
                        ON (cu.c_id = c.id)
1279
                        WHERE
1280
                            cu.user_id = '.api_get_user_id().' AND
1281
                            cu.status = '.COURSEMANAGER.'
1282
                    )';
1283
            }
1284
        } elseif (api_is_platform_admin()) {
1285
            if (isset($session_id) && $session_id != 0) {
1286
                $sql .= ' AND session_id='.$session_id;
1287
            } else {
1288
                $sql .= ' AND coalesce(session_id,0)=0';
1289
            }
1290
        }
1291
        $result = Database::query($sql);
1292
        $cats = self::create_category_objects_from_sql_result($result);
1293
1294
        // course independent categories
1295
        if (empty($course_code)) {
1296
            $cats = self::getIndependentCategoriesWithStudentResult(
0 ignored issues
show
Bug Best Practice introduced by
The method Category::getIndependent...riesWithStudentResult() 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

1296
            /** @scrutinizer ignore-call */ 
1297
            $cats = self::getIndependentCategoriesWithStudentResult(
Loading history...
1297
                0,
1298
                $stud_id,
1299
                $cats
1300
            );
1301
        }
1302
1303
        return $cats;
1304
    }
1305
1306
    /**
1307
     * Return array of Category objects where a teacher is admin for.
1308
     *
1309
     * @param integer $user_id (to return everything, use 'null' here)
1310
     * @param string $course_code (optional)
1311
     * @param integer $session_id (optional)
1312
     * @return array
1313
     */
1314
    public function get_root_categories_for_teacher(
1315
        $user_id,
1316
        $course_code = null,
1317
        $session_id = null
1318
    ) {
1319
        if ($user_id == null) {
1320
            return self::load(null, null, $course_code, 0, null, $session_id);
1321
        }
1322
1323
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1324
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1325
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1326
1327
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1328
                WHERE parent_id = 0 ';
1329
        if (!empty($course_code)) {
1330
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1331
            if (!empty($session_id)) {
1332
                $sql .= " AND session_id = ".(int) $session_id;
1333
            }
1334
        } else {
1335
            $sql .= ' AND course_code in
1336
                 (
1337
                    SELECT c.code
1338
                    FROM '.$main_course_user_table.' cu
1339
                    INNER JOIN '.$courseTable.' c
1340
                    ON (cu.c_id = c.id)
1341
                    WHERE user_id = '.intval($user_id).'
1342
                )';
1343
        }
1344
        $result = Database::query($sql);
1345
        $cats = self::create_category_objects_from_sql_result($result);
1346
        // course independent categories
1347
        if (isset($course_code)) {
1348
            $indcats = self::load(
1349
                null,
1350
                $user_id,
1351
                $course_code,
1352
                0,
1353
                null,
1354
                $session_id
1355
            );
1356
            $cats = array_merge($cats, $indcats);
1357
        }
1358
1359
        return $cats;
1360
    }
1361
1362
    /**
1363
     * Can this category be moved to somewhere else ?
1364
     * The root and courses cannot be moved.
1365
     * @return bool
1366
     */
1367
    public function is_movable()
1368
    {
1369
        return !(!isset($this->id) || $this->id == 0 || $this->is_course());
1370
    }
1371
1372
    /**
1373
     * Generate an array of possible categories where this category can be moved to.
1374
     * Notice: its own parent will be included in the list: it's up to the frontend
1375
     * to disable this element.
1376
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1377
     */
1378
    public function get_target_categories()
1379
    {
1380
        // the root or a course -> not movable
1381
        if (!$this->is_movable()) {
1382
            return null;
1383
        } else {
1384
            // otherwise:
1385
            // - course independent category
1386
            //   -> movable to root or other independent categories
1387
            // - category inside a course
1388
            //   -> movable to root, independent categories or categories inside the course
1389
1390
            $user = api_is_platform_admin() ? null : api_get_user_id();
1391
            $targets = [];
1392
            $level = 0;
1393
1394
            $root = [0, get_lang('RootCat'), $level];
1395
            $targets[] = $root;
1396
1397
            if (isset($this->course_code) && !empty($this->course_code)) {
1398
                $crscats = self::load(null, null, $this->course_code, 0);
1399
                foreach ($crscats as $cat) {
1400
                    if ($this->can_be_moved_to_cat($cat)) {
1401
                        $targets[] = [
1402
                            $cat->get_id(),
1403
                            $cat->get_name(),
1404
                            $level + 1
1405
                        ];
1406
                        $targets = $this->addTargetSubcategories(
1407
                            $targets,
1408
                            $level + 1,
1409
                            $cat->get_id()
1410
                        );
1411
                    }
1412
                }
1413
            }
1414
1415
            $indcats = self::load(null, $user, 0, 0);
1416
            foreach ($indcats as $cat) {
1417
                if ($this->can_be_moved_to_cat($cat)) {
1418
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1419
                    $targets = $this->addTargetSubcategories(
1420
                        $targets,
1421
                        $level + 1,
1422
                        $cat->get_id()
1423
                    );
1424
                }
1425
            }
1426
1427
            return $targets;
1428
        }
1429
    }
1430
1431
    /**
1432
     * Internal function used by get_target_categories()
1433
     * @param array $targets
1434
     * @param integer $level
1435
     * @param int $catid
1436
     *
1437
     * @return array
1438
     */
1439
    private function addTargetSubcategories($targets, $level, $catid)
1440
    {
1441
        $subcats = self::load(null, null, null, $catid);
1442
        foreach ($subcats as $cat) {
1443
            if ($this->can_be_moved_to_cat($cat)) {
1444
                $targets[] = [
1445
                    $cat->get_id(),
1446
                    $cat->get_name(),
1447
                    $level + 1
1448
                ];
1449
                $targets = $this->addTargetSubcategories(
1450
                    $targets,
1451
                    $level + 1,
1452
                    $cat->get_id()
1453
                );
1454
            }
1455
        }
1456
1457
        return $targets;
1458
    }
1459
1460
    /**
1461
     * Internal function used by get_target_categories() and addTargetSubcategories()
1462
     * Can this category be moved to the given category ?
1463
     * Impossible when origin and target are the same... children won't be processed
1464
     * either. (a category can't be moved to one of its own children)
1465
     */
1466
    private function can_be_moved_to_cat($cat)
1467
    {
1468
        return $cat->get_id() != $this->get_id();
1469
    }
1470
1471
    /**
1472
     * Move this category to the given category.
1473
     * If this category moves from inside a course to outside,
1474
     * its course code must be changed, as well as the course code
1475
     * of all underlying categories and evaluations. All links will
1476
     * be deleted as well !
1477
     */
1478
    public function move_to_cat($cat)
1479
    {
1480
        $this->set_parent_id($cat->get_id());
1481
        if ($this->get_course_code() != $cat->get_course_code()) {
1482
            $this->set_course_code($cat->get_course_code());
1483
            $this->applyCourseCodeToChildren();
1484
        }
1485
        $this->save();
1486
    }
1487
1488
    /**
1489
     * Internal function used by move_to_cat()
1490
     */
1491
    private function applyCourseCodeToChildren()
1492
    {
1493
        $cats = self::load(null, null, null, $this->id, null);
1494
        $evals = Evaluation::load(null, null, null, $this->id, null);
1495
        $links = LinkFactory::load(
1496
            null,
1497
            null,
1498
            null,
1499
            null,
1500
            null,
1501
            $this->id,
1502
            null
1503
        );
1504
        /** @var Category $cat */
1505
        foreach ($cats as $cat) {
1506
            $cat->set_course_code($this->get_course_code());
1507
            $cat->save();
1508
            $cat->applyCourseCodeToChildren();
1509
        }
1510
1511
        foreach ($evals as $eval) {
1512
            $eval->set_course_code($this->get_course_code());
1513
            $eval->save();
1514
        }
1515
1516
        foreach ($links as $link) {
1517
            $link->delete();
1518
        }
1519
    }
1520
1521
    /**
1522
     * Generate an array of all categories the user can navigate to
1523
     */
1524
    public function get_tree()
1525
    {
1526
        $targets = [];
1527
        $level = 0;
1528
        $root = [0, get_lang('RootCat'), $level];
1529
        $targets[] = $root;
1530
1531
        // course or platform admin
1532
        if (api_is_allowed_to_edit()) {
1533
            $user = api_is_platform_admin() ? null : api_get_user_id();
1534
            $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

1534
            /** @scrutinizer ignore-call */ 
1535
            $cats = self::get_root_categories_for_teacher($user);
Loading history...
1535
            foreach ($cats as $cat) {
1536
                $targets[] = [
1537
                    $cat->get_id(),
1538
                    $cat->get_name(),
1539
                    $level + 1
1540
                ];
1541
                $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

1541
                /** @scrutinizer ignore-call */ 
1542
                $targets = self::add_subtree(
Loading history...
1542
                    $targets,
1543
                    $level + 1,
1544
                    $cat->get_id(),
1545
                    null
1546
                );
1547
            }
1548
        } else {
1549
            // student
1550
            $cats = self::get_root_categories_for_student(api_get_user_id());
0 ignored issues
show
Bug Best Practice introduced by
The method Category::get_root_categories_for_student() 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

1550
            /** @scrutinizer ignore-call */ 
1551
            $cats = self::get_root_categories_for_student(api_get_user_id());
Loading history...
1551
            foreach ($cats as $cat) {
1552
                $targets[] = [
1553
                    $cat->get_id(),
1554
                    $cat->get_name(),
1555
                    $level + 1
1556
                ];
1557
                $targets = self::add_subtree(
1558
                    $targets,
1559
                    $level + 1,
1560
                    $cat->get_id(),
1561
                    1
1562
                );
1563
            }
1564
        }
1565
1566
        return $targets;
1567
    }
1568
1569
    /**
1570
     * Internal function used by get_tree()
1571
     * @param integer $level
1572
     * @param null|integer $visible
1573
     * @return array
1574
     */
1575
    private function add_subtree($targets, $level, $catid, $visible)
1576
    {
1577
        $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

1577
        $subcats = self::load(null, null, null, $catid, /** @scrutinizer ignore-type */ $visible);
Loading history...
1578
1579
        if (!empty($subcats)) {
1580
            foreach ($subcats as $cat) {
1581
                $targets[] = [
1582
                    $cat->get_id(),
1583
                    $cat->get_name(),
1584
                    $level + 1
1585
                ];
1586
                $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

1586
                /** @scrutinizer ignore-call */ 
1587
                $targets = self::add_subtree(
Loading history...
1587
                    $targets,
1588
                    $level + 1,
1589
                    $cat->get_id(),
1590
                    $visible
1591
                );
1592
            }
1593
        }
1594
1595
        return $targets;
1596
    }
1597
1598
    /**
1599
     * Generate an array of courses that a teacher hasn't created a category for.
1600
     * @param integer $user_id
1601
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1602
     */
1603
    public function get_not_created_course_categories($user_id)
1604
    {
1605
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1606
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1607
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1608
1609
        $sql = 'SELECT DISTINCT(code), title
1610
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1611
                WHERE 
1612
                    cc.id = cu.c_id AND 
1613
                    cu.status = '.COURSEMANAGER;
1614
        if (!api_is_platform_admin()) {
1615
            $sql .= ' AND cu.user_id = '.$user_id;
1616
        }
1617
        $sql .= ' AND cc.code NOT IN
1618
             (
1619
                SELECT course_code FROM '.$tbl_grade_categories.'
1620
                WHERE
1621
                    parent_id = 0 AND
1622
                    course_code IS NOT NULL
1623
                )';
1624
        $result = Database::query($sql);
1625
1626
        $cats = [];
1627
        while ($data = Database::fetch_array($result)) {
1628
            $cats[] = [$data['code'], $data['title']];
1629
        }
1630
1631
        return $cats;
1632
    }
1633
1634
    /**
1635
     * Generate an array of all courses that a teacher is admin of.
1636
     * @param integer $user_id
1637
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1638
     */
1639
    public function get_all_courses($user_id)
1640
    {
1641
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1642
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1643
        $sql = 'SELECT DISTINCT(code), title
1644
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1645
                WHERE cc.id = cu.c_id AND cu.status = '.COURSEMANAGER;
1646
        if (!api_is_platform_admin()) {
1647
            $sql .= ' AND cu.user_id = '.intval($user_id);
1648
        }
1649
1650
        $result = Database::query($sql);
1651
        $cats = [];
1652
        while ($data = Database::fetch_array($result)) {
1653
            $cats[] = [$data['code'], $data['title']];
1654
        }
1655
1656
        return $cats;
1657
    }
1658
1659
    /**
1660
     * Apply the same visibility to every subcategory, evaluation and link
1661
     */
1662
    public function apply_visibility_to_children()
1663
    {
1664
        $cats = self::load(null, null, null, $this->id, null);
1665
        $evals = Evaluation::load(null, null, null, $this->id, null);
1666
        $links = LinkFactory::load(
1667
            null,
1668
            null,
1669
            null,
1670
            null,
1671
            null,
1672
            $this->id,
1673
            null
1674
        );
1675
        if (!empty($cats)) {
1676
            foreach ($cats as $cat) {
1677
                $cat->set_visible($this->is_visible());
1678
                $cat->save();
1679
                $cat->apply_visibility_to_children();
1680
            }
1681
        }
1682
        if (!empty($evals)) {
1683
            foreach ($evals as $eval) {
1684
                $eval->set_visible($this->is_visible());
1685
                $eval->save();
1686
            }
1687
        }
1688
        if (!empty($links)) {
1689
            foreach ($links as $link) {
1690
                $link->set_visible($this->is_visible());
1691
                $link->save();
1692
            }
1693
        }
1694
    }
1695
1696
    /**
1697
     * Check if a category contains evaluations with a result for a given student
1698
     * @param int $studentId
1699
     * @return bool
1700
     */
1701
    public function hasEvaluationsWithStudentResults($studentId)
1702
    {
1703
        $evals = Evaluation::get_evaluations_with_result_for_student(
1704
            $this->id,
1705
            $studentId
1706
        );
1707
        if (count($evals) != 0) {
1708
            return true;
1709
        } else {
1710
            $cats = self::load(
1711
                null,
1712
                null,
1713
                null,
1714
                $this->id,
1715
                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

1715
                /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1
Loading history...
1716
            );
1717
            /** @var Category $cat */
1718
            foreach ($cats as $cat) {
1719
                if ($cat->hasEvaluationsWithStudentResults($studentId)) {
1720
                    return true;
1721
                }
1722
            }
1723
1724
            return false;
1725
        }
1726
    }
1727
1728
    /**
1729
     * Retrieve all categories inside a course independent category
1730
     * that should be visible to a student.
1731
     * @param int $categoryId parent category
1732
     * @param int $studentId
1733
     * @param array $cats optional: if defined, the categories will be added to this array
1734
     * @return array
1735
     */
1736
    public function getIndependentCategoriesWithStudentResult(
1737
        $categoryId,
1738
        $studentId,
1739
        $cats = []
1740
    ) {
1741
        $creator = api_is_allowed_to_edit() && !api_is_platform_admin() ? api_get_user_id() : null;
1742
1743
        $categories = self::load(
1744
            null,
1745
            $creator,
1746
            '0',
1747
            $categoryId,
1748
            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

1748
            /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1
Loading history...
1749
        );
1750
1751
        if (!empty($categories)) {
1752
            /** @var Category $category */
1753
            foreach ($categories as $category) {
1754
                if ($category->hasEvaluationsWithStudentResults($studentId)) {
1755
                    $cats[] = $category;
1756
                }
1757
            }
1758
        }
1759
1760
        return $cats;
1761
    }
1762
1763
    /**
1764
     * Return the session id (in any case, even if it's null or 0)
1765
     * @return  int Session id (can be null)
1766
     */
1767
    public function get_session_id()
1768
    {
1769
        return $this->session_id;
1770
    }
1771
1772
    /**
1773
     * Get appropriate subcategories visible for the user (and optionally the course and session)
1774
     * @param int    $studentId student id (default: all students)
1775
     * @param string $course_code Course code (optional)
1776
     * @param int    $session_id Session ID (optional)
1777
     * @param bool   $order
1778
1779
     * @return array Array of subcategories
1780
     */
1781
    public function get_subcategories(
1782
        $studentId = null,
1783
        $course_code = null,
1784
        $session_id = null,
1785
        $order = null
1786
    ) {
1787
        if (!empty($session_id)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1788
            /*$tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1789
            $sql = 'SELECT id FROM '.$tbl_grade_categories. ' WHERE session_id = '.$session_id;
1790
            $result_session = Database::query($sql);
1791
            if (Database::num_rows($result_session) > 0) {
1792
                $data_session = Database::fetch_array($result_session);
1793
                $parent_id = $data_session['id'];
1794
                return self::load(null, null, null, $parent_id, null, null, $order);
1795
            }*/
1796
        }
1797
1798
        // 1 student
1799
        if (isset($studentId)) {
1800
            // Special case: this is the root
1801
            if ($this->id == 0) {
1802
                return self::get_root_categories_for_student($studentId, $course_code, $session_id);
0 ignored issues
show
Bug Best Practice introduced by
The method Category::get_root_categories_for_student() 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

1802
                return self::/** @scrutinizer ignore-call */ get_root_categories_for_student($studentId, $course_code, $session_id);
Loading history...
1803
            } else {
1804
                return self::load(
1805
                    null,
1806
                    null,
1807
                    $course_code,
1808
                    $this->id,
1809
                    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

1809
                    /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1,
Loading history...
1810
                    $session_id,
1811
                    $order
1812
                );
1813
            }
1814
        } else {
1815
            // All students
1816
            // Course admin
1817
            if (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1818
                // root
1819
                if ($this->id == 0) {
1820
                    return $this->get_root_categories_for_teacher(
1821
                        api_get_user_id(),
1822
                        $course_code,
1823
                        $session_id,
1824
                        false
1825
                    );
1826
                    // inside a course
1827
                } elseif (!empty($this->course_code)) {
1828
                    return self::load(
1829
                        null,
1830
                        null,
1831
                        $this->course_code,
1832
                        $this->id,
1833
                        null,
1834
                        $session_id,
1835
                        $order
1836
                    );
1837
                } elseif (!empty($course_code)) {
1838
                    return self::load(
1839
                        null,
1840
                        null,
1841
                        $course_code,
1842
                        $this->id,
1843
                        null,
1844
                        $session_id,
1845
                        $order
1846
                    );
1847
                    // course independent
1848
                } else {
1849
                    return self::load(
1850
                        null,
1851
                        api_get_user_id(),
1852
                        0,
1853
                        $this->id,
1854
                        null
1855
                    );
1856
                }
1857
            } elseif (api_is_platform_admin()) {
1858
                // platform admin
1859
                // we explicitly avoid listing subcats from another session
1860
                return self::load(
1861
                    null,
1862
                    null,
1863
                    $course_code,
1864
                    $this->id,
1865
                    null,
1866
                    $session_id,
1867
                    $order
1868
                );
1869
            }
1870
        }
1871
1872
        return [];
1873
    }
1874
1875
    /**
1876
     * Get appropriate evaluations visible for the user
1877
     * @param int $studentId student id (default: all students)
1878
     * @param boolean $recursive process subcategories (default: no recursion)
1879
     * @param string $course_code
1880
     * @param int $sessionId
1881
     *
1882
     * @return array
1883
     */
1884
    public function get_evaluations(
1885
        $studentId = null,
1886
        $recursive = false,
1887
        $course_code = '',
1888
        $sessionId = 0
1889
    ) {
1890
        $evals = [];
1891
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1892
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1893
1894
        // 1 student
1895
        if (isset($studentId) && !empty($studentId)) {
1896
            // Special case: this is the root
1897
            if ($this->id == 0) {
1898
                $evals = Evaluation::get_evaluations_with_result_for_student(
1899
                    0,
1900
                    $studentId
1901
                );
1902
            } else {
1903
                $evals = Evaluation::load(
1904
                    null,
1905
                    null,
1906
                    $course_code,
1907
                    $this->id,
1908
                    api_is_allowed_to_edit() ? null : 1
1909
                );
1910
            }
1911
        } else {
1912
            // All students
1913
            // course admin
1914
            if ((api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) &&
1915
                !api_is_platform_admin()
1916
            ) {
1917
                // root
1918
                if ($this->id == 0) {
1919
                    $evals = Evaluation::load(
1920
                        null,
1921
                        api_get_user_id(),
1922
                        null,
1923
                        $this->id,
1924
                        null
1925
                    );
1926
                } elseif (isset($this->course_code) &&
1927
                    !empty($this->course_code)
1928
                ) {
1929
                    // inside a course
1930
                    $evals = Evaluation::load(
1931
                        null,
1932
                        null,
1933
                        $course_code,
1934
                        $this->id,
1935
                        null
1936
                    );
1937
                } else {
1938
                    // course independent
1939
                    $evals = Evaluation::load(
1940
                        null,
1941
                        api_get_user_id(),
1942
                        null,
1943
                        $this->id,
1944
                        null
1945
                    );
1946
                }
1947
            } else {
1948
                $evals = Evaluation::load(
1949
                    null,
1950
                    null,
1951
                    $course_code,
1952
                    $this->id,
1953
                    null
1954
                );
1955
            }
1956
        }
1957
1958
        if ($recursive) {
1959
            $subcats = $this->get_subcategories(
1960
                $studentId,
1961
                $course_code,
1962
                $sessionId
1963
            );
1964
1965
            if (!empty($subcats)) {
1966
                foreach ($subcats as $subcat) {
1967
                    $subevals = $subcat->get_evaluations(
1968
                        $studentId,
1969
                        true,
1970
                        $course_code
1971
                    );
1972
                    $evals = array_merge($evals, $subevals);
1973
                }
1974
            }
1975
        }
1976
1977
        return $evals;
1978
    }
1979
1980
    /**
1981
     * Get appropriate links visible for the user
1982
     * @param int $studentId student id (default: all students)
1983
     * @param boolean $recursive process subcategories (default: no recursion)
1984
     * @param string $course_code
1985
     * @param int $sessionId
1986
     *
1987
     * @return array
1988
     */
1989
    public function get_links(
1990
        $studentId = null,
1991
        $recursive = false,
1992
        $course_code = '',
1993
        $sessionId = 0
1994
    ) {
1995
        $links = [];
1996
1997
        $course_code = empty($course_code) ? $this->get_course_code() : $course_code;
1998
        $sessionId = empty($sessionId) ? $this->get_session_id() : $sessionId;
1999
2000
        // no links in root or course independent categories
2001
        if ($this->id == 0) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
2002
        } elseif (isset($studentId)) {
2003
            // 1 student $studentId
2004
            $links = LinkFactory::load(
2005
                null,
2006
                null,
2007
                null,
2008
                null,
2009
                empty($this->course_code) ? null : $course_code,
2010
                $this->id,
2011
                api_is_allowed_to_edit() ? null : 1
2012
            );
2013
        } else {
2014
            // All students -> only for course/platform admin
2015
            $links = LinkFactory::load(
2016
                null,
2017
                null,
2018
                null,
2019
                null,
2020
                empty($this->course_code) ? null : $this->course_code,
2021
                $this->id,
2022
                null
2023
            );
2024
        }
2025
2026
        if ($recursive) {
2027
            $subcats = $this->get_subcategories(
2028
                $studentId,
2029
                $course_code,
2030
                $sessionId
2031
            );
2032
            if (!empty($subcats)) {
2033
                /** @var Category $subcat */
2034
                foreach ($subcats as $subcat) {
2035
                    $sublinks = $subcat->get_links(
2036
                        $studentId,
2037
                        false,
2038
                        $course_code,
2039
                        $sessionId
2040
                    );
2041
                    $links = array_merge($links, $sublinks);
2042
                }
2043
            }
2044
        }
2045
2046
        return $links;
2047
    }
2048
2049
    /**
2050
     * Get all the categories from with the same given direct parent
2051
     * @param int $catId Category parent ID
2052
     * @return array Array of Category objects
2053
     */
2054
    public function getCategories($catId)
2055
    {
2056
        $tblGradeCategories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
2057
        $sql = 'SELECT * FROM '.$tblGradeCategories.'
2058
                WHERE parent_id = '.intval($catId);
2059
2060
        $result = Database::query($sql);
2061
        $categories = self::create_category_objects_from_sql_result($result);
2062
2063
        return $categories;
2064
    }
2065
2066
    /**
2067
     * Gets the type for the current object
2068
     * @return string 'C' to represent "Category" object type
2069
     */
2070
    public function get_item_type()
2071
    {
2072
        return 'C';
2073
    }
2074
2075
    /**
2076
     * @param array $skills
2077
     */
2078
    public function set_skills($skills)
2079
    {
2080
        $this->skills = $skills;
2081
    }
2082
2083
    /**
2084
     * @return null
2085
     */
2086
    public function get_date()
2087
    {
2088
        return null;
2089
    }
2090
2091
    /**
2092
     * @return string
2093
     */
2094
    public function get_icon_name()
2095
    {
2096
        return 'cat';
2097
    }
2098
2099
    /**
2100
     * Find category by name
2101
     * @param string $name_mask search string
2102
     * @return array category objects matching the search criterium
2103
     */
2104
    public function find_category($name_mask, $allcat)
2105
    {
2106
        $categories = [];
2107
        foreach ($allcat as $search_cat) {
2108
            if (!(strpos(strtolower($search_cat->get_name()), strtolower($name_mask)) === false)) {
2109
                $categories[] = $search_cat;
2110
            }
2111
        }
2112
2113
        return $categories;
2114
    }
2115
2116
    /**
2117
     * This function, locks a category , only one who can unlock it is
2118
     * the platform administrator.
2119
     * @param int locked 1 or unlocked 0
2120
2121
     * @return boolean|null
2122
     * */
2123
    public function lock($locked)
2124
    {
2125
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
2126
        $sql = "UPDATE $table SET locked = '".intval($locked)."'
2127
                WHERE id='".intval($this->id)."'";
2128
        Database::query($sql);
2129
    }
2130
2131
    /**
2132
     * @param $locked
2133
     */
2134
    public function lockAllItems($locked)
2135
    {
2136
        if (api_get_setting('gradebook_locking_enabled') == 'true') {
2137
            $this->lock($locked);
2138
            $evals_to_lock = $this->get_evaluations();
2139
            if (!empty($evals_to_lock)) {
2140
                foreach ($evals_to_lock as $item) {
2141
                    $item->lock($locked);
2142
                }
2143
            }
2144
2145
            $link_to_lock = $this->get_links();
2146
            if (!empty($link_to_lock)) {
2147
                foreach ($link_to_lock as $item) {
2148
                    $item->lock($locked);
2149
                }
2150
            }
2151
2152
            $event_type = LOG_GRADEBOOK_UNLOCKED;
2153
            if ($locked == 1) {
2154
                $event_type = LOG_GRADEBOOK_LOCKED;
2155
            }
2156
            Event::addEvent($event_type, LOG_GRADEBOOK_ID, $this->id);
2157
        }
2158
    }
2159
2160
    /**
2161
     * Generates a certificate for this user if everything matches
2162
     * @param int $category_id gradebook id
2163
     * @param int $user_id
2164
     * @param bool $sendNotification
2165
     *
2166
     * @return array
2167
     */
2168
    public static function generateUserCertificate(
2169
        $category_id,
2170
        $user_id,
2171
        $sendNotification = false
2172
    ) {
2173
        // Generating the total score for a course
2174
        $cats_course = self::load(
2175
            $category_id,
2176
            null,
2177
            null,
2178
            null,
2179
            null,
2180
            null,
2181
            false
2182
        );
2183
2184
        /** @var Category $category */
2185
        $category = $cats_course[0];
2186
2187
        if (empty($category)) {
2188
            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...
2189
        }
2190
2191
        // Block certification links depending gradebook configuration (generate certifications)
2192
        if (empty($category->getGenerateCertificates())) {
2193
            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...
2194
        }
2195
2196
        $sessionId = $category->get_session_id();
2197
        $courseCode = $category->get_course_code();
2198
        $courseInfo = api_get_course_info($courseCode);
2199
        $courseId = $courseInfo['real_id'];
2200
2201
        //@todo move these in a function
2202
        $sum_categories_weight_array = [];
2203
        if (isset($cats_course) && !empty($cats_course)) {
2204
            $categories = self::load(null, null, null, $category_id);
2205
            if (!empty($categories)) {
2206
                foreach ($categories as $subCategory) {
2207
                    $sum_categories_weight_array[$subCategory->get_id()] = $subCategory->get_weight();
2208
                }
2209
            } else {
2210
                $sum_categories_weight_array[$category_id] = $cats_course[0]->get_weight();
2211
            }
2212
        }
2213
2214
        $cattotal = self::load($category_id);
2215
        $scoretotal = $cattotal[0]->calc_score($user_id);
2216
2217
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2218
        // file load this variable as a global
2219
        $scoredisplay = ScoreDisplay::instance();
2220
        $my_score_in_gradebook = $scoredisplay->display_score(
2221
            $scoretotal,
2222
            SCORE_SIMPLE
2223
        );
2224
        $userFinishedCourse = self::userFinishedCourse(
2225
            $user_id,
2226
            $cats_course[0],
2227
            true
2228
        );
2229
2230
        if (!$userFinishedCourse) {
2231
            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...
2232
        }
2233
2234
        $skillToolEnabled = Skill::hasAccessToUserSkill(
2235
            api_get_user_id(),
2236
            $user_id
2237
        );
2238
        $userHasSkills = false;
2239
2240
        if ($skillToolEnabled) {
2241
            $skill = new Skill();
2242
            $skill->addSkillToUser(
2243
                $user_id,
2244
                $category_id,
2245
                $courseId,
2246
                $sessionId
2247
            );
2248
2249
            $objSkillRelUser = new SkillRelUser();
2250
            $userSkills = $objSkillRelUser->getUserSkills(
2251
                $user_id,
2252
                $courseId,
2253
                $sessionId
2254
            );
2255
            $userHasSkills = !empty($userSkills);
2256
2257
            if (!$category->getGenerateCertificates() && $userHasSkills) {
2258
                return [
2259
                    'badge_link' => Display::toolbarButton(
2260
                        get_lang('ExportBadges'),
2261
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2262
                        'external-link'
2263
                    ),
2264
                ];
2265
            }
2266
        }
2267
2268
        $my_certificate = GradebookUtils::get_certificate_by_user_id(
2269
            $cats_course[0]->get_id(),
2270
            $user_id
2271
        );
2272
2273
        if (empty($my_certificate)) {
2274
            GradebookUtils::registerUserInfoAboutCertificate(
2275
                $category_id,
2276
                $user_id,
2277
                $my_score_in_gradebook,
2278
                api_get_utc_datetime()
2279
            );
2280
            $my_certificate = GradebookUtils::get_certificate_by_user_id(
2281
                $cats_course[0]->get_id(),
2282
                $user_id
2283
            );
2284
        }
2285
2286
        $html = [];
2287
        if (!empty($my_certificate)) {
2288
            $certificate_obj = new Certificate(
2289
                $my_certificate['id'],
2290
                0,
2291
                $sendNotification
2292
            );
2293
2294
            $fileWasGenerated = $certificate_obj->isHtmlFileGenerated();
2295
2296
            if (!empty($fileWasGenerated)) {
2297
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'];
2298
                $certificates = Display::toolbarButton(
2299
                    get_lang('DisplayCertificate'),
2300
                    $url,
2301
                    'eye',
2302
                    'primary'
2303
                );
2304
2305
                $exportToPDF = Display::url(
2306
                    Display::return_icon(
2307
                        'pdf.png',
2308
                        get_lang('ExportToPDF'),
2309
                        [],
2310
                        ICON_SIZE_MEDIUM
2311
                    ),
2312
                    "$url&action=export"
2313
                );
2314
2315
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2316
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2317
                if ($hideExportLink === 'true' || (api_is_student() && $hideExportLinkStudent === 'true')) {
2318
                    $exportToPDF = null;
2319
                }
2320
2321
                $html = [
2322
                    'certificate_link' => $certificates,
2323
                    'pdf_link' => $exportToPDF,
2324
                    'pdf_url' => "$url&action=export",
2325
                ];
2326
2327
                if ($skillToolEnabled && $userHasSkills) {
2328
                    $html['badge_link'] = Display::toolbarButton(
2329
                        get_lang('ExportBadges'),
2330
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2331
                        'external-link'
2332
                    );
2333
                }
2334
            }
2335
2336
            return $html;
2337
        }
2338
    }
2339
2340
    /**
2341
     * @param int $catId
2342
     * @param array $userList
2343
     */
2344
    public static function generateCertificatesInUserList($catId, $userList)
2345
    {
2346
        if (!empty($userList)) {
2347
            foreach ($userList as $userInfo) {
2348
                self::generateUserCertificate($catId, $userInfo['user_id']);
2349
            }
2350
        }
2351
    }
2352
2353
    /**
2354
     * @param int $catId
2355
     * @param array $userList
2356
     */
2357
    public static function exportAllCertificates(
2358
        $catId,
2359
        $userList = []
2360
    ) {
2361
        $orientation = api_get_configuration_value('certificate_pdf_orientation');
2362
2363
        $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...
2364
        if (!empty($orientation)) {
2365
            $params['orientation'] = $orientation;
2366
        }
2367
2368
        $params['left'] = 0;
2369
        $params['right'] = 0;
2370
        $params['top'] = 0;
2371
        $params['bottom'] = 0;
2372
        $page_format = $params['orientation'] == 'landscape' ? 'A4-L' : 'A4';
2373
        $pdf = new PDF($page_format, $params['orientation'], $params);
2374
2375
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2376
        $certificate_path_list = [];
2377
2378
        if (!empty($certificate_list)) {
2379
            foreach ($certificate_list as $index=>$value) {
2380
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2381
                    $value['user_id'],
2382
                    $catId
2383
                );
2384
                foreach ($list_certificate as $value_certificate) {
2385
                    $certificate_obj = new Certificate($value_certificate['id']);
2386
                    $certificate_obj->generate(['hide_print_button' => true]);
2387
                    if ($certificate_obj->isHtmlFileGenerated()) {
2388
                        $certificate_path_list[] = $certificate_obj->html_file;
2389
                    }
2390
                }
2391
            }
2392
        }
2393
2394
        if (!empty($certificate_path_list)) {
2395
            // Print certificates (without the common header/footer/watermark
2396
            //  stuff) and return as one multiple-pages PDF
2397
            $pdf->html_to_pdf(
2398
                $certificate_path_list,
2399
                get_lang('Certificates'),
2400
                null,
2401
                false,
2402
                false
2403
            );
2404
        }
2405
    }
2406
2407
    /**
2408
     * @param int $catId
2409
     */
2410
    public static function deleteAllCertificates($catId)
2411
    {
2412
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2413
        if (!empty($certificate_list)) {
2414
            foreach ($certificate_list as $index => $value) {
2415
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2416
                    $value['user_id'],
2417
                    $catId
2418
                );
2419
                foreach ($list_certificate as $value_certificate) {
2420
                    $certificate_obj = new Certificate($value_certificate['id']);
2421
                    $certificate_obj->delete(true);
2422
                }
2423
            }
2424
        }
2425
    }
2426
2427
    /**
2428
     * Check whether a user has finished a course by its gradebook
2429
     * @param int $userId The user ID
2430
     * @param \Category $category Optional. The gradebook category.
2431
     *         To check by the gradebook category
2432
     * @param boolean $recalculateScore Whether recalculate the score
2433
     * @return boolean
2434
     */
2435
    public static function userFinishedCourse(
2436
        $userId,
2437
        \Category $category,
2438
        $recalculateScore = false
2439
    ) {
2440
        if (empty($category)) {
2441
            return false;
2442
        }
2443
2444
        $currentScore = self::getCurrentScore(
2445
            $userId,
2446
            $category,
2447
            $recalculateScore
2448
        );
2449
2450
        $minCertificateScore = $category->getCertificateMinScore();
2451
        $passedCourse = $currentScore >= $minCertificateScore;
2452
2453
        return $passedCourse;
2454
    }
2455
2456
    /**
2457
     * Get the current score (as percentage) on a gradebook category for a user
2458
     * @param int $userId The user id
2459
     * @param Category $category The gradebook category
2460
     * @param bool $recalculate
2461
     *
2462
     * @return float The score
2463
     */
2464
    public static function getCurrentScore(
2465
        $userId,
2466
        $category,
2467
        $recalculate = false
2468
    ) {
2469
        if (empty($category)) {
2470
            return 0;
2471
        }
2472
2473
        if ($recalculate) {
2474
            return self::calculateCurrentScore(
2475
                $userId,
2476
                $category
2477
            );
2478
        }
2479
2480
        $resultData = Database::select(
2481
            '*',
2482
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2483
            [
2484
                'where' => [
2485
                    'category_id = ? AND user_id = ?' => [$category->get_id(), $userId],
2486
                ],
2487
                'order' => 'registered_at DESC',
2488
                'limit' => '1',
2489
            ],
2490
            'first'
2491
        );
2492
2493
        if (empty($resultData)) {
2494
            return 0;
2495
        }
2496
2497
        return $resultData['score'];
2498
    }
2499
2500
    /**
2501
     * Calculate the current score on a gradebook category for a user
2502
     * @param int $userId The user id
2503
     * @param Category $category The gradebook category
2504
     * @return float The score
2505
     */
2506
    private static function calculateCurrentScore(
2507
        $userId,
2508
        $category
2509
    ) {
2510
        if (empty($category)) {
2511
            return 0;
2512
        }
2513
        $courseEvaluations = $category->get_evaluations(
2514
            $userId,
2515
            true
2516
        );
2517
        $courseLinks = $category->get_links($userId, true);
2518
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2519
        $categoryScore = 0;
2520
        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...
2521
            $item = $evaluationsAndLinks[$i];
2522
            $score = $item->calc_score($userId);
2523
            $itemValue = 0;
2524
            if (!empty($score)) {
2525
                $divider = $score[1] == 0 ? 1 : $score[1];
2526
                $itemValue = $score[0] / $divider * $item->get_weight();
2527
            }
2528
2529
            $categoryScore += $itemValue;
2530
        }
2531
2532
        return api_float_val($categoryScore);
2533
    }
2534
2535
    /**
2536
     * Register the current score for a user on a category gradebook
2537
     * @param float $score The achieved score
2538
     * @param int $userId The user id
2539
     * @param int $categoryId The gradebook category
2540
     * @return int The insert id
2541
     */
2542
    public static function registerCurrentScore($score, $userId, $categoryId)
2543
    {
2544
        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...
2545
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2546
            [
2547
                'category_id' => intval($categoryId),
2548
                'user_id' => intval($userId),
2549
                'score' => api_float_val($score),
2550
                'registered_at' => api_get_utc_datetime(),
2551
            ]
2552
        );
2553
    }
2554
2555
    /**
2556
     * @return array
2557
     */
2558
    public function getStudentList()
2559
    {
2560
        return $this->studentList;
2561
    }
2562
2563
    /**
2564
     * @param array $list
2565
     */
2566
    public function setStudentList($list)
2567
    {
2568
        $this->studentList = $list;
2569
    }
2570
2571
    /**
2572
     * @return string
2573
     */
2574
    public static function getUrl()
2575
    {
2576
        $url = Session::read('gradebook_dest');
2577
        if (empty($url)) {
2578
            // We guess the link
2579
            $courseInfo = api_get_course_info();
2580
            if (!empty($courseInfo)) {
2581
                return api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2582
            } else {
2583
                return api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2584
            }
2585
        }
2586
2587
        return $url;
2588
    }
2589
2590
    /**
2591
     * Destination is index.php or gradebook.php
2592
     * @param string $url
2593
     */
2594
    public static function setUrl($url)
2595
    {
2596
        switch ($url) {
2597
            case 'gradebook.php':
2598
                $url = api_get_path(WEB_CODE_PATH).'gradebook/gradebook.php?';
2599
                break;
2600
            case 'index.php':
2601
                $url = api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq().'&';
2602
                break;
2603
        }
2604
        Session::write('gradebook_dest', $url);
2605
    }
2606
2607
    /**
2608
     * @return int
2609
     */
2610
    public function getGradeBooksToValidateInDependence()
2611
    {
2612
        return $this->gradeBooksToValidateInDependence;
2613
    }
2614
2615
    /**
2616
     * @param int $value
2617
     * @return Category
2618
     */
2619
    public function setGradeBooksToValidateInDependence($value)
2620
    {
2621
        $this->gradeBooksToValidateInDependence = $value;
2622
2623
        return $this;
2624
    }
2625
}
2626