Completed
Push — master ( d0e06e...1fcdba )
by Julito
09:05
created

ScoreDisplay   F

Complexity

Total Complexity 106

Size/Duplication

Total Lines 610
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 252
dl 0
loc 610
rs 2
c 0
b 0
f 0
wmc 106

24 Methods

Rating   Name   Duplication   Size   Complexity  
A get_custom_score_display_settings() 0 3 1
A is_upperlimit_included() 0 3 1
A get_color_split_value() 0 3 1
A is_coloring_enabled() 0 3 1
A is_custom() 0 3 1
A instance() 0 8 2
A display_as_div() 0 14 5
F __construct() 0 61 16
A display_simple_score() 0 7 2
C display_default() 0 46 13
A get_current_gradebook_category_id() 0 17 2
A updateCustomScoreDisplaySettings() 0 27 3
B display_custom() 0 20 10
A get_score_color_percent() 0 20 3
B convert_displays() 0 33 8
A sort_display() 0 6 3
A display_as_percent() 0 8 3
A display_as_decimal() 0 5 2
A compare_scores_by_custom_display() 0 18 6
A get_number_decimals() 0 8 2
A format_score() 0 8 2
C display_score() 0 45 14
A insert_defaults() 0 25 3
A get_custom_displays() 0 14 2

How to fix   Complexity   

Complex Class

Complex classes like ScoreDisplay often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ScoreDisplay, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
/**
6
 * Class ScoreDisplay
7
 * Display scores according to the settings made by the platform admin.
8
 * This class works as a singleton: call instance() to retrieve an object.
9
 *
10
 * @author Bert Steppé
11
 */
