Test Setup Failed
Push — master ( c7183e...00c715 )
by Julito
30:43
created

Category::is_certificate_available()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 3
nop 1
dl 0
loc 21
rs 9.0534
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\GradebookCategory;
5
6
/**
7
 * Class Category
8
 * Defines a gradebook Category object
9
 * @package chamilo.gradebook
10
 */
11
class Category implements GradebookItem
12
{
13
    private $id;
14
    private $name;
15
    private $description;
16
    private $user_id;
17
    private $course_code;
18
    private $courseId;
19
    private $parent;
20
    private $weight;
21
    private $visible;
22
    private $certificate_min_score;
23
    private $session_id;
24
    private $skills = array();
25
    private $grade_model_id;
26
    private $generateCertificates;
27
    private $isRequirement;
28
    public $studentList;
29
30
    public $evaluations;
31
    public $links;
32
    public $subCategories;
33
34
    /**
35
     * Consctructor
36
     */
37
    public function __construct()
38
    {
39
        $this->id = 0;
40
        $this->name = null;
41
        $this->description = null;
42
        $this->user_id = 0;
43
        $this->course_code = '';
44
        $this->courseId = 0;
45
        $this->parent = 0;
46
        $this->weight = 0;
47
        $this->visible = false;
48
        $this->certificate_min_score = 0;
49
        $this->session_id = 0;
50
        $this->grade_model_id = 0;
51
        $this->generateCertificates = false;
52
        $this->isRequirement = false;
53
    }
54
55
    /**
56
     * @return int
57
     */
58
    public function get_id()
59
    {
60
        return $this->id;
61
    }
62
63
    /**
64
     * @return string
65
     */
66
    public function get_name()
67
    {
68
        return $this->name;
69
    }
70
71
    /**
72
     * @return string
73
     */
74
    public function get_description()
75
    {
76
        return $this->description;
77
    }
78
79
    /**
80
     * @return int
81
     */
82
    public function get_user_id()
83
    {
84
        return $this->user_id;
85
    }
86
87
    /**
88
     * @return integer|null
89
     */
90
    public function get_certificate_min_score()
91
    {
92
        if (!empty($this->certificate_min_score)) {
93
            return $this->certificate_min_score;
94
        } else {
95
            return null;
96
        }
97
    }
98
99
    /**
100
     * @return string
101
     */
102
    public function get_course_code()
103
    {
104
        return $this->course_code;
105
    }
106
107
    /**
108
     * @return integer
109
     */
110
    public function get_parent_id()
111
    {
112
        return $this->parent;
113
    }
114
115
    /**
116
     * @return integer
117
     */
118
    public function get_weight()
119
    {
120
        return $this->weight;
121
    }
122
123
    /**
124
     * @return bool
125
     */
126
    public function is_locked()
127
    {
128
        return isset($this->locked) && $this->locked == 1 ? true : false;
129
    }
130
131
    /**
132
     * @return boolean
133
     */
134
    public function is_visible()
135
    {
136
        return $this->visible;
137
    }
138
139
    /**
140
     * Get $isRequirement
141
     * @return int
142
     */
143
    public function getIsRequirement()
144
    {
145
        return $this->isRequirement;
146
    }
147
148
    /**
149
     * @param int $id
150
     */
151
    public function set_id($id)
152
    {
153
        $this->id = $id;
154
    }
155
156
    /**
157
     * @param string $name
158
     */
159
    public function set_name($name)
160
    {
161
        $this->name = $name;
162
    }
163
164
    /**
165
     * @param string $description
166
     */
167
    public function set_description($description)
168
    {
169
        $this->description = $description;
170
    }
171
172
    /**
173
     * @param int $user_id
174
     */
175
    public function set_user_id($user_id)
176
    {
177
        $this->user_id = $user_id;
178
    }
179
180
    /**
181
     * @param string $course_code
182
     */
183
    public function set_course_code($course_code)
184
    {
185
        $this->course_code = $course_code;
186
    }
187
188
    /**
189
     * @param float $min_score
190
     */
191
    public function set_certificate_min_score($min_score = null)
192
    {
193
        $this->certificate_min_score = $min_score;
194
    }
195
196
    /**
197
     * @param int $parent
198
     */
199
    public function set_parent_id($parent)
200
    {
201
        $this->parent = intval($parent);
202
    }
203
204
    /**
205
     * Filters to int and sets the session ID
206
     * @param   int     The session ID from the Dokeos course session
207
     */
208
    public function set_session_id($session_id = 0)
209
    {
210
        $this->session_id = (int) $session_id;
211
    }
212
213
    /**
214
     * @param $weight
215
     */
216
    public function set_weight($weight)
217
    {
218
        $this->weight = $weight;
219
    }
220
221
    /**
222
     * @param $visible
223
     */
224
    public function set_visible($visible)
225
    {
226
        $this->visible = $visible;
227
    }
228
229
    /**
230
     * @param int $id
231
     */
232
    public function set_grade_model_id($id)
233
    {
234
        $this->grade_model_id = $id;
235
    }
236
237
    /**
238
     * @param $locked
239
     */
240
    public function set_locked($locked)
241
    {
242
        $this->locked = $locked;
243
    }
244
245
    /**
246
     * Set $isRequirement
247
     * @param int $isRequirement
248
     */
249
    public function setIsRequirement($isRequirement)
250
    {
251
        $this->isRequirement = $isRequirement;
252
    }
253
254
    /**
255
     * @return null|integer
256
     */
257
    public function get_grade_model_id()
258
    {
259
        if ($this->grade_model_id < 0) {
260
            return null;
261
        }
262
        return $this->grade_model_id;
263
    }
264
265
    /**
266
     * @return string
267
     */
268
    public function get_type()
269
    {
270
        return 'category';
271
    }
272
273
    /**
274
     * @param bool $from_db
275
     * @return array|resource
276
     */
277
    public function get_skills($from_db = true)
278
    {
279
        if ($from_db) {
280
            $cat_id = $this->get_id();
281
282
            $gradebook = new Gradebook();
283
            $skills = $gradebook->get_skills_by_gradebook($cat_id);
284
        } else {
285
            $skills = $this->skills;
286
        }
287
288
        return $skills;
289
    }
290
291
    /**
292
     * @return array
293
     */
294
    public function get_skills_for_select()
295
    {
296
        $skills = $this->get_skills();
297
        $skill_select = array();
298
        if (!empty($skills)) {
299
            foreach ($skills as $skill) {
0 ignored issues
show
Bug introduced by
The expression $skills of type array|resource is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
300
                $skill_select[$skill['id']] = $skill['name'];
301
            }
302
        }
303
304
        return $skill_select;
305
    }
306
307
    /**
308
     * Set the generate_certificates value
309
     * @param int $generateCertificates
310
     */
311
    public function setGenerateCertificates($generateCertificates)
312
    {
313
        $this->generateCertificates = $generateCertificates;
314
    }
315
316
    /**
317
     * Get the generate_certificates value
318
     * @return int
319
     */
320
    public function getGenerateCertificates()
321
    {
322
        return $this->generateCertificates;
323
    }
324
325
    /**
326
     * @param int $id
327
     * @param int $session_id
328
     *
329
     * @return array
330
     */
331
    public static function load_session_categories($id = null, $session_id = null)
332
    {
333 View Code Duplication
        if (isset($id) && (int) $id === 0) {
334
            $cats = array();
335
            $cats[] = self::create_root_category();
336
            return $cats;
337
        }
338
339
        $courseInfo = api_get_course_info_by_id(api_get_course_int_id());
340
        $courseCode = $courseInfo['code'];
341
        $session_id = intval($session_id);
342
343
        if (!empty($session_id)) {
344
            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
345
            $sql = 'SELECT id, course_code
346
                    FROM '.$table.'
347
                    WHERE session_id = '.$session_id;
348
            $result_session = Database::query($sql);
349
            if (Database::num_rows($result_session) > 0) {
350
                $categoryList = array();
351
                while ($data_session = Database::fetch_array($result_session)) {
352
                    $parent_id = $data_session['id'];
353
                    if ($data_session['course_code'] == $courseCode) {
354
                        $categories = self::load($parent_id);
355
                        $categoryList = array_merge($categoryList, $categories);
356
                    }
357
                }
358
359
                return $categoryList;
360
            }
361
        }
362
    }
363
364
    /**
365
     * Retrieve categories and return them as an array of Category objects
366
     * @param int $id category id
367
     * @param int $user_id (category owner)
368
     * @param string $course_code
369
     * @param int $parent_id parent category
370
     * @param bool $visible
371
     * @param int $session_id (in case we are in a session)
372
     * @param bool $order_by Whether to show all "session"
373
     * categories (true) or hide them (false) in case there is no session id
374
     *
375
     * @return array
376
     */
377
    public static function load(
378
        $id = null,
379
        $user_id = null,
380
        $course_code = null,
381
        $parent_id = null,
382
        $visible = null,
383
        $session_id = null,
384
        $order_by = null
385
    ) {
386
        //if the category given is explicitly 0 (not null), then create
387
        // a root category object (in memory)
388 View Code Duplication
        if (isset($id) && (int) $id === 0) {
389
            $cats = array();
390
            $cats[] = self::create_root_category();
391
392
            return $cats;
393
        }
394
395
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
396
        $sql = 'SELECT * FROM '.$tbl_grade_categories;
397
        $paramcount = 0;
398
        if (isset($id)) {
399
            $sql .= ' WHERE id = '.intval($id);
400
            $paramcount++;
401
        }
402
403 View Code Duplication
        if (isset($user_id)) {
404
            $user_id = intval($user_id);
405
            if ($paramcount != 0) {
406
                $sql .= ' AND';
407
            } else {
408
                $sql .= ' WHERE';
409
            }
410
            $sql .= ' user_id = '.intval($user_id);
411
            $paramcount++;
412
        }
413
414
        if (isset($course_code)) {
415
            if ($paramcount != 0) {
416
                $sql .= ' AND';
417
            } else {
418
                $sql .= ' WHERE';
419
            }
420
421
            if ($course_code == '0') {
422
                $sql .= ' c_id is null ';
423
            } else {
424
                $courseInfo = api_get_course_info($course_code);
425
                if ($courseInfo) {
426
                    $sql .= " c_id = '".intval($courseInfo['real_id'])."'";
427
                }
428
            }
429
430
            /*if ($show_session_categories !== true) {
431
                // a query on the course should show all
432
                // the categories inside sessions for this course
433
                // otherwise a special parameter is given to ask explicitely
434
                $sql .= " AND (session_id IS NULL OR session_id = 0) ";
435
            } else {*/
436
            if (empty($session_id)) {
437
                $sql .= ' AND (session_id IS NULL OR session_id = 0) ';
438
            } else {
439
                $sql .= ' AND session_id = '.(int) $session_id.' ';
440
            }
441
            //}
442
            $paramcount++;
443
        }
444
445 View Code Duplication
        if (isset($parent_id)) {
446
            if ($paramcount != 0) {
447
                $sql .= ' AND ';
448
            } else {
449
                $sql .= ' WHERE ';
450
            }
451
            $sql .= ' parent_id = '.intval($parent_id);
452
            $paramcount++;
453
        }
454
455 View Code Duplication
        if (isset($visible)) {
456
            if ($paramcount != 0) {
457
                $sql .= ' AND';
458
            } else {
459
                $sql .= ' WHERE';
460
            }
461
            $sql .= ' visible = '.intval($visible);
462
        }
463
464
        if (!empty($order_by)) {
465
            if (!empty($order_by) && $order_by != '') {
466
                $sql .= ' '.$order_by;
467
            }
468
        }
469
470
        $result = Database::query($sql);
471
472
        $categories = array();
473
        if (Database::num_rows($result) > 0) {
474
            $categories = self::create_category_objects_from_sql_result(
475
                $result
476
            );
477
        }
478
479
        return $categories;
480
    }
481
482
    /**
483
     * @return Category
484
     */
485
    private static function create_root_category()
486
    {
487
        $cat = new Category();
488
        $cat->set_id(0);
489
        $cat->set_name(get_lang('RootCat'));
490
        $cat->set_description(null);
491
        $cat->set_user_id(0);
492
        $cat->set_course_code(null);
493
        $cat->set_parent_id(null);
494
        $cat->set_weight(0);
495
        $cat->set_visible(1);
496
        $cat->setGenerateCertificates(0);
497
        $cat->setIsRequirement(false);
498
499
        return $cat;
500
    }
501
502
    /**
503
     * @param Doctrine\DBAL\Driver\Statement|null $result
504
     *
505
     * @return array
506
     */
507
    private static function create_category_objects_from_sql_result($result)
508
    {
509
        $categories = array();
510
        while ($data = Database::fetch_array($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result defined by parameter $result on line 507 can be null; however, Database::fetch_array() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
511
            $cat = new Category();
512
            $cat->set_id($data['id']);
513
            $cat->set_name($data['name']);
514
            $cat->set_description($data['description']);
515
            $cat->set_user_id($data['user_id']);
516
            $courseInfo = api_get_course_info_by_id($data['c_id']);
517
            $cat->set_course_code($courseInfo['code']);
518
            $cat->setCourseId($data['c_id']);
519
            $cat->set_parent_id($data['parent_id']);
520
            $cat->set_weight($data['weight']);
521
            $cat->set_visible($data['visible']);
522
            $cat->set_session_id($data['session_id']);
523
            $cat->set_certificate_min_score($data['certif_min_score']);
524
            $cat->set_grade_model_id($data['grade_model_id']);
525
            $cat->set_locked($data['locked']);
526
            $cat->setGenerateCertificates($data['generate_certificates']);
527
            $cat->setIsRequirement($data['is_requirement']);
528
            $categories[] = $cat;
529
        }
530
531
        return $categories;
532
    }
533
534
    /**
535
     * Create a category object from a GradebookCategory entity
536
     * @param GradebookCategory $gradebookCategory  The entity
537
     * @return \Category
538
     */
539
    public static function createCategoryObjectFromEntity(
540
        GradebookCategory $gradebookCategory
541
    ) {
542
        $category = new Category();
543
        $category->set_id($gradebookCategory->getId());
544
        $category->set_name($gradebookCategory->getName());
545
        $category->set_description($gradebookCategory->getDescription());
546
        $category->set_user_id($gradebookCategory->getUserId());
547
        $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()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
548
        $category->set_parent_id($gradebookCategory->getParentId());
549
        $category->set_weight($gradebookCategory->getWeight());
550
        $category->set_visible($gradebookCategory->getVisible());
551
        $category->set_session_id($gradebookCategory->getSessionId());
552
        $category->set_certificate_min_score(
553
            $gradebookCategory->getCertifMinScore()
554
        );
555
        $category->set_grade_model_id($gradebookCategory->getGradeModelId());
556
        $category->set_locked($gradebookCategory->getLocked());
557
        $category->setGenerateCertificates(
558
            $gradebookCategory->getGenerateCertificates()
559
        );
560
        $category->setIsRequirement($gradebookCategory->getIsRequirement());
561
562
        return $category;
563
    }
564
565
    /**
566
     * Insert this category into the database
567
     */
568
    public function add()
569
    {
570
        if (isset($this->name) && '-1' == $this->name) {
571
            return false;
572
        }
573
574
        if (isset($this->name) && isset($this->user_id)) {
575
            $em = Database::getManager();
576
577
            $courseInfo = api_get_course_info($this->course_code);
578
            $course = api_get_user_course_entity($courseInfo['real_id']);
579
580
            $category = new GradebookCategory();
581
            $category->setName($this->name);
582
            $category->setDescription($this->description);
583
            $category->setUserId($this->user_id);
584
            $category->setCourse($course);
585
            $category->setParentId($this->parent);
586
            $category->setWeight($this->weight);
587
            $category->setVisible($this->visible);
588
            $category->setCertifMinScore($this->certificate_min_score);
589
            $category->setSessionId($this->session_id);
590
            $category->setGenerateCertificates($this->generateCertificates);
0 ignored issues
show
Bug introduced by
It seems like $this->generateCertificates can also be of type integer; however, Chamilo\CoreBundle\Entit...tGenerateCertificates() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
591
            $category->setGradeModelId($this->grade_model_id);
592
            $category->setIsRequirement($this->isRequirement);
0 ignored issues
show
Bug introduced by
It seems like $this->isRequirement can also be of type integer; however, Chamilo\CoreBundle\Entit...ory::setIsRequirement() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
593
            $category->setLocked(false);
594
595
            $em->persist($category);
596
            $em->flush();
597
598
            $id = $category->getId();
599
            $this->set_id($id);
600
601 View Code Duplication
            if (!empty($id)) {
602
                $parent_id = $this->get_parent_id();
603
                $grade_model_id = $this->get_grade_model_id();
604
                if ($parent_id == 0) {
605
                    //do something
606
                    if (isset($grade_model_id) && !empty($grade_model_id) && $grade_model_id != '-1') {
607
                        $obj = new GradeModel();
608
                        $components = $obj->get_components($grade_model_id);
609
                        $default_weight_setting = api_get_setting('gradebook_default_weight');
610
                        $default_weight = 100;
611
                        if (isset($default_weight_setting)) {
612
                            $default_weight = $default_weight_setting;
613
                        }
614
                        foreach ($components as $component) {
0 ignored issues
show
Bug introduced by
The expression $components of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
615
                            $gradebook = new Gradebook();
616
                            $params = array();
617
618
                            $params['name'] = $component['acronym'];
619
                            $params['description'] = $component['title'];
620
                            $params['user_id'] = api_get_user_id();
621
                            $params['parent_id'] = $id;
622
                            $params['weight'] = $component['percentage'] / 100 * $default_weight;
623
                            $params['session_id'] = api_get_session_id();
624
                            $params['course_code'] = $this->get_course_code();
625
626
                            $gradebook->save($params);
627
                        }
628
                    }
629
                }
630
            }
631
632
            $gradebook = new Gradebook();
633
            $gradebook->update_skills_to_gradebook(
634
                $this->id,
635
                $this->get_skills(false)
0 ignored issues
show
Bug introduced by
It seems like $this->get_skills(false) targeting Category::get_skills() can also be of type resource; however, Gradebook::update_skills_to_gradebook() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
636
            );
637
638
            return $id;
639
        }
640
    }
641
642
    /**
643
     * Update the properties of this category in the database
644
     * @todo fix me
645
     */
646
    public function save()
647
    {
648
        $em = Database::getManager();
649
650
        /** @var  GradebookCategory $gradebookCategory */
651
        $gradebookCategory = $em
652
            ->getRepository('ChamiloCoreBundle:GradebookCategory')
653
            ->find($this->id);
654
655
        if (empty($gradebookCategory)) {
656
            return false;
657
        }
658
659
        $course = api_get_user_course_entity();
660
661
        $gradebookCategory->setName($this->name);
662
        $gradebookCategory->setDescription($this->description);
663
        $gradebookCategory->setUserId($this->user_id);
664
        $gradebookCategory->setCourse($course);
665
        //$gradebookCategory->setCourseCode($this->course_code);
666
        $gradebookCategory->setParentId($this->parent);
667
        $gradebookCategory->setWeight($this->weight);
668
        $gradebookCategory->setVisible($this->visible);
669
        $gradebookCategory->setCertifMinScore($this->certificate_min_score);
670
        $gradebookCategory->setGenerateCertificates(
671
            $this->generateCertificates
0 ignored issues
show
Bug introduced by
It seems like $this->generateCertificates can also be of type integer; however, Chamilo\CoreBundle\Entit...tGenerateCertificates() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
672
        );
673
        $gradebookCategory->setGradeModelId($this->grade_model_id);
674
        $gradebookCategory->setIsRequirement($this->isRequirement);
0 ignored issues
show
Bug introduced by
It seems like $this->isRequirement can also be of type integer; however, Chamilo\CoreBundle\Entit...ory::setIsRequirement() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
675
676
        $em->merge($gradebookCategory);
677
        $em->flush();
678
679
        if (!empty($this->id)) {
680
            $parent_id = $this->get_parent_id();
681
            $grade_model_id = $this->get_grade_model_id();
682 View Code Duplication
            if ($parent_id == 0) {
683
                if (isset($grade_model_id) && !empty($grade_model_id) && $grade_model_id != '-1') {
684
                    $obj = new GradeModel();
685
                    $components = $obj->get_components($grade_model_id);
686
                    $default_weight_setting = api_get_setting('gradebook_default_weight');
687
                    $default_weight = 100;
688
                    if (isset($default_weight_setting)) {
689
                        $default_weight = $default_weight_setting;
690
                    }
691
                    $final_weight = $this->get_weight();
692
                    if (!empty($final_weight)) {
693
                        $default_weight = $this->get_weight();
694
                    }
695
                    foreach ($components as $component) {
0 ignored issues
show
Bug introduced by
The expression $components of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
696
                        $gradebook = new Gradebook();
697
                        $params = array();
698
                        $params['name'] = $component['acronym'];
699
                        $params['description'] = $component['title'];
700
                        $params['user_id'] = api_get_user_id();
701
                        $params['parent_id'] = $this->id;
702
                        $params['weight'] = $component['percentage'] / 100 * $default_weight;
703
                        $params['session_id'] = api_get_session_id();
704
                        $params['course_code'] = $this->get_course_code();
705
                        $gradebook->save($params);
706
                    }
707
                }
708
            }
709
        }
710
711
        $gradebook = new Gradebook();
712
        $gradebook->update_skills_to_gradebook(
713
            $this->id,
714
            $this->get_skills(false),
0 ignored issues
show
Bug introduced by
It seems like $this->get_skills(false) targeting Category::get_skills() can also be of type resource; however, Gradebook::update_skills_to_gradebook() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
715
            true
716
        );
717
    }
718
719
    /**
720
     * Update link weights see #5168
721
     * @param type $new_weight
722
     */
723
    public function update_children_weight($new_weight)
724
    {
725
        $links = $this->get_links();
726
        $old_weight = $this->get_weight();
727
728
        if (!empty($links)) {
729
            foreach ($links as $link_item) {
730
                if (isset($link_item)) {
731
                    $new_item_weight = $new_weight * $link_item->get_weight() / $old_weight;
732
                    $link_item->set_weight($new_item_weight);
733
                    $link_item->save();
734
                }
735
            }
736
        }
737
    }
738
739
    /**
740
     * Delete this evaluation from the database
741
     */
742
    public function delete()
743
    {
744
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
745
        $sql = 'DELETE FROM '.$table.' WHERE id = '.intval($this->id);
746
        Database::query($sql);
747
    }
748
749
    /**
750
     * Not delete this category from the database,when visible=3 is category eliminated
751
     * @param int $courseId
752
     */
753 View Code Duplication
    public function update_category_delete($courseId)
754
    {
755
        $tbl_grade_categories = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces before double colon; 1 found

This check looks for references to static members where there are spaces between the name of the type and the actual member.

An example:

Certificate ::TRIPLEDES_CBC

will actually work, but is not very readable.

Loading history...
756
        $sql = 'UPDATE '.$tbl_grade_categories.' SET visible=3
757
                WHERE c_id ="'.intval($courseId).'"';
758
        Database::query($sql);
759
    }
760
761
     /**
762
     * Delete this category from the database
763
     * @param int $courseId
764
     */
765 View Code Duplication
    public static function deleteCategoryFromCourse($courseId)
766
    {
767
        $table = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces before double colon; 1 found

This check looks for references to static members where there are spaces between the name of the type and the actual member.

An example:

Certificate ::TRIPLEDES_CBC

will actually work, but is not very readable.

Loading history...
768
        $sql = 'DELETE FROM '.$table.' 
769
                WHERE c_id ="'.intval($courseId).'"';
770
        Database::query($sql);
771
    }
772
773
    /**
774
     * @param int $course_id
775
     * @return bool|string
776
     */
777
    public function show_message_resource_delete($course_id)
778
    {
779
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
780
        $sql = 'SELECT count(*) AS num 
781
                FROM '.$tbl_grade_categories.'
782
                WHERE
783
                    c_id = "'.Database::escape_string($course_id).'" AND
784
                    visible=3';
785
        $res = Database::query($sql);
786
        $option = Database::fetch_array($res, 'ASSOC');
787
        if ($option['num'] >= 1) {
788
            return '&nbsp;&nbsp;<span class="resource-deleted">(&nbsp;'.get_lang('ResourceDeleted').'&nbsp;)</span>';
789
        } else {
790
            return false;
791
        }
792
    }
793
794
    /**
795
     * Shows all information of an category
796
     */
797 View Code Duplication
    public function shows_all_information_an_category($selectcat = '')
798
    {
799
        if ($selectcat == '') {
800
            return null;
801
        } else {
802
            $tbl_category = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces before double colon; 1 found

This check looks for references to static members where there are spaces between the name of the type and the actual member.

An example:

Certificate ::TRIPLEDES_CBC

will actually work, but is not very readable.

Loading history...
803
            $sql = 'SELECT 
804
                        name,
805
                        description,
806
                        user_id,
807
                        c_id,
808
                        parent_id,
809
                        weight,
810
                        visible,
811
                        certif_min_score,
812
                        session_id, 
813
                        generate_certificates, 
814
                        is_requirement
815
                    FROM '.$tbl_category.' c
816
                    WHERE c.id='.intval($selectcat);
817
            $result = Database::query($sql);
818
            $row = Database::fetch_array($result, 'ASSOC');
819
820
            return $row;
821
        }
822
    }
823
824
    /**
825
     * Check if a category name (with the same parent category) already exists
826
     * @param string $name name to check (if not given, the name property of this object will be checked)
827
     * @param int $parent parent category
828
     *
829
     * @return bool
830
     */
831
    public function does_name_exist($name, $parent)
832
    {
833
        if (!isset($name)) {
834
            $name = $this->name;
835
            $parent = $this->parent;
836
        }
837
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
838
        $sql = "SELECT count(id) AS number
839
                FROM $tbl_grade_categories
840
                WHERE name = '".Database::escape_string($name)."'";
841
842
        if (api_is_allowed_to_edit()) {
843
            $parent = self::load($parent);
844
            $code = $parent[0]->get_course_code();
845
            $courseInfo = api_get_course_info($code);
846
            $courseId = $courseInfo['real_id'];
847
            if (isset($code) && $code != '0') {
848
                $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
849
                $sql .= ' AND user_id IN (
850
                            SELECT user_id FROM '.$main_course_user_table.'
851
                            WHERE c_id = '.$courseId.' AND status = '.COURSEMANAGER.'
852
                        )';
853
            } else {
854
                $sql .= ' AND user_id = '.api_get_user_id();
855
            }
856
        } else {
857
            $sql .= ' AND user_id = '.api_get_user_id();
858
        }
859
860
        if (!isset($parent)) {
861
            $sql .= ' AND parent_id is null';
862
        } else {
863
            $sql .= ' AND parent_id = '.intval($parent);
864
        }
865
866
        $result = Database::query($sql);
867
        $number = Database::fetch_row($result);
868
869
        return $number[0] != 0;
870
    }
871
872
    /**
873
     * Checks if the certificate is available for the given user in this category
874
     * @param   integer    $user_id User ID
875
     * @return  boolean    True if conditions match, false if fails
876
     */
877
    public function is_certificate_available($user_id)
878
    {
879
        $score = $this->calc_score(
880
            $user_id,
881
            null,
882
            $this->course_code,
883
            $this->session_id
884
        );
885
886
        if (isset($score) && isset($score[0])) {
887
            // Get a percentage score to compare to minimum certificate score
888
            // $certification_score = $score[0] / $score[1] * 100;
889
            // Get real score not a percentage.
890
            $certification_score = $score[0];
891
            if ($certification_score >= $this->certificate_min_score) {
892
                return true;
893
            }
894
        }
895
896
        return false;
897
    }
898
899
    /**
900
     * Is this category a course ?
901
     * A category is a course if it has a course code and no parent category.
902
     */
903
    public function is_course()
904
    {
905
        return (isset($this->course_code) && !empty($this->course_code)
906
            && (!isset($this->parent) || $this->parent == 0));
907
    }
908
909
    /**
910
     * Calculate the score of this category
911
     * @param integer $stud_id student id (default: all students - then the average is returned)
912
     * @param integer $session_id
913
     * @param string $course_code
914
     * @param int $session_id
915
     * @return    array (score sum, weight sum)
916
     *             or null if no scores available
917
     */
918
    public function calc_score(
919
        $stud_id = null,
920
        $type = null,
921
        $course_code = '',
922
        $session_id = null
923
    ) {
924
        // Classic
925
        if (!empty($stud_id) && $type == '') {
926
            if (!empty($course_code)) {
927
                $cats = $this->get_subcategories(
928
                    $stud_id,
929
                    $course_code,
930
                    $session_id
931
                );
932
                $evals = $this->get_evaluations($stud_id, false, $course_code);
933
                $links = $this->get_links($stud_id, false, $course_code);
934
            } else {
935
                $cats = $this->get_subcategories($stud_id);
936
                $evals = $this->get_evaluations($stud_id);
937
                $links = $this->get_links($stud_id);
938
            }
939
940
            // Calculate score
941
            $count = 0;
942
            $ressum = 0;
943
            $weightsum = 0;
944
945 View Code Duplication
            if (!empty($cats)) {
946
                /** @var Category $cat */
947
                foreach ($cats as $cat) {
948
                    $cat->set_session_id($session_id);
949
                    $cat->set_course_code($course_code);
950
                    $cat->setStudentList($this->getStudentList());
951
                    $score = $cat->calc_score(
952
                        $stud_id,
953
                        null,
954
                        $course_code,
955
                        $session_id
956
                    );
957
958
                    $catweight = 0;
959
                    if ($cat->get_weight() != 0) {
960
                        $catweight = $cat->get_weight();
961
                        $weightsum += $catweight;
962
                    }
963
964
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
965
                        $ressum += $score[0] / $score[1] * $catweight;
966
                    }
967
                }
968
            }
969
970
            $students = array();
971
            if (!empty($evals)) {
972
                /** @var Evaluation $eval */
973
                foreach ($evals as $eval) {
974
                    $eval->setStudentList($this->getStudentList());
975
                    $evalres = $eval->calc_score($stud_id, null);
976
977
                    if (isset($evalres) && $eval->get_weight() != 0) {
978
                        $evalweight = $eval->get_weight();
979
                        $weightsum += $evalweight;
980
                        $count++;
981 View Code Duplication
                        if (!empty($evalres[1])) {
982
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
983
                        }
984
                    } else {
985
                        if ($eval->get_weight() != 0) {
986
                            $evalweight = $eval->get_weight();
987
                            $weightsum += $evalweight;
988
                        }
989
                    }
990
                }
991
            }
992
993
            if (!empty($links)) {
994
                /** @var EvalLink|ExerciseLink $link */
995
                foreach ($links as $link) {
996
                    $link->setStudentList($this->getStudentList());
997
998
                    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 zero. 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...
999
                        $link->set_session_id($session_id);
1000
                    }
1001
1002
                    $linkres = $link->calc_score($stud_id, null);
1003
                    if (!empty($linkres) && $link->get_weight() != 0) {
1004
                        $students[$stud_id] = $linkres[0];
1005
                        $linkweight = $link->get_weight();
1006
                        $link_res_denom = $linkres[1] == 0 ? 1 : $linkres[1];
1007
                        $count++;
1008
                        $weightsum += $linkweight;
1009
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1010
                    } else {
1011
                        // Adding if result does not exists
1012
                        if ($link->get_weight() != 0) {
1013
                            $linkweight = $link->get_weight();
1014
                            $weightsum += $linkweight;
1015
                        }
1016
                    }
1017
                }
1018
            }
1019
        } else {
1020
            if (!empty($course_code)) {
1021
                $cats = $this->get_subcategories(
1022
                    null,
1023
                    $course_code,
1024
                    $session_id
1025
                );
1026
                $evals = $this->get_evaluations(null, false, $course_code);
1027
                $links = $this->get_links(null, false, $course_code);
1028
            } else {
1029
                $cats = $this->get_subcategories(null);
1030
                $evals = $this->get_evaluations(null);
1031
                $links = $this->get_links(null);
1032
            }
1033
1034
            // Calculate score
1035
            $count = 0;
1036
            $ressum = 0;
1037
            $weightsum = 0;
1038
            $bestResult = 0;
1039
1040 View Code Duplication
            if (!empty($cats)) {
1041
                /** @var Category $cat */
1042
                foreach ($cats as $cat) {
1043
                    $cat->setStudentList($this->getStudentList());
1044
                    $score = $cat->calc_score(
1045
                        null,
1046
                        $type,
1047
                        $course_code,
1048
                        $session_id
1049
                    );
1050
1051
                    $catweight = 0;
1052
                    if ($cat->get_weight() != 0) {
1053
                        $catweight = $cat->get_weight();
1054
                        $weightsum += $catweight;
1055
                    }
1056
1057
                    if (isset($score) && !empty($score[1]) && !empty($catweight)) {
1058
                        $ressum += $score[0] / $score[1] * $catweight;
1059
1060
                        if ($ressum > $bestResult) {
1061
                            $bestResult = $ressum;
1062
                        }
1063
                    }
1064
1065
                }
1066
            }
1067
1068
            if (!empty($evals)) {
1069
                /** @var Evaluation $eval */
1070
                foreach ($evals as $eval) {
1071
                    $evalres = $eval->calc_score(null, $type);
1072
                    $eval->setStudentList($this->getStudentList());
1073
1074
                    if (isset($evalres) && $eval->get_weight() != 0) {
1075
                        $evalweight = $eval->get_weight();
1076
                        $weightsum += $evalweight;
1077
                        $count++;
1078 View Code Duplication
                        if (!empty($evalres[1])) {
1079
                            $ressum += $evalres[0] / $evalres[1] * $evalweight;
1080
                        }
1081
1082
                        if ($ressum > $bestResult) {
1083
                            $bestResult = $ressum;
1084
                        }
1085
1086
                    } else {
1087
                        if ($eval->get_weight() != 0) {
1088
                            $evalweight = $eval->get_weight();
1089
                            $weightsum += $evalweight;
1090
                        }
1091
                    }
1092
                }
1093
            }
1094
            if (!empty($links)) {
1095
                /** @var EvalLink|ExerciseLink $link */
1096
                foreach ($links as $link) {
1097
                    $link->setStudentList($this->getStudentList());
1098
1099
                    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 zero. 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...
1100
                        $link->set_session_id($session_id);
1101
                    }
1102
1103
                    $linkres = $link->calc_score($stud_id, $type);
1104
                    if (!empty($linkres) && $link->get_weight() != 0) {
1105
                        $students[$stud_id] = $linkres[0];
1106
                        $linkweight = $link->get_weight();
1107
                        $link_res_denom = $linkres[1] == 0 ? 1 : $linkres[1];
1108
1109
                        $count++;
1110
                        $weightsum += $linkweight;
1111
                        $ressum += $linkres[0] / $link_res_denom * $linkweight;
1112
1113
                        if ($ressum > $bestResult) {
1114
                            $bestResult = $ressum;
1115
                        }
1116
                    } else {
1117
                        // Adding if result does not exists
1118
                        if ($link->get_weight() != 0) {
1119
                            $linkweight = $link->get_weight();
1120
                            $weightsum += $linkweight;
1121
                        }
1122
                    }
1123
                }
1124
            }
