Passed
Push — master ( ccb5fd...57fe34 )
by Julito
13:03
created

Category::create_root_category()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

Loading history...
552
        $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

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

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

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

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

668
        $gradebookCategory->/** @scrutinizer ignore-call */ 
669
                            setUserId($this->user_id);

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

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

Loading history...
669
        $gradebookCategory->setCourse($course);
670
        //$gradebookCategory->setCourseCode($this->course_code);
671
        $gradebookCategory->setParentId($this->parent);
672
        $gradebookCategory->setWeight($this->weight);
673
        $gradebookCategory->setVisible($this->visible);
674
        $gradebookCategory->setCertifMinScore($this->certificate_min_score);
675
        $gradebookCategory->setGenerateCertificates(
676
            $this->generateCertificates
677
        );
678
        $gradebookCategory->setGradeModelId($this->grade_model_id);
679
        $gradebookCategory->setIsRequirement($this->isRequirement);
680
681
        $em->merge($gradebookCategory);
682
        $em->flush();
683
684
        if (!empty($this->id)) {
685
            $parent_id = $this->get_parent_id();
686
            $grade_model_id = $this->get_grade_model_id();
687
            if ($parent_id == 0) {
688
                if (isset($grade_model_id) &&
689
                    !empty($grade_model_id) &&
690
                    $grade_model_id != '-1'
691
                ) {
692
                    $obj = new GradeModel();
693
                    $components = $obj->get_components($grade_model_id);
694
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
695
                    $default_weight = 100;
696
                    if (isset($default_weight_setting)) {
697
                        $default_weight = $default_weight_setting;
698
                    }
699
                    $final_weight = $this->get_weight();
700
                    if (!empty($final_weight)) {
701
                        $default_weight = $this->get_weight();
702
                    }
703
                    foreach ($components as $component) {
704
                        $gradebook = new Gradebook();
705
                        $params = [];
706
                        $params['name'] = $component['acronym'];
707
                        $params['description'] = $component['title'];
708
                        $params['user_id'] = api_get_user_id();
709
                        $params['parent_id'] = $this->id;
710
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
711
                        $params['session_id'] = api_get_session_id();
712
                        $params['course_code'] = $this->get_course_code();
713
                        $gradebook->save($params);
714
                    }
715
                }
716
            }
717
        }
718
719
        $gradebook = new Gradebook();
720
        $gradebook->updateSkillsToGradeBook(
721
            $this->id,
722
            $this->get_skills(false),
723
            true
724
        );
725
    }
726
727
    /**
728
     * Update link weights see #5168.
729
     *
730
     * @param type $new_weight
731
     */
732
    public function updateChildrenWeight($new_weight)
733
    {
734
        $links = $this->get_links();
735
        $old_weight = $this->get_weight();
736
737
        if (!empty($links)) {
738
            foreach ($links as $link_item) {
739
                if (isset($link_item)) {
740
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
741
                    $link_item->set_weight($new_item_weight);
742
                    $link_item->save();
743
                }
744
            }
745
        }
746
    }
747
748
    /**
749
     * Delete this evaluation from the database.
750
     */
751
    public function delete()
752
    {
753
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
754
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
755
        Database::query($sql);
756
    }
757
758
    /**
759
     * Not delete this category from the database,when visible=3 is category eliminated.
760
     *
761
     * @param int $courseId
762
     */
763
    public function update_category_delete($courseId)
764
    {
765
        $tbl_grade_categories = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
766
        $sql = 'UPDATE '.$tbl_grade_categories.' SET visible=3
767
                WHERE c_id ="'.intval($courseId).'"';
768
        Database::query($sql);
769
    }
770
771
    /**
772
     * Delete the gradebook categories from a course, including course sessions.
773
     *
774
     * @param \Chamilo\CoreBundle\Entity\Course $course
775
     */
776
    public static function deleteFromCourse($course)
777
    {
778
        $em = Database::getManager();
779
        $categories = $em
780
            ->createQuery(
781
                'SELECT DISTINCT gc.sessionId
782
                FROM ChamiloCoreBundle:GradebookCategory gc WHERE gc.course = :course'
783
            )
784
            ->setParameter('course', $course)
785
            ->getResult();
786
787
        foreach ($categories as $category) {
788
            $cats = self::load(
789
                null,
790
                null,
791
                $course->getCode(),
792
                null,
793
                null,
794
                (int) $category['sessionId']
795
            );
796
797
            if (!empty($cats)) {
798
                /** @var self $cat */
799
                foreach ($cats as $cat) {
800
                    $cat->delete_all();
801
                }
802
            }
803
        }
804
    }
805
806
    /**
807
     * @param int $course_id
808
     *
809
     * @return bool|string
810
     */
811
    public function show_message_resource_delete($course_id)
812
    {
813
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
814
        $sql = 'SELECT count(*) AS num 
815
                FROM '.$table.'
816
                WHERE
817
                    c_id = "'.Database::escape_string($course_id).'" AND
818
                    visible = 3';
819
        $res = Database::query($sql);
820
        $option = Database::fetch_array($res, 'ASSOC');
821
        if ($option['num'] >= 1) {
822
            return '&nbsp;&nbsp;<span class="resource-deleted">(&nbsp;'.get_lang('ResourceDeleted').'&nbsp;)</span>';
823
        } else {
824
            return false;
825
        }
826
    }
