Passed
Push — master ( e65bc8...3bb10f )
by
unknown
17:18 queued 09:44
created

Color   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 470
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 54
eloc 202
c 1
b 0
f 0
dl 0
loc 470
ccs 126
cts 126
cp 1
rs 6.4799

21 Methods

Rating   Name   Duplication   Size   Complexity  
A setARGB() 0 19 5
A getGreen() 0 3 1
A exportArray1() 0 7 1
A setRGB() 0 3 1
A getSharedComponent() 0 15 3
A setHyperlinkTheme() 0 11 3
A setTheme() 0 14 2
A getRed() 0 3 1
A getBlue() 0 3 1
A getHashCode() 0 10 2
A applyFromArray() 0 21 5
A validateColor() 0 17 6
A getStyleArray() 0 6 1
A __construct() 0 8 3
A getTheme() 0 7 2
A indexedColor() 0 16 5
A getHasChanged() 0 7 2
A getRGB() 0 7 2
A getColourComponent() 0 8 4
A getARGB() 0 7 2
A changeBrightness() 0 13 2

How to fix   Complexity   

Complex Class

Complex classes like Color 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 Color, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Style;
4
5
use PhpOffice\PhpSpreadsheet\Theme;
6
7
class Color extends Supervisor
8
{
9
    const NAMED_COLORS = [
10
        'Black',
11
        'White',
12
        'Red',
13
        'Green',
14
        'Blue',
15
        'Yellow',
16
        'Magenta',
17
        'Cyan',
18
    ];
19
20
    // Colors
21
    const COLOR_BLACK = 'FF000000';
22
    const COLOR_WHITE = 'FFFFFFFF';
23
    const COLOR_RED = 'FFFF0000';
24
    const COLOR_DARKRED = 'FF800000';
25
    const COLOR_BLUE = 'FF0000FF';
26
    const COLOR_DARKBLUE = 'FF000080';
27
    const COLOR_GREEN = 'FF00FF00';
28
    const COLOR_DARKGREEN = 'FF008000';
29
    const COLOR_YELLOW = 'FFFFFF00';
30
    const COLOR_DARKYELLOW = 'FF808000';
31
    const COLOR_MAGENTA = 'FFFF00FF';
32
    const COLOR_CYAN = 'FF00FFFF';
33
34
    const NAMED_COLOR_TRANSLATIONS = [
35
        'Black' => self::COLOR_BLACK,
36
        'White' => self::COLOR_WHITE,
37
        'Red' => self::COLOR_RED,
38
        'Green' => self::COLOR_GREEN,
39
        'Blue' => self::COLOR_BLUE,
40
        'Yellow' => self::COLOR_YELLOW,
41
        'Magenta' => self::COLOR_MAGENTA,
42
        'Cyan' => self::COLOR_CYAN,
43
    ];
44
45
    const VALIDATE_ARGB_SIZE = 8;
46
    const VALIDATE_RGB_SIZE = 6;
47
    const VALIDATE_COLOR_6 = '/^[A-F0-9]{6}$/i';
48
    const VALIDATE_COLOR_8 = '/^[A-F0-9]{8}$/i';
49
50
    private const INDEXED_COLORS = [
51
        1 => 'FF000000', //  System Colour #1 - Black
52
        2 => 'FFFFFFFF', //  System Colour #2 - White
53
        3 => 'FFFF0000', //  System Colour #3 - Red
54
        4 => 'FF00FF00', //  System Colour #4 - Green
55
        5 => 'FF0000FF', //  System Colour #5 - Blue
56
        6 => 'FFFFFF00', //  System Colour #6 - Yellow
57
        7 => 'FFFF00FF', //  System Colour #7- Magenta
58
        8 => 'FF00FFFF', //  System Colour #8- Cyan
59
        9 => 'FF800000', //  Standard Colour #9
60
        10 => 'FF008000', //  Standard Colour #10
61
        11 => 'FF000080', //  Standard Colour #11
62
        12 => 'FF808000', //  Standard Colour #12
63
        13 => 'FF800080', //  Standard Colour #13
64
        14 => 'FF008080', //  Standard Colour #14
65
        15 => 'FFC0C0C0', //  Standard Colour #15
66
        16 => 'FF808080', //  Standard Colour #16
67
        17 => 'FF9999FF', //  Chart Fill Colour #17
68
        18 => 'FF993366', //  Chart Fill Colour #18
69
        19 => 'FFFFFFCC', //  Chart Fill Colour #19
70
        20 => 'FFCCFFFF', //  Chart Fill Colour #20
71
        21 => 'FF660066', //  Chart Fill Colour #21
72
        22 => 'FFFF8080', //  Chart Fill Colour #22
73
        23 => 'FF0066CC', //  Chart Fill Colour #23
74
        24 => 'FFCCCCFF', //  Chart Fill Colour #24
75
        25 => 'FF000080', //  Chart Line Colour #25
76
        26 => 'FFFF00FF', //  Chart Line Colour #26
77
        27 => 'FFFFFF00', //  Chart Line Colour #27
78
        28 => 'FF00FFFF', //  Chart Line Colour #28
79
        29 => 'FF800080', //  Chart Line Colour #29
80
        30 => 'FF800000', //  Chart Line Colour #30
81
        31 => 'FF008080', //  Chart Line Colour #31
82
        32 => 'FF0000FF', //  Chart Line Colour #32
83
        33 => 'FF00CCFF', //  Standard Colour #33
84
        34 => 'FFCCFFFF', //  Standard Colour #34
85
        35 => 'FFCCFFCC', //  Standard Colour #35
86
        36 => 'FFFFFF99', //  Standard Colour #36
87
        37 => 'FF99CCFF', //  Standard Colour #37
88
        38 => 'FFFF99CC', //  Standard Colour #38
89
        39 => 'FFCC99FF', //  Standard Colour #39
90
        40 => 'FFFFCC99', //  Standard Colour #40
91
        41 => 'FF3366FF', //  Standard Colour #41
92
        42 => 'FF33CCCC', //  Standard Colour #42
93
        43 => 'FF99CC00', //  Standard Colour #43
94
        44 => 'FFFFCC00', //  Standard Colour #44
95
        45 => 'FFFF9900', //  Standard Colour #45
96
        46 => 'FFFF6600', //  Standard Colour #46
97
        47 => 'FF666699', //  Standard Colour #47
98
        48 => 'FF969696', //  Standard Colour #48
99
        49 => 'FF003366', //  Standard Colour #49
100
        50 => 'FF339966', //  Standard Colour #50
101
        51 => 'FF003300', //  Standard Colour #51
102
        52 => 'FF333300', //  Standard Colour #52
103
        53 => 'FF993300', //  Standard Colour #53
104
        54 => 'FF993366', //  Standard Colour #54
105
        55 => 'FF333399', //  Standard Colour #55
106
        56 => 'FF333333', //  Standard Colour #56
107
    ];
108
109
    /**
110
     * ARGB - Alpha RGB.
111
     */
112
    protected ?string $argb = null;
113
114
    private bool $hasChanged = false;
115
116
    private int $theme = -1;
117
118
    /**
119
     * Create a new Color.
120
     *
121
     * @param string $colorValue ARGB value for the colour, or named colour
122
     * @param bool $isSupervisor Flag indicating if this is a supervisor or not
123
     *                                    Leave this value at default unless you understand exactly what
124
     *                                        its ramifications are
125
     * @param bool $isConditional Flag indicating if this is a conditional style or not
126
     *                                    Leave this value at default unless you understand exactly what
127
     *                                        its ramifications are
128
     */
129 10758
    public function __construct(string $colorValue = self::COLOR_BLACK, bool $isSupervisor = false, bool $isConditional = false)
130
    {
131
        //    Supervisor?
132 10758
        parent::__construct($isSupervisor);
133
134
        //    Initialise values
135 10758
        if (!$isConditional) {
136 10758
            $this->argb = $this->validateColor($colorValue) ?: self::COLOR_BLACK;
137
        }
138
    }
139
140
    /**
141
     * Get the shared style component for the currently active cell in currently active sheet.
142
     * Only used for style supervisor.
143
     */
144 49
    public function getSharedComponent(): self
145
    {
146
        /** @var Style $parent */
147 49
        $parent = $this->parent;
148
        /** @var Border|Fill $sharedComponent */
149 49
        $sharedComponent = $parent->getSharedComponent();
150 49
        if ($sharedComponent instanceof Fill) {
151 39
            if ($this->parentPropertyName === 'endColor') {
152 21
                return $sharedComponent->getEndColor();
153
            }
154
155 38
            return $sharedComponent->getStartColor();
156
        }
157
158 31
        return $sharedComponent->getColor();
159
    }
160
161
    /**
162
     * Build style array from subcomponents.
163
     *
164
     * @param mixed[] $array
165
     *
166
     * @return mixed[]
167
     */
168 51
    public function getStyleArray(array $array): array
169
    {
170
        /** @var Style $parent */
171 51
        $parent = $this->parent;
172
173 51
        return $parent->getStyleArray([$this->parentPropertyName => $array]);
174
    }
175
176
    /**
177
     * Apply styles from array.
178
     *
179
     * <code>
180
     * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray(['rgb' => '808080']);
181
     * </code>
182
     *
183
     * @param array{rgb?: string, argb?: string, theme?: int} $styleArray Array containing style information
184
     *
185
     * @return $this
186
     */
187 183
    public function applyFromArray(array $styleArray): static
188
    {
189 183
        if ($this->isSupervisor) {
190 1
            $this->getActiveSheet()
191 1
                ->getStyle($this->getSelectedCells())
192 1
                ->applyFromArray(
193 1
                    $this->getStyleArray($styleArray)
194 1
                );
195
        } else {
196 183
            if (isset($styleArray['rgb'])) {
197 74
                $this->setRGB($styleArray['rgb']);
198
            }
199 183
            if (isset($styleArray['argb'])) {
200 111
                $this->setARGB($styleArray['argb']);
201
            }
202 183
            if (isset($styleArray['theme'])) {
203 40
                $this->setTheme($styleArray['theme']);
204
            }
205
        }
206
207 183
        return $this;
208
    }
209
210 10758
    private function validateColor(?string $colorValue): string
211
    {
212 10758
        if ($colorValue === null || $colorValue === '') {
213 4
            return self::COLOR_BLACK;
214
        }
215 10758
        $named = ucfirst(strtolower($colorValue));
216 10758
        if (array_key_exists($named, self::NAMED_COLOR_TRANSLATIONS)) {
217 2
            return self::NAMED_COLOR_TRANSLATIONS[$named];
218
        }
219 10758
        if (preg_match(self::VALIDATE_COLOR_8, $colorValue) === 1) {
220 10758
            return $colorValue;
221
        }
222 250
        if (preg_match(self::VALIDATE_COLOR_6, $colorValue) === 1) {
223 235
            return 'FF' . $colorValue;
224
        }
225
226 18
        return '';
227
    }
228
229
    /**
230
     * Get ARGB.
231
     */
232 681
    public function getARGB(): ?string
233
    {
234 681
        if ($this->isSupervisor) {
235 37
            return $this->getSharedComponent()->getARGB();
236
        }
237
238 681
        return $this->argb;
239
    }
240
241
    /**
242
     * Set ARGB.
243
     *
244
     * @param ?string $colorValue  ARGB value, or a named color
245
     *
246
     * @return $this
247
     */
248 992
    public function setARGB(?string $colorValue = self::COLOR_BLACK, bool $nullStringOkay = false): static
249
    {
250 992
        $this->hasChanged = true;
251 992
        $this->setTheme(-1);
252 992
        if (!$nullStringOkay || $colorValue !== '') {
253 992
            $colorValue = $this->validateColor($colorValue);
254 992
            if ($colorValue === '') {
255 18
                return $this;
256
            }
257
        }
258
259 990
        if ($this->isSupervisor) {
260 30
            $styleArray = $this->getStyleArray(['argb' => $colorValue]);
261 30
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
262
        } else {
263 990
            $this->argb = $colorValue;
264
        }
265
266 990
        return $this;
267
    }
268
269
    /**
270
     * Get RGB.
271
     */
272 731
    public function getRGB(): string
273
    {
274 731
        if ($this->isSupervisor) {
275 20
            return $this->getSharedComponent()->getRGB();
276
        }
277
278 731
        return substr($this->argb ?? '', 2);
279
    }
280
281
    /**
282
     * Set RGB.
283
     *
284
     * @param ?string $colorValue RGB value, or a named color
285
     *
286
     * @return $this
287
     */
288 252
    public function setRGB(?string $colorValue = self::COLOR_BLACK): static
289
    {
290 252
        return $this->setARGB($colorValue);
291
    }
292
293
    /**
294
     * Get a specified colour component of an RGB value.
295
     *
296
     * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
297
     * @param int $offset Position within the RGB value to extract
298
     * @param bool $hex Flag indicating whether the component should be returned as a hex or a
299
     *                                    decimal value
300
     *
301
     * @return int|string The extracted colour component
302
     */
303 247
    private static function getColourComponent(string $rgbValue, int $offset, bool $hex = true): string|int
304
    {
305 247
        $colour = substr($rgbValue, $offset, 2) ?: '';
306 247
        if (preg_match('/^[0-9a-f]{2}$/i', $colour) !== 1) {
307 1
            $colour = '00';
308
        }
309
310 247
        return ($hex) ? $colour : (int) hexdec($colour);
311
    }
312
313
    /**
314
     * Get the red colour component of an RGB value.
315
     *
316
     * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
317
     * @param bool $hex Flag indicating whether the component should be returned as a hex or a
318
     *                                    decimal value
319
     *
320
     * @return int|string The red colour component
321
     */
322 234
    public static function getRed(string $rgbValue, bool $hex = true)
323
    {
324 234
        return self::getColourComponent($rgbValue, strlen($rgbValue) - 6, $hex);
325
    }
326
327
    /**
328
     * Get the green colour component of an RGB value.
329
     *
330
     * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
331
     * @param bool $hex Flag indicating whether the component should be returned as a hex or a
332
     *                                    decimal value
333
     *
334
     * @return int|string The green colour component
335
     */
336 234
    public static function getGreen(string $rgbValue, bool $hex = true)
337
    {
338 234
        return self::getColourComponent($rgbValue, strlen($rgbValue) - 4, $hex);
339
    }
340
341
    /**
342
     * Get the blue colour component of an RGB value.
343
     *
344
     * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
345
     * @param bool $hex Flag indicating whether the component should be returned as a hex or a
346
     *                                    decimal value
347
     *
348
     * @return int|string The blue colour component
349
     */
350 235
    public static function getBlue(string $rgbValue, bool $hex = true)
351
    {
352 235
        return self::getColourComponent($rgbValue, strlen($rgbValue) - 2, $hex);
353
    }
354
355
    /**
356
     * Adjust the brightness of a color.
357
     *
358
     * @param string $hexColourValue The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
359
     * @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1
360
     *
361
     * @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
362
     */
363 228
    public static function changeBrightness(string $hexColourValue, float $adjustPercentage): string
364
    {
365 228
        $rgba = (strlen($hexColourValue) === 8);
366 228
        $adjustPercentage = max(-1.0, min(1.0, $adjustPercentage));
367
368
        /** @var int $red */
369 228
        $red = self::getRed($hexColourValue, false);
370
        /** @var int $green */
371 228
        $green = self::getGreen($hexColourValue, false);
372
        /** @var int $blue */
373 228
        $blue = self::getBlue($hexColourValue, false);
374
375 228
        return (($rgba) ? 'FF' : '') . RgbTint::rgbAndTintToRgb($red, $green, $blue, $adjustPercentage);
376
    }
377
378
    /**
379
     * Get indexed color.
380
     *
381
     * @param int $colorIndex Index entry point into the colour array
382
     * @param bool $background Flag to indicate whether default background or foreground colour
383
     *                                            should be returned if the indexed colour doesn't exist
384
     * @param null|string[] $palette
385
     */
386 103
    public static function indexedColor(int $colorIndex, bool $background = false, ?array $palette = null): self
387
    {
388
        // Clean parameter
389 103
        $colorIndex = (int) $colorIndex;
390
391 103
        if (empty($palette)) {
392 100
            if (isset(self::INDEXED_COLORS[$colorIndex])) {
393 45
                return new self(self::INDEXED_COLORS[$colorIndex]);
394
            }
395
        } else {
396 4
            if (isset($palette[$colorIndex])) {
397 4
                return new self($palette[$colorIndex]);
398
            }
399
        }
400
401 69
        return ($background) ? new self(self::COLOR_WHITE) : new self(self::COLOR_BLACK);
402
    }
403
404
    /**
405
     * Get hash code.
406
     *
407
     * @return string Hash code
408
     */
409 1312
    public function getHashCode(): string
410
    {
411 1312
        if ($this->isSupervisor) {
412 1
            return $this->getSharedComponent()->getHashCode();
413
        }
414
415 1312
        return md5(
416 1312
            $this->argb
417 1312
            . (string) $this->theme
418 1312
            . __CLASS__
419 1312
        );
420
    }
421
422
    /** @return mixed[] */
423 14
    protected function exportArray1(): array
424
    {
425 14
        $exportedArray = [];
426 14
        $this->exportArray2($exportedArray, 'argb', $this->getARGB());
427 14
        $this->exportArray2($exportedArray, 'theme', $this->getTheme());
428
429 14
        return $exportedArray;
430
    }
431
432 1259
    public function getHasChanged(): bool
433
    {
434 1259
        if ($this->isSupervisor) {
435 8
            return $this->getSharedComponent()->hasChanged;
436
        }
437
438 1259
        return $this->hasChanged;
439
    }
440
441 394
    public function getTheme(): int
442
    {
443 394
        if ($this->isSupervisor) {
444 9
            return $this->getSharedComponent()->getTheme();
445
        }
446
447 394
        return $this->theme;
448
    }
449
450 992
    public function setTheme(int $theme): self
451
    {
452 992
        $this->hasChanged = true;
453
454 992
        if ($this->isSupervisor) {
455 30
            $styleArray = $this->getStyleArray(['theme' => $theme]);
456 30
            $this->getActiveSheet()
457 30
                ->getStyle($this->getSelectedCells())
458 30
                ->applyFromArray($styleArray);
459
        } else {
460 992
            $this->theme = $theme;
461
        }
462
463 992
        return $this;
464
    }
465
466 19
    public function setHyperlinkTheme(): self
467
    {
468 19
        $rgb = $this->getActiveSheet()
469 19
            ->getParent()
470 19
            ?->getTheme()
471 19
            ->getThemeColors();
472 19
        if (is_array($rgb) && array_key_exists('hlink', $rgb)) {
473 19
            $this->setRGB($rgb['hlink']);
474
        }
475
476 19
        return $this->setTheme(Theme::HYPERLINK_THEME);
477
    }
478
}
479