1125
        }
1126
1127
        switch ($type) {
1128
            case 'best':
1129
                if (empty($bestResult)) {
1130
                    return null;
1131
                }
1132
                return array($bestResult, $weightsum);
1133
                break;
1134
            case 'average':
1135
                if (empty($ressum)) {
1136
                    return null;
1137
                }
1138
                return array($ressum, $weightsum);
1139
                break;
1140
            case 'ranking':
1141
                // category ranking is calculated in gradebook_data_generator.class.php
1142
                // function get_data
1143
                return null;
1144
                return AbstractLink::getCurrentUserRanking($stud_id, array());
0 ignored issues
show
Unused Code introduced by
return \AbstractLink::ge...ing($stud_id, array()); does not seem to be 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...
1145
                break;
1146
            default:
1147
                return array($ressum, $weightsum);
1148
                break;
1149
        }
1150
    }
1151
1152
    /**
1153
     * Delete this category and every subcategory, evaluation and result inside
1154
     */
1155
    public function delete_all()
1156
    {
1157
        $cats = self::load(null, null, $this->course_code, $this->id, null);
1158
        $evals = Evaluation::load(
1159
            null,
1160
            null,
1161
            $this->course_code,
1162
            $this->id,
1163
            null
1164
        );
1165
1166
        $links = LinkFactory::load(
1167
            null,
1168
            null,
1169
            null,
1170
            null,
1171
            $this->course_code,
1172
            $this->id,
1173
            null
1174
        );
1175
1176
        if (!empty($cats)) {
1177
            /** @var Category $cat */
1178
            foreach ($cats as $cat) {
1179
                $cat->delete_all();
1180
                $cat->delete();
1181
            }
1182
        }
1183
1184
        if (!empty($evals)) {
1185
            /** @var Evaluation $eval */
1186
            foreach ($evals as $eval) {
1187
                $eval->delete_with_results();
1188
            }
1189
        }
1190
1191
        if (!empty($links)) {
1192
            /** @var AbstractLink $link */
1193
            foreach ($links as $link) {
1194
                $link->delete();
1195
            }
1196
        }
1197
1198
        $this->delete();
1199
    }