12
class ScoreDisplay
13
{
14
    private $coloring_enabled;
15
    private $color_split_value;
16
    private $custom_enabled;
17
    private $upperlimit_included;
18
    private $custom_display;
19
    private $custom_display_conv;
20
21
    /**
22
     * Protected constructor - call instance() to instantiate.
23
     *
24
     * @param int $category_id
25
     */
26
    public function __construct($category_id = 0)
27
    {
28
        if (!empty($category_id)) {
29
            $this->category_id = $category_id;
30
        }
31
32
        // Loading portal settings + using standard functions.
33
        $value = api_get_setting('gradebook_score_display_coloring');
34
35
        // Setting coloring.
36
        $this->coloring_enabled = 'true' == $value ? true : false;
37
38
        if ($this->coloring_enabled) {
39
            $value = api_get_setting('gradebook_score_display_colorsplit');
40
            if (isset($value)) {
41
                $this->color_split_value = $value;
42
            }
43
        }
44
45
        // Setting custom enabled
46
        $value = api_get_setting('gradebook_score_display_custom');
47
        $this->custom_enabled = 'true' == $value ? true : false;
48
49
        if ($this->custom_enabled) {
50
            $params = ['category = ?' => ['Gradebook']];
51
            $displays = api_get_settings_params($params);
52
            $portal_displays = [];
53
            if (!empty($displays)) {
54
                foreach ($displays as $display) {
55
                    $data = explode('::', $display['selected_value']);
56
                    if (empty($data[1])) {
57
                        $data[1] = '';
58
                    }
59
                    $portal_displays[$data[0]] = [
60
                        'score' => $data[0],
61
                        'display' => $data[1],
62
                    ];
63
                }
64
                sort($portal_displays);
65
            }
66
            $this->custom_display = $portal_displays;
67
            if (count($this->custom_display) > 0) {
68
                $value = api_get_setting('gradebook_score_display_upperlimit');
69
                $value = $value['my_display_upperlimit'];
70
                $this->upperlimit_included = 'true' == $value ? true : false;
71
                $this->custom_display_conv = $this->convert_displays($this->custom_display);
72
            }
73
        }
74
75
        //If teachers can override the portal parameters
76
        if ('true' == api_get_setting('teachers_can_change_score_settings')) {
77
            //Load course settings
78
            if ($this->custom_enabled) {
79
                $this->custom_display = $this->get_custom_displays();
80
                if (count($this->custom_display) > 0) {
81
                    $this->custom_display_conv = $this->convert_displays($this->custom_display);
82
                }
83
            }
84
85
            if ($this->coloring_enabled) {
86
                $this->color_split_value = $this->get_score_color_percent();
87
            }
88
        }
89
    }
90
91
    /**
92
     * Get the instance of this class.
93
     *
94
     * @param int $categoryId
95
     *
96
     * @return ScoreDisplay
97
     */
98
    public static function instance($categoryId = 0)
99
    {
100
        static $instance;
101
        if (!isset($instance)) {
102
            $instance = new ScoreDisplay($categoryId);
103
        }
104
105
        return $instance;
106
    }
107
108
    /**
109
     * Compare the custom display of 2 scores, can be useful in sorting.
110
     */
111
    public static function compare_scores_by_custom_display($score1, $score2)
112
    {
113
        if (!isset($score1)) {
114
            return isset($score2) ? 1 : 0;
115
        }
116
117
        if (!isset($score2)) {
118
            return -1;
119
        }
120
121
        $scoreDisplay = self::instance();
122
        $custom1 = $scoreDisplay->display_custom($score1);
123
        $custom2 = $scoreDisplay->display_custom($score2);
124
        if ($custom1 == $custom2) {
125
            return 0;
126
        }
127
128
        return ($score1[0] / $score1[1]) < ($score2[0] / $score2[1]) ? -1 : 1;
129
    }
130
131
    /**
132
     * Is coloring enabled ?
133
     */
134
    public function is_coloring_enabled()
135
    {
136
        return $this->coloring_enabled;
137
    }
138
139
    /**
140
     * Is custom score display enabled ?
141
     */
142
    public function is_custom()
143
    {
144
        return $this->custom_enabled;
145
    }
146
147
    /**
148
     * Is upperlimit included ?
149
     */
150
    public function is_upperlimit_included()
151
    {
152
        return $this->upperlimit_included;
153
    }
154
155
    /**
156
     * If custom score display is enabled, this will return the current settings.
157
     * See also updateCustomScoreDisplaySettings.
158
     *
159
     * @return array current settings (or null if feature not enabled)
160
     */
161
    public function get_custom_score_display_settings()
162
    {
163
        return $this->custom_display;
164
    }
165
166
    /**
167
     * If coloring is enabled, scores below this value will be displayed in red.
168
     *
169
     * @return int color split value, in percent (or null if feature not enabled)
170
     */
171
    public function get_color_split_value()
172
    {
173
        return $this->color_split_value;
174
    }
175
176
    /**
177
     * Update custom score display settings.
178
     *
179
     * @param array $displays 2-dimensional array - every sub array must have keys (score, display)
180
     * @param int   score color percent (optional)
181
     * @param int   gradebook category id (optional)
182
     */
183
    public function updateCustomScoreDisplaySettings(
184
        $displays,
185
        $scorecolpercent = 0,
186
        $category_id = null
187
    ) {
188
        $this->custom_display = $displays;
189
        $this->custom_display_conv = $this->convert_displays($this->custom_display);
190
        if (isset($category_id)) {
191
            $category_id = (int) $category_id;
192
        } else {
193
            $category_id = $this->get_current_gradebook_category_id();
194
        }
195
196
        // remove previous settings
197
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
198
        $sql = 'DELETE FROM '.$table.' WHERE category_id = '.$category_id;
199
        Database::query($sql);
200
201
        // add new settings
202
        foreach ($displays as $display) {
203
            $params = [
204
                'score' => $display['score'],
205
                'display' => $display['display'],
206
                'category_id' => $category_id,
207
                'score_color_percent' => $scorecolpercent,
208
            ];
209
            Database::insert($table, $params);
210
        }
211
    }
212
213
    /**
214
     * @param int $category_id
215
     *
216
     * @return false|null
217
     */
218
    public function insert_defaults($category_id)
219
    {
220
        if (empty($category_id)) {
221
            return false;
222
        }
223
224
        //Get this from DB settings
225
        $display = [
226
            50 => get_lang('Failed'),
227
            60 => get_lang('Poor'),
228
            70 => get_lang('Fair'),
229
            80 => get_lang('Good'),
230
            90 => get_lang('Outstanding'),
231
            100 => get_lang('Excellent'),
232
        ];
233
234
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
235
        foreach ($display as $value => $text) {
236
            $params = [
237
                'score' => $value,
238
                'display' => $text,
239
                'category_id' => $category_id,
240
                'score_color_percent' => 0,
241
            ];
242
            Database::insert($table, $params);
243
        }
244
    }
245
246
    /**
247
     * @return int
248
     */
249
    public function get_number_decimals()
250
    {
251
        $number_decimals = api_get_setting('gradebook_number_decimals');
252
        if (!isset($number_decimals)) {
253
            $number_decimals = 0;
254
        }
255
256
        return $number_decimals;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $number_decimals also could return the type string which is incompatible with the documented return type integer.
Loading history...
257
    }
258
259
    /**
260
     * Formats a number depending of the number of decimals.
261
     *
262
     * @param float  $score
263
     * @param bool   $ignoreDecimals
264
     * @param string $decimalSeparator
265
     * @param string $thousandSeparator
266
     *
267
     * @return float the score formatted
268
     */
269
    public function format_score($score, $ignoreDecimals = false, $decimalSeparator = '.', $thousandSeparator = ',')
270
    {
271
        $decimals = $this->get_number_decimals();
272
        if ($ignoreDecimals) {
273
            $decimals = 0;
274
        }
275
276
        return api_number_format($score, $decimals, $decimalSeparator, $thousandSeparator);
277
    }
278
279
    /**
280
     * Display a score according to the current settings.
281
     *
282
     * @param array $score          data structure, as returned by the calc_score functions
283
     * @param int   $type           one of the following constants:
284
     *                              SCORE_DIV, SCORE_PERCENT, SCORE_DIV_PERCENT, SCORE_AVERAGE
285
     *                              (ignored for student's view if custom score display is enabled)
286
     * @param int   $what           one of the following constants:
287
     *                              SCORE_BOTH, SCORE_ONLY_DEFAULT, SCORE_ONLY_CUSTOM (default: SCORE_BOTH)
288
     *                              (only taken into account if custom score display is enabled and for course/platform admin)
289
     * @param bool  $disableColor
290
     * @param bool  $ignoreDecimals
291
     *
292
     * @return string
293
     */
294
    public function display_score(
295
        $score,
296
        $type = SCORE_DIV_PERCENT,
297
        $what = SCORE_BOTH,
298
        $disableColor = false,
299
        $ignoreDecimals = false
300
    ) {
301
        $my_score = $score == 0 ? [] : $score;
302
303
        switch ($type) {
304
            case SCORE_BAR:
305
                $percentage = $my_score[0] / $my_score[1] * 100;
306
307
                return Display::bar_progress($percentage);
308
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
309
            case SCORE_NUMERIC:
310
311
                $percentage = $my_score[0] / $my_score[1] * 100;
312
313
                return round($percentage);
314
                break;
315
            case SCORE_SIMPLE:
316
                return $this->format_score($my_score[0], $ignoreDecimals);
317
                break;
318
        }
319
320
        if ($this->custom_enabled && isset($this->custom_display_conv)) {
321
            $display = $this->display_default($my_score, $type, $ignoreDecimals);
322
        } else {
323
            // if no custom display set, use default display
324
            $display = $this->display_default($my_score, $type, $ignoreDecimals);
325
        }
326
        if ($this->coloring_enabled && false == $disableColor) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
327
            $my_score_denom = isset($score[1]) && !empty($score[1]) && $score[1] > 0 ? $score[1] : 1;
328
            $scoreCleaned = isset($score[0]) ? $score[0] : 0;
329
            if (($scoreCleaned / $my_score_denom) < ($this->color_split_value / 100)) {
330
                $display = Display::tag(
331
                    'font',
332
                    $display,
333
                    ['color' => 'red']
334
                );
335
            }
336
        }
337
338
        return $display;
339
    }