827
828
    /**
829
     * Shows all information of an category.
830
     *
831
     * @param int $categoryId
832
     *
833
     * @return array
834
     */
835
    public function showAllCategoryInfo($categoryId = 0)
836
    {
837
        $categoryId = (int) $categoryId;
838
        if (empty($categoryId)) {
839
            return [];
840
        }
841
842
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
843
        $sql = 'SELECT * FROM '.$table.'
844
                WHERE id = '.$categoryId;
845
        $result = Database::query($sql);
846
        $row = Database::fetch_array($result, 'ASSOC');
847
848
        return $row;
849
    }
850
851
    /**
852
     * Check if a category name (with the same parent category) already exists.
853
     *
854
     * @param string $name   name to check (if not given, the name property of this object will be checked)
855
     * @param int    $parent parent category
856
     *
857
     * @return bool
858
     */
859
    public function does_name_exist($name, $parent)
860
    {
861
        if (!isset($name)) {
862
            $name = $this->name;
863
            $parent = $this->parent;
864
        }
865
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
866
        $sql = "SELECT count(id) AS number
867
                FROM $table
868
                WHERE name = '".Database::escape_string($name)."'";
869
870
        if (api_is_allowed_to_edit()) {
871
            $parent = self::load($parent);
872
            $code = $parent[0]->get_course_code();
873
            $courseInfo = api_get_course_info($code);
874
            $courseId = $courseInfo['real_id'];
875
            if (isset($code) && $code != '0') {
876
                $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
877
                $sql .= ' AND user_id IN (
878
                            SELECT user_id FROM '.$main_course_user_table.'
879
                            WHERE c_id = '.$courseId.' AND status = '.COURSEMANAGER.'
880
                        )';
881
            } else {
882
                $sql .= ' AND user_id = '.api_get_user_id();
883
            }
884
        } else {
885
            $sql .= ' AND user_id = '.api_get_user_id();
886
        }
887
888
        if (!isset($parent)) {
889
            $sql .= ' AND parent_id is null';
890
        } else {
891
            $sql .= ' AND parent_id = '.intval($parent);
892
        }
893
        $result = Database::query($sql);
894
        $number = Database::fetch_row($result);
895
896
        return $number[0] != 0;
897
    }
898
899
    /**
900
     * Checks if the certificate is available for the given user in this category.
901
     *
902
     * @param int $user_id User ID
903
     *
904
     * @return bool True if conditions match, false if fails
905
     */
906
    public function is_certificate_available($user_id)
907
    {
908
        $score = $this->calc_score(
909
            $user_id,
910
            null,
911
            $this->course_code,
912
            $this->session_id
913
        );
914
915
        if (isset($score) && isset($score[0])) {
916
            // Get a percentage score to compare to minimum certificate score
917
            // $certification_score = $score[0] / $score[1] * 100;
918
            // Get real score not a percentage.
919
            $certification_score = $score[0];
920
            if ($certification_score >= $this->certificate_min_score) {
921
                return true;
922
            }
923
        }
924
925
        return false;
926
    }
927
928
    /**
929
     * Is this category a course ?
930
     * A category is a course if it has a course code and no parent category.
931
     */
932
    public function is_course()
933
    {
934
        return isset($this->course_code) && !empty($this->course_code)
935
            && (!isset($this->parent) || $this->parent == 0);
936
    }
937
938
    /**
939
     * Calculate the score of this category.
940
     *
941
     * @param int    $stud_id     student id (default: all students - then the average is returned)
942
     * @param        $type
943
     * @param string $course_code
944
     * @param int    $session_id
945
     *
946
     * @return array (score sum, weight sum)
947
     *               or null if no scores available
948
     */