1200
1201
    /**
1202
     * Return array of Category objects where a student is subscribed to.
1203
     *
1204
     * @param integer $stud_id
1205
     * @param string $course_code
1206
     * @param integer $session_id
1207
     * @return array
1208
     */
1209
    public function get_root_categories_for_student(
1210
        $stud_id,
1211
        $course_code = null,
1212
        $session_id = null
1213
    ) {
1214
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1215
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1216
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1217
1218
        $course_code = Database::escape_string($course_code);
1219
        $session_id = (int) $session_id;
1220
1221
        $sql = "SELECT * FROM $tbl_grade_categories WHERE parent_id = 0";
1222
1223
        if (!api_is_allowed_to_edit()) {
1224
            $sql .= ' AND visible = 1';
1225
            //proceed with checks on optional parameters course & session
1226 View Code Duplication
            if (!empty($course_code)) {
1227
                // TODO: considering it highly improbable that a user would get here
1228
                // if he doesn't have the rights to view this course and this
1229
                // session, we don't check his registration to these, but this
1230
                // could be an improvement
1231
                if (!empty($session_id)) {
1232
                    $sql .= " AND course_code = '".$course_code."' AND session_id = ".$session_id;
1233
                } else {
1234
                    $sql .= " AND course_code = '".$course_code."' AND session_id is null OR session_id=0";
1235
                }
1236
            } else {
1237
                //no optional parameter, proceed as usual
1238
                $sql .= ' AND course_code in
1239
                     (
1240
                        SELECT c.code
1241
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1242
                        ON (cu.c_id = c.id)
1243
                        WHERE cu.user_id = '.intval($stud_id).'
1244
                        AND cu.status = '.STUDENT.'
1245
                    )';
1246
            }
1247
        } elseif (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1248
            //proceed with checks on optional parameters course & session
1249 View Code Duplication
            if (!empty($course_code)) {
1250
                // TODO: considering it highly improbable that a user would get here
1251
                // if he doesn't have the rights to view this course and this
1252
                // session, we don't check his registration to these, but this
1253
                // could be an improvement
1254
                $sql .= " AND course_code  = '".$course_code."'";
1255
                if (!empty($session_id)) {
1256
                    $sql .= " AND session_id = ".$session_id;
1257
                } else {
1258
                    $sql .= "AND session_id IS NULL OR session_id=0";
1259
                }
1260
            } else {
1261
                $sql .= ' AND course_code IN
1262
                     (
1263
                        SELECT c.code
1264
                        FROM '.$main_course_user_table.' cu INNER JOIN '.$courseTable.' c
1265
                        ON (cu.c_id = c.id)
1266
                        WHERE
1267
                            cu.user_id = '.api_get_user_id().' AND
1268
                            cu.status = '.COURSEMANAGER.'
1269
                    )';
1270
            }
1271 View Code Duplication
        } elseif (api_is_platform_admin()) {
1272
            if (isset($session_id) && $session_id != 0) {
1273
                $sql .= ' AND session_id='.$session_id;
1274
            } else {
1275
                $sql .= ' AND coalesce(session_id,0)=0';
1276
            }
1277
        }
1278
        $result = Database::query($sql);
1279
        $cats = self::create_category_objects_from_sql_result($result);
1280
1281
        // course independent categories
1282
        if (empty($course_code)) {
1283
            $cats = self::get_independent_categories_with_result_for_student(
1284
                0,
1285
                $stud_id,
1286
                $cats
1287
            );
1288
        }
1289
1290
        return $cats;
1291
    }