340
341
    /**
342
     * Get current gradebook category id.
343
     *
344
     * @return int Category id
345
     */
346
    private function get_current_gradebook_category_id()
347
    {
348
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
349
        $courseId = api_get_course_int_id();
350
        $sessionId = api_get_session_id();
351
        $sessionCondition = api_get_session_condition($sessionId, true);
352
353
        $sql = "SELECT id FROM $table
354
                WHERE c_id = '$courseId'  $sessionCondition";
355
        $rs = Database::query($sql);
356
        $categoryId = 0;
357
        if (Database::num_rows($rs) > 0) {
358
            $row = Database::fetch_row($rs);
359
            $categoryId = $row[0];
360
        }
361
362
        return $categoryId;
363
    }
364
365
    /**
366
     * @param $score
367
     * @param int  $type
368
     * @param bool $ignoreDecimals
369
     *
370
     * @return string
371
     */
372
    private function display_default($score, $type, $ignoreDecimals = false)
373
    {
374
        switch ($type) {
375
            case SCORE_DIV:                            // X / Y
376
                return $this->display_as_div($score, $ignoreDecimals);
377
            case SCORE_PERCENT:                        // XX %
378
                return $this->display_as_percent($score);
379
            case SCORE_DIV_PERCENT:                    // X / Y (XX %)
380
                return $this->display_as_div($score).' ('.$this->display_as_percent($score).')';
381
            case SCORE_AVERAGE:                        // XX %
382
                return $this->display_as_percent($score);
383
            case SCORE_DECIMAL:                        // 0.50  (X/Y)
384
                return $this->display_as_decimal($score);
385
            case SCORE_DIV_PERCENT_WITH_CUSTOM:        // X / Y (XX %) - Good!
386
                $custom = $this->display_custom($score);
387
                if (!empty($custom)) {
388
                    $custom = ' - '.$custom;
389
                }
390
391
                return $this->display_as_div($score).' ('.$this->display_as_percent($score).')'.$custom;
392
            case SCORE_DIV_SIMPLE_WITH_CUSTOM:         // X - Good!
393
                $custom = $this->display_custom($score);
394
395
                if (!empty($custom)) {
396
                    $custom = ' - '.$custom;
397
                }
398
399
                return $this->display_simple_score($score).$custom;
400
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
401
            case SCORE_DIV_SIMPLE_WITH_CUSTOM_LETTERS:
402
                $custom = $this->display_custom($score);
403
                if (!empty($custom)) {
404
                    $custom = ' - '.$custom;
405
                }
406
                $score = $this->display_simple_score($score);
407
408
                $iso = api_get_language_isocode();
409
                $f = new NumberFormatter($iso, NumberFormatter::SPELLOUT);
410
                $letters = $f->format($score);
411
                $letters = api_strtoupper($letters);
412
                $letters = " ($letters) ";
413
414
                return $score.$letters.$custom;
415
                break;
416
            case SCORE_CUSTOM:                          // Good!
417
                return $this->display_custom($score);
418
        }
419
    }