949
    public function calc_score(
950
        $stud_id = null,
951
        $type = null,
952
        $course_code = '',
953
        $session_id = null
954
    ) {
955
        $key = 'category:'.$this->id.'student:'.(int) $stud_id.'type:'.$type.'course:'.$course_code.'session:'.(int) $session_id;
956
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
957
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
958
959
        if ($cacheAvailable) {
960
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
961
            if ($cacheDriver->contains($key)) {
962
                return $cacheDriver->fetch($key);
963
            }
964
        }
965
        // Classic
966
        if (!empty($stud_id) && $type == '') {
967
            if (!empty($course_code)) {
968
                $cats = $this->get_subcategories(
969
                    $stud_id,
970
                    $course_code,
971
                    $session_id
972
                );
973
                $evals = $this->get_evaluations($stud_id, false, $course_code);
974
                $links = $this->get_links($stud_id, false, $course_code);
975
            } else {
976
                $cats = $this->get_subcategories($stud_id);
977
                $evals = $this->get_evaluations($stud_id);
978
                $links = $this->get_links($stud_id);
979
            }
980
981
            // Calculate score
982
            $count = 0;
983
            $ressum = 0;
984
            $weightsum = 0;
985
986
            if (!empty($cats)) {
987
                /** @var Category $cat */
988
                foreach ($cats as $cat) {
989
                    $cat->set_session_id($session_id);
990
                    $cat->set_course_code($course_code);
991
                    $cat->setStudentList($this->getStudentList());
992
                    $score = $cat->calc_score(
993
                        $stud_id,
994
                        null,
995
                        $course_code,
996
                        $session_id
997
                    );
998
999
                    $catweight = 0;
1000
                    if ($cat->get_weight() != 0) {
1001
                        $catweight = $cat->get_weight();
1002
                        $weightsum += $catweight;
1003
                    }
1004
1005
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
1006
                        $ressum += $score[0] / $score[1] * $catweight;
1007
                    }
1008
                }
1009
            }
1010
1011
            $students = [];
1012
            if (!empty($evals)) {
1013
                /** @var Evaluation $eval */
1014
                foreach ($evals as $eval) {
1015
                    $eval->setStudentList($this->getStudentList());
1016
                    $evalres = $eval->calc_score($stud_id);
1017
                    if (isset($evalres) && $eval->get_weight() != 0) {
1018
                        $evalweight = $eval->get_weight();
1019
                        $weightsum += $evalweight;
1020
                        if (!empty($evalres[1])) {
1021
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
1022
                        }
1023
                    } else {
1024
                        if ($eval->get_weight() != 0) {
1025
                            $evalweight = $eval->get_weight();
1026
                            $weightsum += $evalweight;
1027
                        }
1028
                    }
1029
                }
1030
            }
1031
1032
            if (!empty($links)) {
1033
                /** @var EvalLink|ExerciseLink $link */
1034
                foreach ($links as $link) {
1035
                    $link->setStudentList($this->getStudentList());
1036
1037
                    if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1038
                        $link->set_session_id($session_id);
1039
                    }
1040
1041
                    $linkres = $link->calc_score($stud_id, null);
1042
                    if (!empty($linkres) && $link->get_weight() != 0) {
1043
                        $students[$stud_id] = $linkres[0];
1044
                        $linkweight = $link->get_weight();
1045
                        $link_res_denom = $linkres[1] == 0 ? 1 : $linkres[1];
1046
                        $count++;
1047
                        $weightsum += $linkweight;
1048
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1049
                    } else {
1050
                        // Adding if result does not exists
1051
                        if ($link->get_weight() != 0) {
1052
                            $linkweight = $link->get_weight();
1053
                            $weightsum += $linkweight;
1054
                        }
1055
                    }
1056
                }
1057
            }
1058
        } else {
1059
            if (!empty($course_code)) {
1060
                $cats = $this->get_subcategories(
1061
                    null,
1062
                    $course_code,
1063
                    $session_id
1064
                );
1065
                $evals = $this->get_evaluations(null, false, $course_code);
1066
                $links = $this->get_links(null, false, $course_code);
1067
            } else {
1068
                $cats = $this->get_subcategories(null);
1069
                $evals = $this->get_evaluations(null);
1070
                $links = $this->get_links(null);
1071
            }
1072
1073
            // Calculate score
1074
            $count = 0;
1075
            $ressum = 0;
1076
            $weightsum = 0;
1077
            $bestResult = 0;
1078
1079
            if (!empty($cats)) {
1080
                /** @var Category $cat */
1081
                foreach ($cats as $cat) {
1082
                    $cat->setStudentList($this->getStudentList());
1083
                    $score = $cat->calc_score(
1084
                        null,
1085
                        $type,
1086
                        $course_code,
1087
                        $session_id
1088
                    );
1089
1090
                    $catweight = 0;
1091
                    if ($cat->get_weight() != 0) {
1092
                        $catweight = $cat->get_weight();
1093
                        $weightsum += $catweight;
1094
                    }
1095
1096
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
1097
                        $ressum += $score[0] / $score[1] * $catweight;
1098
1099
                        if ($ressum > $bestResult) {
1100
                            $bestResult = $ressum;
1101
                        }
1102
                    }
1103
                }
1104
            }
1105
1106
            if (!empty($evals)) {
1107
                /** @var Evaluation $eval */
1108
                foreach ($evals as $eval) {
1109
                    $evalres = $eval->calc_score(null, $type);
1110
                    $eval->setStudentList($this->getStudentList());
1111
1112
                    if (isset($evalres) && $eval->get_weight() != 0) {
1113
                        $evalweight = $eval->get_weight();
1114
                        $weightsum += $evalweight;
1115
                        $count++;
1116
                        if (!empty($evalres[1])) {
1117
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
1118
                        }
1119
1120
                        if ($ressum > $bestResult) {
1121
                            $bestResult = $ressum;
1122
                        }
1123
                    } else {
1124
                        if ($eval->get_weight() != 0) {
1125
                            $evalweight = $eval->get_weight();
1126
                            $weightsum += $evalweight;
1127
                        }
1128
                    }
1129
                }
1130
            }