1292
1293
    /**
1294
     * Return array of Category objects where a teacher is admin for.
1295
     *
1296
     * @param integer $user_id (to return everything, use 'null' here)
1297
     * @param string $course_code (optional)
1298
     * @param integer $session_id (optional)
1299
     * @return array
1300
     */
1301
    public function get_root_categories_for_teacher(
1302
        $user_id,
1303
        $course_code = null,
1304
        $session_id = null
1305
    ) {
1306
        if ($user_id == null) {
1307
            return self::load(null, null, $course_code, 0, null, $session_id);
1308
        }
1309
1310
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
1311
        $main_course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1312
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1313
1314
        $sql = 'SELECT * FROM '.$tbl_grade_categories.'
1315
                WHERE parent_id = 0 ';
1316
        if (!empty($course_code)) {
1317
            $sql .= " AND course_code = '".Database::escape_string($course_code)."' ";
1318
            if (!empty($session_id)) {
1319
                $sql .= " AND session_id = ".(int) $session_id;
1320
            }
1321
        } else {
1322
            $sql .= ' AND course_code in
1323
                 (
1324
                    SELECT c.code
1325
                    FROM '.$main_course_user_table.' cu
1326
                    INNER JOIN '.$courseTable.' c
1327
                    ON (cu.c_id = c.id)
1328
                    WHERE user_id = '.intval($user_id).'
1329
                )';
1330
        }
1331
        $result = Database::query($sql);
1332
        $cats = self::create_category_objects_from_sql_result($result);
1333
        // course independent categories
1334
        if (isset($course_code)) {
1335
            $indcats = self::load(
1336
                null,
1337
                $user_id,
1338
                $course_code,
1339
                0,
1340
                null,
1341
                $session_id
1342
            );
1343
            $cats = array_merge($cats, $indcats);
1344
        }
1345
1346
        return $cats;
1347
    }