420
421
    /**
422
     * @param array $score
423
     *
424
     * @return float|string
425
     */
426
    private function display_simple_score($score)
427
    {
428
        if (isset($score[0])) {
429
            return $this->format_score($score[0]);
430
        }
431
432
        return '';
433
    }
434
435
    /**
436
     * Returns "1" for array("100", "100");.
437
     *
438
     * @param array $score
439
     *
440
     * @return float
441
     */
442
    private function display_as_decimal($score)
443
    {
444
        $score_denom = (0 == $score[1]) ? 1 : $score[1];
445
446
        return $this->format_score($score[0] / $score_denom);
447
    }
448
449
    /**
450
     * Returns "100 %" for array("100", "100");.
451
     */
452
    private function display_as_percent($score)
453
    {
454
        if (empty($score)) {
455
            return null;
456
        }
457
        $score_denom = ($score[1] == 0) ? 1 : $score[1];
458
459
        return $this->format_score($score[0] / $score_denom * 100).' %';
460
    }
461
462
    /**
463
     * Returns 10.00 / 10.00 for array("100", "100");.
464
     *
465
     * @param array $score
466
     * @param bool  $ignoreDecimals
467
     *
468
     * @return string
469
     */
470
    private function display_as_div($score, $ignoreDecimals = false)
471
    {
472
        if ($score == 1) {
473
            return '0 / 0';
474
        }
475
476
        if (empty($score)) {
477
            return '0 / 0';
478
        }
479
480
        $score[0] = isset($score[0]) ? $this->format_score($score[0], $ignoreDecimals) : 0;
481
        $score[1] = isset($score[1]) ? $this->format_score($score[1], $ignoreDecimals) : 0;
482
483
        return  $score[0].' / '.$score[1];
484
    }