1131
            if (!empty($links)) {
1132
                /** @var EvalLink|ExerciseLink $link */
1133
                foreach ($links as $link) {
1134
                    $link->setStudentList($this->getStudentList());
1135
1136
                    if ($session_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $session_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1137
                        $link->set_session_id($session_id);
1138
                    }
1139
1140
                    $linkres = $link->calc_score($stud_id, $type);
1141
                    if (!empty($linkres) && $link->get_weight() != 0) {
1142
                        $students[$stud_id] = $linkres[0];
1143
                        $linkweight = $link->get_weight();
1144
                        $link_res_denom = $linkres[1] == 0 ? 1 : $linkres[1];
1145
1146
                        $count++;
1147
                        $weightsum += $linkweight;
1148
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1149
1150
                        if ($ressum > $bestResult) {
1151
                            $bestResult = $ressum;
1152
                        }
1153
                    } else {
1154
                        // Adding if result does not exists
1155
                        if ($link->get_weight() != 0) {
1156
                            $linkweight = $link->get_weight();
1157
                            $weightsum += $linkweight;
1158
                        }
1159
                    }
1160
                }
1161
            }
1162
        }
1163
1164
        switch ($type) {
1165
            case 'best':
1166
                if (empty($bestResult)) {
1167
                    if ($cacheAvailable) {
1168
                        $cacheDriver->save($key, null);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cacheDriver does not seem to be defined for all execution paths leading up to this point.
Loading history...
1169
                    }
1170
1171
                    return null;
1172
                }
1173
                if ($cacheAvailable) {
1174
                    $cacheDriver->save($key, [$bestResult, $weightsum]);
1175
                }
1176
1177
                return [$bestResult, $weightsum];
1178
                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...
1179
            case 'average':
1180
                if (empty($ressum)) {
1181
                    if ($cacheAvailable) {
1182
                        $cacheDriver->save($key, null);
1183
                    }
1184
1185
                    return null;
1186
                }
1187
1188
                if ($cacheAvailable) {
1189
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1190
                }
1191
1192
                return [$ressum, $weightsum];
1193
                break;
1194
            case 'ranking':
1195
                // category ranking is calculated in gradebook_data_generator.class.php
1196
                // function get_data
1197
                return null;
1198
1199
                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...
1200
                break;
1201
            default:
1202
                if ($cacheAvailable) {
1203
                    $cacheDriver->save($key, [$ressum, $weightsum]);
1204
                }
1205
1206
                return [$ressum, $weightsum];
1207
                break;
1208
        }
1209
    }
1210
1211
    /**
1212
     * Delete this category and every subcategory, evaluation and result inside.
1213
     */
1214
    public function delete_all()
1215
    {
1216
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1217
        $evals = Evaluation::load(
1218
            null,
1219
            null,
1220
            $this->course_code,
1221
            $this->id,
1222
            null
1223
        );
1224
1225
        $links = LinkFactory::load(
1226
            null,
1227
            null,
1228
            null,
1229
            null,
1230
            $this->course_code,
1231
            $this->id,
1232
            null
1233
        );
1234
1235
        if (!empty($cats)) {
1236
            /** @var Category $cat */
1237
            foreach ($cats as $cat) {
1238
                $cat->delete_all();
1239
                $cat->delete();
1240
            }
1241
        }
1242
1243
        if (!empty($evals)) {
1244
            /** @var Evaluation $eval */
1245
            foreach ($evals as $eval) {
1246
                $eval->delete_with_results();
1247
            }
1248
        }
1249
1250
        if (!empty($links)) {
1251
            /** @var AbstractLink $link */
1252
            foreach ($links as $link) {
1253
                $link->delete();
1254
            }
1255
        }
1256
1257
        $this->delete();
1258
    }
1259
1260
    /**
1261
     * Return array of Category objects where a student is subscribed to.
1262
     *
1263
     * @param int    $stud_id
1264
     * @param string $course_code
1265
     * @param int    $session_id
1266
     *
1267
     * @return array
1268
     */