1348
1349
    /**
1350
     * Can this category be moved to somewhere else ?
1351
     * The root and courses cannot be moved.
1352
     * @return bool
1353
     */
1354
    public function is_movable()
1355
    {
1356
        return !(!isset($this->id) || $this->id == 0 || $this->is_course());
1357
    }
1358
1359
    /**
1360
     * Generate an array of possible categories where this category can be moved to.
1361
     * Notice: its own parent will be included in the list: it's up to the frontend
1362
     * to disable this element.
1363
     * @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
1364
     */
1365
    public function get_target_categories()
1366
    {
1367
        // the root or a course -> not movable
1368
        if (!$this->is_movable()) {
1369
            return null;
1370
        } else {
1371
            // otherwise:
1372
            // - course independent category
1373
            //   -> movable to root or other independent categories
1374
            // - category inside a course
1375
            //   -> movable to root, independent categories or categories inside the course
1376
1377
            $user = api_is_platform_admin() ? null : api_get_user_id();
1378
            $targets = array();
1379
            $level = 0;
1380
1381
            $root = array(0, get_lang('RootCat'), $level);
1382
            $targets[] = $root;
1383
1384 View Code Duplication
            if (isset($this->course_code) && !empty($this->course_code)) {
1385
                $crscats = self::load(null, null, $this->course_code, 0);
1386
                foreach ($crscats as $cat) {
1387
                    if ($this->can_be_moved_to_cat($cat)) {
1388
                        $targets[] = array($cat->get_id(), $cat->get_name(), $level + 1);
1389
                        $targets = $this->add_target_subcategories($targets, $level + 1, $cat->get_id());
1390
                    }
1391
                }
1392
            }
1393
1394
            $indcats = self::load(null, $user, 0, 0);
1395
            foreach ($indcats as $cat) {
1396
                if ($this->can_be_moved_to_cat($cat)) {
1397
                    $targets[] = array($cat->get_id(), $cat->get_name(), $level + 1);
1398
                    $targets = $this->add_target_subcategories($targets, $level + 1, $cat->get_id());
1399
                }
1400
            }
1401
1402
            return $targets;
1403
        }
1404
    }
1405
1406
    /**
1407
     * Internal function used by get_target_categories()
1408
     * @param array $targets
1409
     * @param integer $level
1410
     * @param int $catid
1411
     *
1412
     * @return array
1413
     */
1414 View Code Duplication
    private function add_target_subcategories($targets, $level, $catid)
1415
    {
1416
        $subcats = self::load(null, null, null, $catid);
1417
        foreach ($subcats as $cat) {
1418
            if ($this->can_be_moved_to_cat($cat)) {
1419
                $targets[] = array(
1420
                    $cat->get_id(),
1421
                    $cat->get_name(),
1422
                    $level + 1
1423
                );
1424
                $targets = $this->add_target_subcategories(
1425
                    $targets,
1426
                    $level + 1,
1427
                    $cat->get_id()
1428
                );
1429
            }
1430
        }
1431
1432
        return $targets;
1433
    }
1434
1435
    /**
1436
     * Internal function used by get_target_categories() and add_target_subcategories()
1437
     * Can this category be moved to the given category ?
1438
     * Impossible when origin and target are the same... children won't be processed
1439
     * either. (a category can't be moved to one of its own children)
1440
     */
1441
    private function can_be_moved_to_cat($cat)
1442
    {
1443
        return $cat->get_id() != $this->get_id();
1444
    }
1445
1446
    /**
1447
     * Move this category to the given category.
1448
     * If this category moves from inside a course to outside,
1449
     * its course code must be changed, as well as the course code
1450
     * of all underlying categories and evaluations. All links will
1451
     * be deleted as well !
1452
     */
1453
    public function move_to_cat($cat)
1454
    {
1455
        $this->set_parent_id($cat->get_id());
1456
        if ($this->get_course_code() != $cat->get_course_code()) {
1457
            $this->set_course_code($cat->get_course_code());
1458
            $this->apply_course_code_to_children();
1459
        }
1460
        $this->save();
1461
    }
1462
1463
    /**
1464
     * Internal function used by move_to_cat()
1465
     */
1466
    private function apply_course_code_to_children()
1467
    {
1468
        $cats = self::load(null, null, null, $this->id, null);
1469
        $evals = Evaluation::load(null, null, null, $this->id, null);
1470
        $links = LinkFactory::load(
1471
            null,
1472
            null,
1473
            null,
1474
            null,
1475
            null,
1476
            $this->id,
1477
            null
1478
        );
1479
1480
        foreach ($cats as $cat) {
1481
            $cat->set_course_code($this->get_course_code());
1482
            $cat->save();
1483
            $cat->apply_course_code_to_children();
1484
        }
1485
1486
        foreach ($evals as $eval) {
1487
            $eval->set_course_code($this->get_course_code());
1488
            $eval->save();
1489
        }
1490
1491
        foreach ($links as $link) {
1492
            $link->delete();
1493
        }
1494
    }
1495
1496
    /**
1497
     * Generate an array of all categories the user can navigate to
1498
     */
1499
    public function get_tree()
1500
    {
1501
        $targets = array();
1502
        $level = 0;
1503
        $root = array(0, get_lang('RootCat'), $level);
1504
        $targets[] = $root;
1505
1506
        // course or platform admin
1507
        if (api_is_allowed_to_edit()) {
1508
            $user = api_is_platform_admin() ? null : api_get_user_id();
1509
            $cats = self::get_root_categories_for_teacher($user);
1510
            foreach ($cats as $cat) {
1511
                $targets[] = array($cat->get_id(), $cat->get_name(), $level + 1);
1512
                $targets = self::add_subtree($targets, $level + 1, $cat->get_id(), null);
1513
            }
1514
        } else {
1515
            // student
1516
            $cats = self::get_root_categories_for_student(api_get_user_id());
1517
            foreach ($cats as $cat) {
1518
                $targets[] = array($cat->get_id(), $cat->get_name(), $level + 1);
1519
                $targets = self::add_subtree($targets, $level + 1, $cat->get_id(), 1);
1520
            }
1521
        }
1522
1523
        return $targets;
1524
    }
1525
1526
    /**
1527
     * Internal function used by get_tree()
1528
     * @param integer $level
1529
     * @param null|integer $visible
1530
     * @return array
1531
     */
1532
    private function add_subtree($targets, $level, $catid, $visible)
1533
    {
1534
        $subcats = self::load(null, null, null, $catid, $visible);
0 ignored issues
show
Bug introduced by
It seems like $visible defined by parameter $visible on line 1532 can also be of type integer; however, Category::load() does only seem to accept boolean|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1535
1536
        if (!empty($subcats)) {
1537
            foreach ($subcats as $cat) {
1538
                $targets[] = array(
1539
                    $cat->get_id(),
1540
                    $cat->get_name(),
1541
                    $level + 1
1542
                );
1543
                $targets = self::add_subtree(
1544
                    $targets,
1545
                    $level + 1,
1546
                    $cat->get_id(),
1547
                    $visible
1548
                );
1549
            }
1550
        }
1551
1552
        return $targets;
1553
    }
1554
1555
    /**
1556
     * Generate an array of courses that a teacher hasn't created a category for.
1557
     * @param integer $user_id
1558
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1559
     */
1560
    public function get_not_created_course_categories($user_id)