485
486
    /**
487
     * Depends on the teacher's configuration of thresholds. i.e. [0 50] "Bad", [50:100] "Good".
488
     *
489
     * @param array $score
490
     *
491
     * @return string
492
     */
493
    private function display_custom($score)
494
    {
495
        if (empty($score)) {
496
            return null;
497
        }
498
499
        $my_score_denom = $score[1] == 0 ? 1 : $score[1];
500
        $scaledscore = $score[0] / $my_score_denom;
501
502
        if ($this->upperlimit_included) {
503
            foreach ($this->custom_display_conv as $displayitem) {
504
                if ($scaledscore <= $displayitem['score']) {
505
                    return $displayitem['display'];
506
                }
507
            }
508
        } else {
509
            if (!empty($this->custom_display_conv)) {
510
                foreach ($this->custom_display_conv as $displayitem) {
511
                    if ($scaledscore < $displayitem['score'] || 1 == $displayitem['score']) {
512
                        return $displayitem['display'];
513
                    }
514
                }
515
            }
516
        }
517
    }
518
519
    /**
520
     * Get score color percent by category.
521
     *
522
     * @param   int Gradebook category id
523
     *
524
     * @return int Score
525
     */
526
    private function get_score_color_percent($category_id = null)
527
    {
528
        $tbl_display = Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
529
        if (isset($category_id)) {
530
            $category_id = (int) $category_id;
531
        } else {
532
            $category_id = $this->get_current_gradebook_category_id();
533
        }
534
535
        $sql = 'SELECT score_color_percent FROM '.$tbl_display.'
536
                WHERE category_id = '.$category_id.'
537
                LIMIT 1';
538
        $result = Database::query($sql);
539
        $score = 0;
540
        if (Database::num_rows($result) > 0) {
541
            $row = Database::fetch_row($result);
542
            $score = $row[0];
543
        }
544
545
        return $score;
546
    }
547
548
    /**
549
     * Get current custom score display settings.
550
     *
551
     * @param   int     Gradebook category id
552
     *
553
     * @return array 2-dimensional array every element contains 3 subelements (id, score, display)
554
     */
555
    private function get_custom_displays($category_id = null)
556
    {
557
        $tbl_display = Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
558
        if (isset($category_id)) {
559
            $category_id = (int) $category_id;
560
        } else {
561
            $category_id = $this->get_current_gradebook_category_id();
562
        }
563
        $sql = 'SELECT * FROM '.$tbl_display.'
564
                WHERE category_id = '.$category_id.'
565
                ORDER BY score';
566
        $result = Database::query($sql);
567
568
        return Database::store_result($result, 'ASSOC');
569
    }
570
571
    /**
572
     * Convert display settings to internally used values.
573
     */
574
    private function convert_displays($custom_display)
575
    {
576
        if (isset($custom_display)) {
577
            // get highest score entry, and copy each element to a new array
578
            $converted = [];
579
            $highest = 0;
580
            foreach ($custom_display as $element) {
581
                if ($element['score'] > $highest) {
582
                    $highest = $element['score'];
583
                }
584
                $converted[] = $element;
585
            }
586
            // sort the new array (ascending)
587
            usort($converted, ['ScoreDisplay', 'sort_display']);
588
589
            // adjust each score in such a way that
590
            // each score is scaled between 0 and 1
591
            // the highest score in this array will be equal to 1
592
            $converted2 = [];
593
            foreach ($converted as $element) {
594
                $newelement = [];
595
                if (isset($highest) && !empty($highest) && $highest > 0) {
596
                    $newelement['score'] = $element['score'] / $highest;
597
                } else {
598
                    $newelement['score'] = 0;
599
                }
600
                $newelement['display'] = $element['display'];
601
                $converted2[] = $newelement;
602
            }
603
604
            return $converted2;
605
        } else {
606
            return null;
607
        }
608
    }
609
610
    /**
611
     * @param array $item1
612
     * @param array $item2
613
     *
614
     * @return int
615
     */
616
    private function sort_display($item1, $item2)
617
    {
618
        if ($item1['score'] === $item2['score']) {
619
            return 0;
620
        } else {
621
            return $item1['score'] < $item2['score'] ? -1 : 1;
622
        }
623
    }
624
}
625