1269
    public function get_root_categories_for_student(
1270
        $stud_id,
1271
        $course_code = null,
1272
        $session_id = null
1273
    ) {
1274
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1275
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1276
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1277
1278
        $course_code = Database::escape_string($course_code);
1279
        $session_id = (int) $session_id;
1280
1281
        $sql = "SELECT * FROM $table WHERE parent_id = 0";
1282
1283
        if (!api_is_allowed_to_edit()) {
1284
            $sql .= ' AND visible = 1';
1285
            //proceed with checks on optional parameters course & session
1286
            if (!empty($course_code)) {
1287
                // TODO: considering it highly improbable that a user would get here
1288
                // if he doesn't have the rights to view this course and this
1289
                // session, we don't check his registration to these, but this
1290
                // could be an improvement
1291
                if (!empty($session_id)) {
1292
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1293
                } else {
1294
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1295
                }
1296
            } else {
1297
                //no optional parameter, proceed as usual
1298
                $sql .= ' AND course_code in
1299
                     (
1300
                        SELECT c.code
1301
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1302
                        ON (cu.c_id = c.id)
1303
                        WHERE cu.user_id = '.intval($stud_id).'
1304
                        AND cu.status = '.STUDENT.'
1305
                    )';
1306
            }
1307
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1308
            //proceed with checks on optional parameters course & session
1309
            if (!empty($course_code)) {
1310
                // TODO: considering it highly improbable that a user would get here
1311
                // if he doesn't have the rights to view this course and this
1312
                // session, we don't check his registration to these, but this
1313
                // could be an improvement
1314
                $sql .= " AND course_code  = '".$course_code."'";
1315
                if (!empty($session_id)) {
1316
                    $sql .= " AND session_id = ".$session_id;
1317
                } else {
1318
                    $sql .= 'AND session_id IS NULL OR session_id = 0';
1319
                }
1320
            } else {
1321
                $sql .= ' AND course_code IN
1322
                     (
1323
                        SELECT c.code
1324
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1325
                        ON (cu.c_id = c.id)
1326
                        WHERE
1327
                            cu.user_id = '.api_get_user_id().' AND
1328
                            cu.status = '.COURSEMANAGER.'
1329
                    )';
1330
            }
1331
        } elseif (api_is_platform_admin()) {
1332
            if (isset($session_id) && $session_id != 0) {
1333
                $sql .= ' AND session_id='.$session_id;
1334
            } else {
1335
                $sql .= ' AND coalesce(session_id,0)=0';
1336
            }
1337
        }
1338
        $result = Database::query($sql);
1339
        $cats = self::create_category_objects_from_sql_result($result);
1340
1341
        // course independent categories
1342
        if (empty($course_code)) {
1343
            $cats = $this->getIndependentCategoriesWithStudentResult(
1344
                0,
1345
                $stud_id,
1346
                $cats
1347
            );
1348
        }
1349
1350
        return $cats;
1351
    }
1352
1353
    /**
1354
     * Return array of Category objects where a teacher is admin for.
1355
     *
1356
     * @param int    $user_id     (to return everything, use 'null' here)
1357
     * @param string $course_code (optional)
1358
     * @param int    $session_id  (optional)
1359
     *
1360
     * @return array
1361
     */
1362
    public function get_root_categories_for_teacher(
1363
        $user_id,
1364
        $course_code = null,
1365
        $session_id = null
1366
    ) {
1367
        if ($user_id == null) {
1368
            return self::load(null, null, $course_code, 0, null, $session_id);
1369
        }
1370
1371
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1372
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1373
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1374
1375
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1376
                WHERE parent_id = 0 ';
1377
        if (!empty($course_code)) {
1378
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1379
            if (!empty($session_id)) {
1380
                $sql .= " AND session_id = ".(int) $session_id;
1381
            }
1382
        } else {
1383
            $sql .= ' AND course_code in
1384
                 (
1385
                    SELECT c.code
1386
                    FROM '.$main_course_user_table.' cu
1387
                    INNER JOIN '.$courseTable.' c
1388
                    ON (cu.c_id = c.id)
1389
                    WHERE user_id = '.intval($user_id).'
1390
                )';
1391
        }
1392
        $result = Database::query($sql);
1393
        $cats = self::create_category_objects_from_sql_result($result);
1394
        // course independent categories
1395
        if (isset($course_code)) {
1396
            $indcats = self::load(
1397
                null,
1398
                $user_id,
1399
                $course_code,
1400
                0,
1401
                null,
1402
                $session_id
1403
            );
1404
            $cats = array_merge($cats, $indcats);
1405
        }
1406
1407
        return $cats;
1408
    }
1409
1410
    /**
1411
     * Can this category be moved to somewhere else ?
1412
     * The root and courses cannot be moved.
1413
     *
1414
     * @return bool
1415
     */
1416
    public function is_movable()
1417
    {
1418
        return !(!isset($this->id) || $this->id == 0 || $this->is_course());
1419
    }
1420
1421
    /**
1422
     * Generate an array of possible categories where this category can be moved to.
1423
     * Notice: its own parent will be included in the list: it's up to the frontend
1424
     * to disable this element.
1425
     *
1426
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1427
     */
1428
    public function get_target_categories()
1429
    {
1430
        // the root or a course -> not movable
1431
        if (!$this->is_movable()) {
1432
            return null;
1433
        } else {
1434
            // otherwise:
1435
            // - course independent category
1436
            //   -> movable to root or other independent categories
1437
            // - category inside a course
1438
            //   -> movable to root, independent categories or categories inside the course
1439
            $user = api_is_platform_admin() ? null : api_get_user_id();
1440
            $targets = [];
1441
            $level = 0;
1442
1443
            $root = [0, get_lang('RootCat'), $level];
1444
            $targets[] = $root;
1445
1446
            if (isset($this->course_code) && !empty($this->course_code)) {
1447
                $crscats = self::load(null, null, $this->course_code, 0);
1448
                foreach ($crscats as $cat) {
1449
                    if ($this->can_be_moved_to_cat($cat)) {
1450
                        $targets[] = [
1451
                            $cat->get_id(),
1452
                            $cat->get_name(),
1453
                            $level + 1,
1454
                        ];
1455
                        $targets = $this->addTargetSubcategories(
1456
                            $targets,
1457
                            $level + 1,
1458
                            $cat->get_id()
1459
                        );
1460
                    }
1461
                }
1462
            }
1463
1464
            $indcats = self::load(null, $user, 0, 0);
1465
            foreach ($indcats as $cat) {
1466
                if ($this->can_be_moved_to_cat($cat)) {
1467
                    $targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
1468
                    $targets = $this->addTargetSubcategories(
1469
                        $targets,
1470
                        $level + 1,
1471
                        $cat->get_id()
1472
                    );
1473
                }
1474
            }
1475
1476
            return $targets;
1477
        }
1478
    }