1561
    {
1562
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1563
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1564
        $tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1565
1566
        $sql = 'SELECT DISTINCT(code), title
1567
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1568
                WHERE 
1569
                    cc.id = cu.c_id AND 
1570
                    cu.status = '.COURSEMANAGER;
1571
        if (!api_is_platform_admin()) {
1572
            $sql .= ' AND cu.user_id = '.$user_id;
1573
        }
1574
        $sql .= ' AND cc.code NOT IN
1575
             (
1576
                SELECT course_code FROM '.$tbl_grade_categories.'
1577
                WHERE
1578
                    parent_id = 0 AND
1579
                    course_code IS NOT NULL
1580
                )';
1581
        $result = Database::query($sql);
1582
1583
        $cats = array();
1584
        while ($data = Database::fetch_array($result)) {
1585
            $cats[] = array($data['code'], $data['title']);
1586
        }
1587
1588
        return $cats;
1589
    }
1590
1591
    /**
1592
     * Generate an array of all courses that a teacher is admin of.
1593
     * @param integer $user_id
1594
     * @return array 2-dimensional array - every element contains 2 subelements (code, title)
1595
     */
1596
    public function get_all_courses($user_id)
1597
    {
1598
        $tbl_main_courses = Database::get_main_table(TABLE_MAIN_COURSE);
1599
        $tbl_main_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1600
        $sql = 'SELECT DISTINCT(code), title
1601
                FROM '.$tbl_main_courses.' cc, '.$tbl_main_course_user.' cu
1602
                WHERE cc.id = cu.c_id AND cu.status = '.COURSEMANAGER;
1603
        if (!api_is_platform_admin()) {
1604
            $sql .= ' AND cu.user_id = '.intval($user_id);
1605
        }
1606
1607
        $result = Database::query($sql);
1608
        $cats = array();
1609
        while ($data = Database::fetch_array($result)) {
1610
            $cats[] = array($data['code'], $data['title']);
1611
        }
1612
1613
        return $cats;
1614
    }
1615
1616
    /**
1617
     * Apply the same visibility to every subcategory, evaluation and link
1618
     */
1619
    public function apply_visibility_to_children()
1620
    {
1621
        $cats = self::load(null, null, null, $this->id, null);
1622
        $evals = Evaluation::load(null, null, null, $this->id, null);
1623
        $links = LinkFactory::load(
1624
            null,
1625
            null,
1626
            null,
1627
            null,
1628
            null,
1629
            $this->id,
1630
            null
1631
        );
1632
        if (!empty($cats)) {
1633
            foreach ($cats as $cat) {
1634
                $cat->set_visible($this->is_visible());
1635
                $cat->save();
1636
                $cat->apply_visibility_to_children();
1637
            }
1638
        }
1639
        if (!empty($evals)) {
1640
            foreach ($evals as $eval) {
1641
                $eval->set_visible($this->is_visible());
1642
                $eval->save();
1643
            }
1644
        }
1645
        if (!empty($links)) {
1646
            foreach ($links as $link) {
1647
                $link->set_visible($this->is_visible());
1648
                $link->save();
1649
            }
1650
        }
1651
    }
1652
1653
    /**
1654
     * Check if a category contains evaluations with a result for a given student
1655
     */
1656
    public function has_evaluations_with_results_for_student($stud_id)
1657
    {
1658
        $evals = Evaluation::get_evaluations_with_result_for_student(
1659
            $this->id,
1660
            $stud_id
1661
        );
1662
        if (count($evals) != 0) {
1663
            return true;
1664
        } else {
1665
            $cats = self::load(
1666
                null,
1667
                null,
1668
                null,
1669
                $this->id,
1670
                api_is_allowed_to_edit() ? null : 1
1671
            );
1672
            foreach ($cats as $cat) {
1673
                if ($cat->has_evaluations_with_results_for_student($stud_id)) {
1674
                    return true;
1675
                }
1676
            }
1677
1678
            return false;
1679
        }
1680
    }
1681
1682
    /**
1683
     * Retrieve all categories inside a course independent category
1684
     * that should be visible to a student.
1685
     * @param integer $cat_id parent category
1686
     * @param $stud_id student id
1687
     * @param array $cats optional: if defined, the categories will be added to this array
1688
     * @return array
1689
     */
1690
    public function get_independent_categories_with_result_for_student(
1691
        $cat_id,
1692
        $stud_id,
1693
        $cats = array()
1694
    ) {
1695
        $creator = api_is_allowed_to_edit() && !api_is_platform_admin() ? api_get_user_id() : null;
1696
1697
        $crsindcats = self::load(
1698
            null,
1699
            $creator,
1700
            '0',
1701
            $cat_id,
1702
            api_is_allowed_to_edit() ? null : 1
1703
        );
1704
1705
        if (!empty($crsindcats)) {
1706
            foreach ($crsindcats as $crsindcat) {
1707
                if ($crsindcat->has_evaluations_with_results_for_student($stud_id)) {
1708
                    $cats[] = $crsindcat;
1709
                }
1710
            }
1711
        }
1712
1713
        return $cats;
1714
    }
1715
1716
    /**
1717
     * Return the session id (in any case, even if it's null or 0)
1718
     * @return  int Session id (can be null)
1719
     */
1720
    public function get_session_id()
1721
    {
1722
        return $this->session_id;
1723
    }
1724
1725
    /**
1726
     * Get appropriate subcategories visible for the user (and optionally the course and session)
1727
     * @param int    $stud_id student id (default: all students)
1728
     * @param string $course_code Course code (optional)
1729
     * @param int    $session_id Session ID (optional)
1730
     * @param bool   $order
1731
1732
     * @return array Array of subcategories
1733
     */
1734
    public function get_subcategories(
1735
        $stud_id = null,
1736
        $course_code = null,
1737
        $session_id = null,
1738
        $order = null
1739
    ) {
1740
        if (!empty($session_id)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

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

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

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

could be turned into

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

This is much more concise to read.

Loading history...
1741
            /*$tbl_grade_categories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1742
            $sql = 'SELECT id FROM '.$tbl_grade_categories. ' WHERE session_id = '.$session_id;
1743
            $result_session = Database::query($sql);
1744
            if (Database::num_rows($result_session) > 0) {
1745
                $data_session = Database::fetch_array($result_session);
1746
                $parent_id = $data_session['id'];
1747
                return self::load(null, null, null, $parent_id, null, null, $order);
1748
            }*/
1749
        }
1750
1751
        // 1 student
1752
        if (isset($stud_id)) {
1753
            // Special case: this is the root
1754
            if ($this->id == 0) {
1755
                return self::get_root_categories_for_student($stud_id, $course_code, $session_id);
1756
            } else {
1757
                return self::load(
1758
                    null,
1759
                    null,
1760
                    $course_code,
1761
                    $this->id,
1762
                    api_is_allowed_to_edit() ? null : 1,
1763
                    $session_id,
1764
                    $order
1765
                );
1766
            }
1767
        } else {
1768
            // All students
1769
            // Course admin
1770
            if (api_is_allowed_to_edit() && !api_is_platform_admin()) {
1771
1772
                // root
1773
                if ($this->id == 0) {
1774
                    return $this->get_root_categories_for_teacher(api_get_user_id(), $course_code, $session_id, false);
1775
                    // inside a course
1776
                } elseif (!empty($this->course_code)) {
1777
                    return self::load(null, null, $this->course_code, $this->id, null, $session_id, $order);
1778
                } elseif (!empty($course_code)) {
1779
                    return self::load(null, null, $course_code, $this->id, null, $session_id, $order);
1780
                    // course independent
1781
                } else {
1782
                    return self::load(null, api_get_user_id(), 0, $this->id, null);
1783
                }
1784
            } elseif (api_is_platform_admin()) {
1785
                // platform admin
1786
                // we explicitly avoid listing subcats from another session
1787
                return self::load(null, null, $course_code, $this->id, null, $session_id, $order);
1788
            }
1789
        }
1790
1791
        return array();
1792
    }
1793
1794
    /**
1795
     * Get appropriate evaluations visible for the user
1796
     * @param int $stud_id student id (default: all students)
1797
     * @param boolean $recursive process subcategories (default: no recursion)
1798
     * @param string $course_code
1799
     * @param int $sessionId
1800
     *
1801
     * @return array
1802
     */
1803
    public function get_evaluations(
1804
        $stud_id = null,
1805
        $recursive = false,
1806
        $course_code = '',
1807
        $sessionId = 0
1808
    ) {
1809
        $evals = array();
1810
        if (empty($course_code)) {
1811
            $course_code = api_get_course_id();
1812
        }
1813
1814
        if (empty($sessionId)) {
1815
            $sessionId = api_get_session_id();
1816
        }
1817
1818
        // 1 student
1819
        if (isset($stud_id) && !empty($stud_id)) {
1820
            // Special case: this is the root
1821
            if ($this->id == 0) {
1822
                $evals = Evaluation::get_evaluations_with_result_for_student(0, $stud_id);
1823
            } else {
1824
                $evals = Evaluation::load(
1825
                    null,
1826
                    null,
1827
                    $course_code,
1828
                    $this->id,
1829
                    api_is_allowed_to_edit() ? null : 1
1830
                );
1831
            }
1832
        } else {
1833
            // All students
1834
            // course admin
1835
            if ((api_is_allowed_to_edit() || api_is_drh() || api_is_session_admin()) &&
1836
                !api_is_platform_admin()
1837
            ) {
1838
                // root
1839
                if ($this->id == 0) {
1840
                    $evals = Evaluation::load(null, api_get_user_id(), null, $this->id, null);
1841
                } elseif (isset($this->course_code) && !empty($this->course_code)) {
1842
                    // inside a course
1843
                    $evals = Evaluation::load(null, null, $course_code, $this->id, null);
1844
                } else {
1845
                    // course independent
1846
                    $evals = Evaluation::load(null, api_get_user_id(), null, $this->id, null);
1847
                }
1848
            } else {
1849
                $evals = Evaluation::load(null, null, $course_code, $this->id, null);
1850
            }
1851
        }
1852
1853 View Code Duplication
        if ($recursive) {
1854
            $subcats = $this->get_subcategories($stud_id, $course_code, $sessionId);
1855
1856
            if (!empty($subcats)) {
1857
                foreach ($subcats as $subcat) {
1858
                    $subevals = $subcat->get_evaluations($stud_id, true, $course_code);
1859
                    $evals = array_merge($evals, $subevals);
1860
                }
1861
            }
1862
        }
1863
1864
        return $evals;
1865
    }