1479
1480
    /**
1481
     * Move this category to the given category.
1482
     * If this category moves from inside a course to outside,
1483
     * its course code must be changed, as well as the course code
1484
     * of all underlying categories and evaluations. All links will
1485
     * be deleted as well !
1486
     */
1487
    public function move_to_cat($cat)
1488
    {
1489
        $this->set_parent_id($cat->get_id());
1490
        if ($this->get_course_code() != $cat->get_course_code()) {
1491
            $this->set_course_code($cat->get_course_code());
1492
            $this->applyCourseCodeToChildren();
1493
        }
1494
        $this->save();
1495
    }
1496
1497
    /**
1498
     * Generate an array of all categories the user can navigate to.
1499
     */
1500
    public function get_tree()
1501
    {
1502
        $targets = [];
1503
        $level = 0;
1504
        $root = [0, get_lang('RootCat'), $level];
1505
        $targets[] = $root;
1506
1507
        // course or platform admin
1508
        if (api_is_allowed_to_edit()) {
1509
            $user = api_is_platform_admin() ? null : api_get_user_id();
1510
            $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

1510
            /** @scrutinizer ignore-call */ 
1511
            $cats = self::get_root_categories_for_teacher($user);
Loading history...
1511
            foreach ($cats as $cat) {
1512
                $targets[] = [
1513
                    $cat->get_id(),
1514
                    $cat->get_name(),
1515
                    $level + 1,
1516
                ];
1517
                $targets = $this->add_subtree(
1518
                    $targets,
1519
                    $level + 1,
1520
                    $cat->get_id(),
1521
                    null
1522
                );
1523
            }
1524
        } else {
1525
            // student
1526
            $cats = $this->get_root_categories_for_student(api_get_user_id());
1527
            foreach ($cats as $cat) {
1528
                $targets[] = [
1529
                    $cat->get_id(),
1530
                    $cat->get_name(),
1531
                    $level + 1,
1532
                ];
1533
                $targets = $this->add_subtree(
1534
                    $targets,
1535
                    $level + 1,
1536
                    $cat->get_id(),
1537
                    1
1538
                );
1539
            }
1540
        }
1541
1542
        return $targets;
1543
    }
1544
1545
    /**
1546
     * Generate an array of courses that a teacher hasn't created a category for.
1547
     *
1548
     * @param int $user_id
1549
     *
1550
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1551
     */
1552
    public function get_not_created_course_categories($user_id)
1553
    {
1554
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1555
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1556
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1557
1558
        $user_id = (int) $user_id;
1559
1560
        $sql = 'SELECT DISTINCT(code), title
1561
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1562
                WHERE 
1563
                    cc.id = cu.c_id AND 
1564
                    cu.status = '.COURSEMANAGER;
1565
1566
        if (!api_is_platform_admin()) {
1567
            $sql .= ' AND cu.user_id = '.$user_id;
1568
        }
1569
        $sql .= ' AND cc.code NOT IN
1570
             (
1571
                SELECT course_code FROM '.$tbl_grade_categories.'
1572
                WHERE
1573
                    parent_id = 0 AND
1574
                    course_code IS NOT NULL
1575
                )';
1576
        $result = Database::query($sql);
1577
1578
        $cats = [];
1579
        while ($data = Database::fetch_array($result)) {
1580
            $cats[] = [$data['code'], $data['title']];
1581
        }
1582
1583
        return $cats;
1584
    }
1585
1586
    /**
1587
     * Generate an array of all courses that a teacher is admin of.
1588
     *
1589
     * @param int $user_id
1590
     *
1591
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1592
     */
1593
    public function get_all_courses($user_id)
1594
    {
1595
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1596
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1597
        $sql = 'SELECT DISTINCT(code), title
1598
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1599
                WHERE cc.id = cu.c_id AND cu.status = '.COURSEMANAGER;
1600
        if (!api_is_platform_admin()) {
1601
            $sql .= ' AND cu.user_id = '.intval($user_id);
1602
        }
1603
1604
        $result = Database::query($sql);
1605
        $cats = [];
1606
        while ($data = Database::fetch_array($result)) {
1607
            $cats[] = [$data['code'], $data['title']];
1608
        }
1609
1610
        return $cats;
1611
    }
1612
1613
    /**
1614
     * Apply the same visibility to every subcategory, evaluation and link.
1615
     */
1616
    public function apply_visibility_to_children()
1617
    {
1618
        $cats = self::load(null, null, null, $this->id, null);
1619
        $evals = Evaluation::load(null, null, null, $this->id, null);
1620
        $links = LinkFactory::load(
1621
            null,
1622
            null,
1623
            null,
1624
            null,
1625
            null,
1626
            $this->id,
1627
            null
1628
        );
1629
        if (!empty($cats)) {
1630
            foreach ($cats as $cat) {
1631
                $cat->set_visible($this->is_visible());
1632
                $cat->save();
1633
                $cat->apply_visibility_to_children();
1634
            }
1635
        }
1636
        if (!empty($evals)) {
1637
            foreach ($evals as $eval) {
1638
                $eval->set_visible($this->is_visible());
1639
                $eval->save();
1640
            }
1641
        }
1642
        if (!empty($links)) {
1643
            foreach ($links as $link) {
1644
                $link->set_visible($this->is_visible());
1645
                $link->save();
1646
            }
1647
        }
1648
    }
1649
1650
    /**
1651
     * Check if a category contains evaluations with a result for a given student.
1652
     *
1653
     * @param int $studentId
1654
     *
1655
     * @return bool
1656
     */
1657
    public function hasEvaluationsWithStudentResults($studentId)
1658
    {
1659
        $evals = Evaluation::get_evaluations_with_result_for_student(
1660
            $this->id,
1661
            $studentId
1662
        );
1663
        if (count($evals) != 0) {
1664
            return true;
1665
        } else {
1666
            $cats = self::load(
1667
                null,
1668
                null,
1669
                null,
1670
                $this->id,
1671
                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

1671
                /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1
Loading history...
1672
            );
1673
1674
            /** @var Category $cat */
1675
            foreach ($cats as $cat) {
1676
                if ($cat->hasEvaluationsWithStudentResults($studentId)) {
1677
                    return true;
1678
                }
1679
            }
1680
1681
            return false;
1682
        }
1683
    }
1684
1685
    /**
1686
     * Retrieve all categories inside a course independent category
1687
     * that should be visible to a student.
1688
     *
1689
     * @param int   $categoryId parent category
1690
     * @param int   $studentId
1691
     * @param array $cats       optional: if defined, the categories will be added to this array
1692
     *
1693
     * @return array
1694
     */
1695
    public function getIndependentCategoriesWithStudentResult(
1696
        $categoryId,
1697
        $studentId,
1698
        $cats = []
1699
    ) {
1700
        $creator = api_is_allowed_to_edit() && !api_is_platform_admin() ? api_get_user_id() : null;
1701
1702
        $categories = self::load(
1703
            null,
1704
            $creator,
1705
            '0',
1706
            $categoryId,
1707
            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

1707
            /** @scrutinizer ignore-type */ api_is_allowed_to_edit() ? null : 1
Loading history...
1708
        );
1709
1710
        if (!empty($categories)) {
1711
            /** @var Category $category */
1712
            foreach ($categories as $category) {
1713
                if ($category->hasEvaluationsWithStudentResults($studentId)) {
1714
                    $cats[] = $category;
1715
                }
1716
            }
1717
        }
1718
1719
        return $cats;
1720
    }
1721
1722
    /**
1723
     * Return the session id (in any case, even if it's null or 0).
1724
     *
1725
     * @return int Session id (can be null)
1726
     */
1727
    public function get_session_id()
1728
    {
1729
        return $this->session_id;
1730
    }
1731
1732
    /**
1733
     * Get appropriate subcategories visible for the user (and optionally the course and session).
1734
     *
1735
     * @param int    $studentId   student id (default: all students)
1736
     * @param string $course_code Course code (optional)
1737
     * @param int    $session_id  Session ID (optional)
1738
     * @param bool   $order
1739
     *
1740
     * @return array Array of subcategories
1741
     */