1866
1867
    /**
1868
     * Get appropriate links visible for the user
1869
     * @param int $stud_id student id (default: all students)
1870
     * @param boolean $recursive process subcategories (default: no recursion)
1871
     * @param string $course_code
1872
     * @param int $sessionId
1873
     *
1874
     * @return array
1875
     */
1876
    public function get_links(
1877
        $stud_id = null,
1878
        $recursive = false,
1879
        $course_code = '',
1880
        $sessionId = 0
1881
    ) {
1882
        $links = array();
1883
1884
        if (empty($course_code)) {
1885
            $course_code = api_get_course_id();
1886
        }
1887
1888
        if (empty($sessionId)) {
1889
            $sessionId = api_get_session_id();
1890
        }
1891
1892
        // no links in root or course independent categories
1893
        if ($this->id == 0) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

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

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

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

could be turned into

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

This is much more concise to read.

Loading history...
1894
        } elseif (isset($stud_id)) {
1895
            // 1 student $stud_id
1896
            $links = LinkFactory::load(
1897
                null,
1898
                null,
1899
                null,
1900
                null,
1901
                empty($this->course_code) ? null : $course_code,
1902
                $this->id,
1903
                api_is_allowed_to_edit() ? null : 1
1904
            );
1905
        } else {
1906
            // All students -> only for course/platform admin
1907
            $links = LinkFactory::load(
1908
                null,
1909
                null,
1910
                null,
1911
                null,
1912
                empty($this->course_code) ? null : $this->course_code,
1913
                $this->id,
1914
                null
1915
            );
1916
        }
1917
1918 View Code Duplication
        if ($recursive) {
1919
            $subcats = $this->get_subcategories(
1920
                $stud_id,
1921
                $course_code,
1922
                $sessionId
1923
            );
1924
            if (!empty($subcats)) {
1925
                /** @var Category $subcat */
1926
                foreach ($subcats as $subcat) {
1927
                    $sublinks = $subcat->get_links(
1928
                        $stud_id,
1929
                        false,
1930
                        $course_code,
1931
                        $sessionId
1932
                    );
1933
                    $links = array_merge($links, $sublinks);
1934
                }
1935
            }
1936
        }
1937
1938
        return $links;
1939
    }
1940
1941
    /**
1942
     * Get all the categories from with the same given direct parent
1943
     * @param int $catId Category parent ID
1944
     * @return array Array of Category objects
1945
     */
1946 View Code Duplication
    public function getCategories($catId)
1947
    {
1948
        $tblGradeCategories = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1949
        $sql = 'SELECT * FROM '.$tblGradeCategories.'
1950
                WHERE parent_id = '.intval($catId);
1951
1952
        $result = Database::query($sql);
1953
        $categories = self::create_category_objects_from_sql_result($result);
1954
1955
        return $categories;
1956
    }
1957
1958
    /**
1959
     * Gets the type for the current object
1960
     * @return string 'C' to represent "Category" object type
1961
     */
1962
    public function get_item_type()
1963
    {
1964
        return 'C';
1965
    }
1966
1967
    /**
1968
     * @param array $skills
1969
     */
1970
    public function set_skills($skills)
1971
    {
1972
        $this->skills = $skills;
1973
    }
1974
1975
    /**
1976
     * @return null
1977
     */
1978
    public function get_date()
1979
    {
1980
        return null;
1981
    }
1982
1983
    /**
1984
     * @return string
1985
     */
1986
    public function get_icon_name()
1987
    {
1988
        return 'cat';
1989
    }
1990
1991
    /**
1992
     * Find category by name
1993
     * @param string $name_mask search string
1994
     * @return array category objects matching the search criterium
1995
     */
1996
    public function find_category($name_mask, $allcat)
1997
    {
1998
        $categories = array();
1999
        foreach ($allcat as $search_cat) {
2000
            if (!(strpos(strtolower($search_cat->get_name()), strtolower($name_mask)) === false)) {
2001
                $categories[] = $search_cat;
2002
            }
2003
        }
2004
2005
        return $categories;
2006
    }
2007
2008
    /**
2009
     * This function, locks a category , only one who can unlock it is
2010
     * the platform administrator.
2011
     * @param int locked 1 or unlocked 0
2012
2013
     * @return boolean|null
2014
     * */
2015 View Code Duplication
    public function lock($locked)
2016
    {
2017
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
2018
        $sql = "UPDATE $table SET locked = '".intval($locked)."'
2019
                WHERE id='".intval($this->id)."'";
2020
        Database::query($sql);
2021
    }
2022
2023
    /**
2024
     * @param $locked
2025
     */
2026
    public function lock_all_items($locked)
2027
    {
2028
        if (api_get_setting('gradebook_locking_enabled') == 'true') {
2029
            $this->lock($locked);
2030
            $evals_to_lock = $this->get_evaluations();
2031
            if (!empty($evals_to_lock)) {
2032
                foreach ($evals_to_lock as $item) {
2033
                    $item->lock($locked);
2034
                }
2035
            }
2036
2037
            $link_to_lock = $this->get_links();
2038
            if (!empty($link_to_lock)) {
2039
                foreach ($link_to_lock as $item) {
2040
                    $item->lock($locked);
2041
                }
2042
            }
2043
2044
            $event_type = LOG_GRADEBOOK_UNLOCKED;
2045
            if ($locked == 1) {
2046
                $event_type = LOG_GRADEBOOK_LOCKED;
2047
            }
2048
            Event::addEvent($event_type, LOG_GRADEBOOK_ID, $this->id);
2049
        }
2050
    }
2051
2052
    /**
2053
     * Generates a certificate for this user if everything matches
2054
     * @param int $category_id
2055
     * @param int $user_id
2056
     * @return bool|string
2057
     */
2058
    public static function register_user_certificate($category_id, $user_id)
2059
    {
2060
        $courseId = api_get_course_int_id();
2061
        $courseCode = api_get_course_id();
2062
        $sessionId = api_get_session_id();
2063
2064
        // Generating the total score for a course
2065
        $cats_course = self::load(
2066
            $category_id,
2067
            null,
2068
            null,
2069
            null,
2070
            null,
2071
            $sessionId,
2072
            false
2073
        );
2074
2075
        /** @var Category $category */
2076
        $category = $cats_course[0];
2077
2078
        //@todo move these in a function
2079
        $sum_categories_weight_array = array();
2080
        if (isset($cats_course) && !empty($cats_course)) {
2081
            $categories = self::load(null, null, null, $category_id);
2082
            if (!empty($categories)) {
2083
                foreach ($categories as $subCategory) {
2084
                    $sum_categories_weight_array[$subCategory->get_id()] = $subCategory->get_weight();
2085
                }
2086
            } else {
2087
                $sum_categories_weight_array[$category_id] = $cats_course[0]->get_weight();
2088
            }
2089
        }
2090
2091
        $main_weight = $cats_course[0]->get_weight();
2092
        $cattotal = self::load($category_id);
2093
        $scoretotal = $cattotal[0]->calc_score($user_id);
2094
2095
        // Do not remove this the gradebook/lib/fe/gradebooktable.class.php
2096
        // file load this variable as a global
2097
        $scoredisplay = ScoreDisplay::instance();
2098
        $my_score_in_gradebook = $scoredisplay->display_score($scoretotal, SCORE_SIMPLE);
2099
2100
        // A student always sees only the teacher's repartition
2101
        $scoretotal_display = $scoredisplay->display_score($scoretotal, SCORE_DIV_PERCENT);
2102
2103
        $userFinishedCourse = self::userFinishedCourse(
2104
            $user_id,
2105
            $cats_course[0],
2106
            0,
2107
            $courseCode,
2108
            $sessionId,
2109
            true
2110
        );
2111
2112
        if (!$userFinishedCourse) {
2113
            return false;
2114
        }
2115
2116
        $skillToolEnabled = api_get_setting('allow_skills_tool') == 'true';
2117
        $userHasSkills = false;
2118
2119
        if ($skillToolEnabled) {
2120
            $skill = new Skill();
2121
            $skill->add_skill_to_user(
2122
                $user_id,
2123
                $category_id,
2124
                $courseId,
2125
                $sessionId
2126
            );
2127
2128
            $objSkillRelUser = new SkillRelUser();
2129
            $userSkills = $objSkillRelUser->get_user_skills($user_id, $courseId, $sessionId);
2130
            $userHasSkills = !empty($userSkills);
2131
2132 View Code Duplication
            if (!$category->getGenerateCertificates() && $userHasSkills) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $category->getGenerateCertificates() of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. 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...
2133
                return [
2134
                    'badge_link' => Display::toolbarButton(
2135
                        get_lang('ExportBadges'),
2136
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2137
                        'external-link'
2138
                    ),
2139
                ];
2140
            }
2141
        }
2142
2143
        $my_certificate = GradebookUtils::get_certificate_by_user_id(
2144
            $cats_course[0]->get_id(),
2145
            $user_id
2146
        );
2147
2148
        if (empty($my_certificate)) {
2149
            GradebookUtils::registerUserInfoAboutCertificate(
2150
                $category_id,
2151
                $user_id,
2152
                $my_score_in_gradebook,
2153
                api_get_utc_datetime()
0 ignored issues
show
Bug introduced by
It seems like api_get_utc_datetime() can also be of type null or object<DateTime>; however, GradebookUtils::registerUserInfoAboutCertificate() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2154
            );
2155
            $my_certificate = GradebookUtils::get_certificate_by_user_id(
2156
                $cats_course[0]->get_id(),
2157
                $user_id
2158
            );
2159
        }
2160
2161
        $html = array();