1742
    public function get_subcategories(
1743
        $studentId = null,
1744
        $course_code = null,
1745
        $session_id = null,
1746
        $order = null
1747
    ) {
1748
        // 1 student
1749
        if (isset($studentId)) {
1750
            // Special case: this is the root
1751
            if ($this->id == 0) {
1752
                return $this->get_root_categories_for_student($studentId, $course_code, $session_id);
1753
            } else {
1754
                return self::load(
1755
                    null,
1756
                    null,
1757
                    $course_code,
1758
                    $this->id,
1759
                    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

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

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

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

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

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

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

2698
        while ($data = Database::fetch_array(/** @scrutinizer ignore-type */ $result)) {
Loading history...
2699
            $cat = new Category();
2700
            $cat->set_id($data['id']);
2701
            $cat->set_name($data['name']);
2702
            $cat->set_description($data['description']);
2703
            $cat->set_user_id($data['user_id']);
2704
            $courseInfo = api_get_course_info_by_id($data['c_id']);
2705
            $cat->set_course_code($courseInfo['code']);
2706
            $cat->setCourseId($data['c_id']);
2707
            $cat->set_parent_id($data['parent_id']);
2708
            $cat->set_weight($data['weight']);
2709
            $cat->set_visible($data['visible']);
2710
            $cat->set_session_id($data['session_id']);
2711
            $cat->set_certificate_min_score($data['certif_min_score']);
2712
            $cat->set_grade_model_id($data['grade_model_id']);
2713
            $cat->set_locked($data['locked']);
2714
            $cat->setGenerateCertificates($data['generate_certificates']);
2715
            $cat->setIsRequirement($data['is_requirement']);
2716
            $cat->setCourseListDependency(isset($data['depends']) ? $data['depends'] : []);
2717
            $cat->setMinimumToValidate(isset($data['minimum_to_validate']) ? $data['minimum_to_validate'] : null);
2718
            $cat->setGradeBooksToValidateInDependence(isset($data['gradebooks_to_validate_in_dependence']) ? $data['gradebooks_to_validate_in_dependence'] : null);
2719
            $cat->setDocumentId($data['document_id']);
2720
            if ($allow) {
2721
                $cat->entity = $repo->find($data['id']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $repo does not seem to be defined for all execution paths leading up to this point.
Loading history...
2722
            }
2723
2724
            $categories[] = $cat;
2725
        }
2726
2727
        return $categories;
2728
    }
2729
2730
    /**
2731
     * Internal function used by get_target_categories().
2732
     *
2733
     * @param array $targets
2734
     * @param int   $level
2735
     * @param int   $catid
2736
     *
2737
     * @return array
2738
     */
2739
    private function addTargetSubcategories($targets, $level, $catid)
2740
    {
2741
        $subcats = self::load(null, null, null, $catid);
2742
        foreach ($subcats as $cat) {
2743
            if ($this->can_be_moved_to_cat($cat)) {
2744
                $targets[] = [
2745
                    $cat->get_id(),
2746
                    $cat->get_name(),
2747
                    $level + 1,
2748
                ];
2749
                $targets = $this->addTargetSubcategories(
2750
                    $targets,
2751
                    $level + 1,
2752
                    $cat->get_id()
2753
                );
2754
            }
2755
        }
2756
2757
        return $targets;
2758
    }
2759
2760
    /**
2761
     * Internal function used by get_target_categories() and addTargetSubcategories()
2762
     * Can this category be moved to the given category ?
2763
     * Impossible when origin and target are the same... children won't be processed
2764
     * either. (a category can't be moved to one of its own children).
2765
     */
2766
    private function can_be_moved_to_cat($cat)
2767
    {
2768
        return $cat->get_id() != $this->get_id();
2769
    }
2770
2771
    /**
2772
     * Internal function used by move_to_cat().
2773
     */
2774
    private function applyCourseCodeToChildren()
2775
    {
2776
        $cats = self::load(null, null, null, $this->id, null);
2777
        $evals = Evaluation::load(null, null, null, $this->id, null);
2778
        $links = LinkFactory::load(
2779
            null,
2780
            null,
2781
            null,
2782
            null,
2783
            null,
2784
            $this->id,
2785
            null
2786
        );
2787
        /** @var Category $cat */
2788
        foreach ($cats as $cat) {
2789
            $cat->set_course_code($this->get_course_code());
2790
            $cat->save();
2791
            $cat->applyCourseCodeToChildren();
2792
        }
2793
2794
        foreach ($evals as $eval) {
2795
            $eval->set_course_code($this->get_course_code());
2796
            $eval->save();
2797
        }
2798
2799
        foreach ($links as $link) {
2800
            $link->delete();
2801
        }
2802
    }
2803
2804
    /**
2805
     * Internal function used by get_tree().
2806
     *
2807
     * @param int      $level
2808
     * @param int|null $visible
2809
     *
2810
     * @return array
2811
     */
2812
    private function add_subtree($targets, $level, $catid, $visible)
2813
    {
2814
        $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

2814
        $subcats = self::load(null, null, null, $catid, /** @scrutinizer ignore-type */ $visible);
Loading history...
2815
2816
        if (!empty($subcats)) {
2817
            foreach ($subcats as $cat) {
2818
                $targets[] = [
2819
                    $cat->get_id(),
2820
                    $cat->get_name(),
2821
                    $level + 1,
2822
                ];
2823
                $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

2823
                /** @scrutinizer ignore-call */ 
2824
                $targets = self::add_subtree(
Loading history...
2824
                    $targets,
2825
                    $level + 1,
2826
                    $cat->get_id(),
2827
                    $visible
2828
                );
2829
            }
2830
        }
2831
2832
        return $targets;
2833
    }
2834
2835
    /**
2836
     * Calculate the current score on a gradebook category for a user.
2837
     *
2838
     * @param int      $userId   The user id
2839
     * @param Category $category The gradebook category
2840
     *
2841
     * @return float The score
2842
     */
2843
    private static function calculateCurrentScore(
2844
        $userId,
2845
        $category
2846
    ) {
2847
        if (empty($category)) {
2848
            return 0;
2849
        }
2850
        $courseEvaluations = $category->get_evaluations(
2851
            $userId,
2852
            true
2853
        );
2854
        $courseLinks = $category->get_links($userId, true);
2855
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2856
        $categoryScore = 0;
2857
        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...
2858
            $item = $evaluationsAndLinks[$i];
2859
            $score = $item->calc_score($userId);
2860
            $itemValue = 0;
2861
            if (!empty($score)) {
2862
                $divider = $score[1] == 0 ? 1 : $score[1];
2863
                $itemValue = $score[0] / $divider * $item->get_weight();
2864
            }
2865
2866
            $categoryScore += $itemValue;
2867
        }
2868
2869
        return api_float_val($categoryScore);
2870
    }
2871
}
2872