2162
        if (!empty($my_certificate)) {
2163
            $certificate_obj = new Certificate($my_certificate['id']);
2164
            $fileWasGenerated = $certificate_obj->html_file_is_generated();
2165
2166
            if (!empty($fileWasGenerated)) {
2167
                $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$my_certificate['id'];
2168
                $certificates = Display::toolbarButton(
2169
                    get_lang('DisplayCertificate'),
2170
                    $url,
2171
                    'eye',
2172
                    'primary'
2173
                );
2174
2175
                $exportToPDF = Display::url(
2176
                    Display::return_icon(
2177
                        'pdf.png',
2178
                        get_lang('ExportToPDF'),
2179
                        array(),
2180
                        ICON_SIZE_MEDIUM
2181
                    ),
2182
                    "$url&action=export"
2183
                );
2184
2185
                $hideExportLink = api_get_setting('hide_certificate_export_link');
2186
                $hideExportLinkStudent = api_get_setting('hide_certificate_export_link_students');
2187
                if ($hideExportLink === 'true' || (api_is_student() && $hideExportLinkStudent === 'true')) {
2188
                    $exportToPDF = null;
2189
                }
2190
2191
                $html = array(
2192
                    'certificate_link' => $certificates,
2193
                    'pdf_link' => $exportToPDF,
2194
                    'pdf_url' => "$url&action=export",
2195
                );
2196
2197 View Code Duplication
                if ($skillToolEnabled && $userHasSkills) {
2198
                    $html['badge_link'] = Display::toolbarButton(
2199
                        get_lang('ExportBadges'),
2200
                        api_get_path(WEB_CODE_PATH)."gradebook/get_badges.php?user=$user_id",
2201
                        'external-link'
2202
                    );
2203
                }
2204
            }
2205
2206
            return $html;
2207
        }
2208
    }
2209
2210
    /**
2211
     * @param int $catId
2212
     * @param array $userList
2213
     */
2214
    public static function generateCertificatesInUserList($catId, $userList)
2215
    {
2216
        if (!empty($userList)) {
2217
            foreach ($userList as $userInfo) {
2218
                self::register_user_certificate($catId, $userInfo['user_id']);
2219
            }
2220
        }
2221
    }
2222
2223
    /**
2224
     * @param int $catId
2225
     * @param array $userList
2226
     */
2227
    public static function exportAllCertificates(
2228
        $catId,
2229
        $userList = array()
2230
    ) {
2231
        $orientation = api_get_configuration_value('certificate_pdf_orientation');
2232
2233
        $params['orientation'] = 'landscape';
2234
        if (!empty($orientation)) {
2235
            $params['orientation'] = $orientation;
2236
        }
2237
2238
        $params['left'] = 0;
2239
        $params['right'] = 0;
2240
        $params['top'] = 0;
2241
        $params['bottom'] = 0;
2242
        $page_format = $params['orientation'] == 'landscape' ? 'A4-L' : 'A4';
2243
        $pdf = new PDF($page_format, $params['orientation'], $params);
2244
2245
        $certificate_list = GradebookUtils::get_list_users_certificates($catId, $userList);
2246
        $certificate_path_list = array();
2247
2248
        if (!empty($certificate_list)) {
2249
            foreach ($certificate_list as $index=>$value) {
2250
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2251
                    $value['user_id'],
2252
                    $catId
2253
                );
2254
                foreach ($list_certificate as $value_certificate) {
2255
                    $certificate_obj = new Certificate($value_certificate['id']);
2256
                    $certificate_obj->generate(array('hide_print_button' => true));
2257
                    if ($certificate_obj->html_file_is_generated()) {
2258
                        $certificate_path_list[] = $certificate_obj->html_file;
2259
                    }
2260
                }
2261
            }
2262
        }
2263
2264
        if (!empty($certificate_path_list)) {
2265
            // Print certificates (without the common header/footer/watermark
2266
            //  stuff) and return as one multiple-pages PDF
2267
            $pdf->html_to_pdf(
2268
                $certificate_path_list,
2269
                get_lang('Certificates'),
2270
                null,
2271
                false,
2272
                false
2273
            );
2274
        }
2275
    }
2276
2277
    /**
2278
     * @param int $catId
2279
     */
2280
    public static function deleteAllCertificates($catId)
2281
    {
2282
        $certificate_list = GradebookUtils::get_list_users_certificates($catId);
2283
        if (!empty($certificate_list)) {
2284
            foreach ($certificate_list as $index => $value) {
2285
                $list_certificate = GradebookUtils::get_list_gradebook_certificates_by_user_id(
2286
                    $value['user_id'],
2287
                    $catId
2288
                );
2289
                foreach ($list_certificate as $value_certificate) {
2290
                    $certificate_obj = new Certificate($value_certificate['id']);
2291
                    $certificate_obj->delete(true);
2292
                }
2293
            }
2294
        }
2295
    }
2296
2297
    /**
2298
     * Check whether a user has finished a course by its gradebook
2299
     * @param int $userId The user ID
2300
     * @param \Category $category Optional. The gradebook category.
2301
     *         To check by the gradebook category
2302
     * @param int $categoryId Optional. The gradebook category ID.
2303
     *         To check by the category ID
2304
     * @param string $courseCode Optional. The course code
2305
     * @param int $sessionId Optional. The session ID
2306
     * @param boolean $recalcutateScore Whether recalculate the score
2307
     * @return boolean
2308
     */
2309
    public static function userFinishedCourse(
2310
        $userId,
2311
        \Category $category = null,
2312
        $categoryId = 0,
2313
        $courseCode = null,
2314
        $sessionId = 0,
2315
        $recalcutateScore = false
2316
    ) {
2317
        if (is_null($category) && empty($categoryId)) {
2318
            return false;
2319
        }
2320
2321
        $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
2322
        $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
2323
2324
        if (is_null($category) && !empty($categoryId)) {
2325
            $cats_course = self::load(
2326
                $categoryId,
2327
                null,
2328
                $courseCode,
2329
                null,
2330
                null,
2331
                $sessionId,
2332
                false
2333
            );
2334
2335
            if (empty($cats_course)) {
2336
                return false;
2337
            }
2338
2339
            $category = $cats_course[0];
2340
        }
2341
2342
        $currentScore = self::getCurrentScore(
2343
            $userId,
2344
            $category->get_id(),
2345
            $courseCode,
2346
            $sessionId,
2347
            $recalcutateScore
2348
        );
2349
2350
        $minCertificateScore = $category->get_certificate_min_score();
2351
2352
        return !empty($minCertificateScore) && $currentScore >= $minCertificateScore;
2353
    }
2354
2355
    /**
2356
     * Get the current score (as percentage) on a gradebook category for a user
2357
     * @param int $userId The user id
2358
     * @param int $categoryId The gradebook category
2359
     * @param int $courseCode The course code
2360
     * @param int $sessionId Optional. The session id
2361
     * @param bool $recalculate
2362
     *
2363
     * @return float The score
2364
     */
2365
    public static function getCurrentScore(
2366
        $userId,
2367
        $categoryId,
2368
        $courseCode,
2369
        $sessionId = 0,
2370
        $recalculate = false
2371
    ) {
2372
        if ($recalculate) {
2373
            return self::calculateCurrentScore($userId, $categoryId, $courseCode, $sessionId);
2374
        }
2375
2376
        $resultData = Database::select(
2377
            '*',
2378
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2379
            [
2380
                'where' => [
2381
                    'category_id = ? AND user_id = ?' => [$categoryId, $userId],
2382
                ],
2383
                'order' => 'registered_at DESC',
2384
                'limit' => '1',
2385
            ],
2386
            'first'
2387
        );
2388
2389
        if (empty($resultData)) {
2390
            return 0;
2391
        }
2392
2393
        return $resultData['score'];
2394
    }
2395
2396
    /**
2397
     * Calculate the current score on a gradebook category for a user
2398
     * @param int $userId The user id
2399
     * @param int $categoryId The gradebook category
2400
     * @param int $courseCode The course code
2401
     * @param int $sessionId Optional. The session id
2402
     * @return float The score
2403
     */
2404
    private static function calculateCurrentScore($userId, $categoryId, $courseCode, $sessionId)
2405
    {
2406
        $cats_course = self::load(
2407
            $categoryId,
2408
            null,
2409
            $courseCode,
2410
            null,
2411
            null,
2412
            $sessionId,
2413
            false
2414
        );
2415
2416
        if (empty($cats_course)) {
2417
            return 0;
2418
        }
2419
2420
        $category = $cats_course[0];
2421
        $courseEvaluations = $category->get_evaluations($userId, true);
2422
        $courseLinks = $category->get_links($userId, true);
2423
        $evaluationsAndLinks = array_merge($courseEvaluations, $courseLinks);
2424
        $categoryScore = 0;
2425
        for ($i = 0; $i < count($evaluationsAndLinks); $i++) {
2426
            $item = $evaluationsAndLinks[$i];
2427
            $score = $item->calc_score($userId);
2428
            $itemValue = 0;
2429
2430
            if (!empty($score)) {
2431
                $divider = $score[1] == 0 ? 1 : $score[1];
2432
                $itemValue = $score[0] / $divider * $item->get_weight();
2433
            }
2434
2435
            $categoryScore += $itemValue;
2436
        }
2437
2438
        return api_float_val($categoryScore);
2439
    }
2440
2441
    /**
2442
     * Register the current score for a user on a category gradebook
2443
     * @param float $score The achieved score
2444
     * @param int $userId The user id
2445
     * @param int $categoryId The gradebook category
2446
     * @return false|string The insert id
2447
     */
2448
    public static function registerCurrentScore($score, $userId, $categoryId)
2449
    {
2450
        return Database::insert(
2451
            Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_LOG),
2452
            [
2453
                'category_id' => intval($categoryId),
2454
                'user_id' => intval($userId),
2455
                'score' => api_float_val($score),
2456
                'registered_at' => api_get_utc_datetime(),
2457
            ]
2458
        );
2459
    }
2460
2461
    /**
2462
     * @return array
2463
     */
2464
    public function getStudentList()
2465
    {
2466
        return $this->studentList;
2467
    }
2468
2469
    /**
2470
     * @param array $list
2471
     */
2472
    public function setStudentList($list)
2473
    {
2474
        $this->studentList = $list;
2475
    }
2476
2477
    /**
2478
     * @return int
2479
     */
2480
    public function getCourseId()
2481
    {
2482
        return $this->courseId;
2483
    }
2484
2485
    /**
2486
     * @param int $courseId
2487
     * @return Category
2488
     */
2489
    public function setCourseId($courseId)
2490
    {
2491
        $this->courseId = $courseId;
2492
2493
        return $this;
2494
    }
2495
2496
}
2497