Test Failed
Push — main ( c8394f...8477f1 )
by Rafael
66:21
created

Style::applyFromArray()   F

Complexity

Conditions 60
Paths > 20000

Size

Total Lines 267
Code Lines 151

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 60
eloc 151
nc 82400
nop 2
dl 0
loc 267
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Style;
4
5
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6
use PhpOffice\PhpSpreadsheet\Spreadsheet;
7
8
class Style extends Supervisor
9
{
10
    /**
11
     * Font.
12
     *
13
     * @var Font
14
     */
15
    protected $font;
16
17
    /**
18
     * Fill.
19
     *
20
     * @var Fill
21
     */
22
    protected $fill;
23
24
    /**
25
     * Borders.
26
     *
27
     * @var Borders
28
     */
29
    protected $borders;
30
31
    /**
32
     * Alignment.
33
     *
34
     * @var Alignment
35
     */
36
    protected $alignment;
37
38
    /**
39
     * Number Format.
40
     *
41
     * @var NumberFormat
42
     */
43
    protected $numberFormat;
44
45
    /**
46
     * Conditional styles.
47
     *
48
     * @var Conditional[]
49
     */
50
    protected $conditionalStyles;
51
52
    /**
53
     * Protection.
54
     *
55
     * @var Protection
56
     */
57
    protected $protection;
58
59
    /**
60
     * Index of style in collection. Only used for real style.
61
     *
62
     * @var int
63
     */
64
    protected $index;
65
66
    /**
67
     * Use Quote Prefix when displaying in cell editor. Only used for real style.
68
     *
69
     * @var bool
70
     */
71
    protected $quotePrefix = false;
72
73
    /**
74
     * Create a new Style.
75
     *
76
     * @param bool $isSupervisor Flag indicating if this is a supervisor or not
77
     *         Leave this value at default unless you understand exactly what
78
     *    its ramifications are
79
     * @param bool $isConditional Flag indicating if this is a conditional style or not
80
     *       Leave this value at default unless you understand exactly what
81
     *    its ramifications are
82
     */
83
    public function __construct($isSupervisor = false, $isConditional = false)
84
    {
85
        parent::__construct($isSupervisor);
86
87
        // Initialise values
88
        $this->conditionalStyles = [];
89
        $this->font = new Font($isSupervisor, $isConditional);
90
        $this->fill = new Fill($isSupervisor, $isConditional);
91
        $this->borders = new Borders($isSupervisor, $isConditional);
92
        $this->alignment = new Alignment($isSupervisor, $isConditional);
93
        $this->numberFormat = new NumberFormat($isSupervisor, $isConditional);
94
        $this->protection = new Protection($isSupervisor, $isConditional);
95
96
        // bind parent if we are a supervisor
97
        if ($isSupervisor) {
98
            $this->font->bindParent($this);
99
            $this->fill->bindParent($this);
100
            $this->borders->bindParent($this);
101
            $this->alignment->bindParent($this);
102
            $this->numberFormat->bindParent($this);
103
            $this->protection->bindParent($this);
104
        }
105
    }
106
107
    /**
108
     * Get the shared style component for the currently active cell in currently active sheet.
109
     * Only used for style supervisor.
110
     *
111
     * @return Style
112
     */
113
    public function getSharedComponent()
114
    {
115
        $activeSheet = $this->getActiveSheet();
116
        $selectedCell = $this->getActiveCell(); // e.g. 'A1'
117
118
        if ($activeSheet->cellExists($selectedCell)) {
119
            $xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex();
120
        } else {
121
            $xfIndex = 0;
122
        }
123
124
        return $this->parent->getCellXfByIndex($xfIndex);
125
    }
126
127
    /**
128
     * Get parent. Only used for style supervisor.
129
     *
130
     * @return Spreadsheet
131
     */
132
    public function getParent()
133
    {
134
        return $this->parent;
135
    }
136
137
    /**
138
     * Build style array from subcomponents.
139
     *
140
     * @param array $array
141
     *
142
     * @return array
143
     */
144
    public function getStyleArray($array)
145
    {
146
        return ['quotePrefix' => $array];
147
    }
148
149
    /**
150
     * Apply styles from array.
151
     *
152
     * <code>
153
     * $spreadsheet->getActiveSheet()->getStyle('B2')->applyFromArray(
154
     *     [
155
     *         'font' => [
156
     *             'name' => 'Arial',
157
     *             'bold' => true,
158
     *             'italic' => false,
159
     *             'underline' => Font::UNDERLINE_DOUBLE,
160
     *             'strikethrough' => false,
161
     *             'color' => [
162
     *                 'rgb' => '808080'
163
     *             ]
164
     *         ],
165
     *         'borders' => [
166
     *             'bottom' => [
167
     *                 'borderStyle' => Border::BORDER_DASHDOT,
168
     *                 'color' => [
169
     *                     'rgb' => '808080'
170
     *                 ]
171
     *             ],
172
     *             'top' => [
173
     *                 'borderStyle' => Border::BORDER_DASHDOT,
174
     *                 'color' => [
175
     *                     'rgb' => '808080'
176
     *                 ]
177
     *             ]
178
     *         ],
179
     *         'alignment' => [
180
     *             'horizontal' => Alignment::HORIZONTAL_CENTER,
181
     *             'vertical' => Alignment::VERTICAL_CENTER,
182
     *             'wrapText' => true,
183
     *         ],
184
     *         'quotePrefix'    => true
185
     *     ]
186
     * );
187
     * </code>
188
     *
189
     * @param array $pStyles Array containing style information
190
     * @param bool $pAdvanced advanced mode for setting borders
191
     *
192
     * @return $this
193
     */
194
    public function applyFromArray(array $pStyles, $pAdvanced = true)
195
    {
196
        if ($this->isSupervisor) {
197
            $pRange = $this->getSelectedCells();
198
199
            // Uppercase coordinate
200
            $pRange = strtoupper($pRange);
201
202
            // Is it a cell range or a single cell?
203
            if (strpos($pRange, ':') === false) {
204
                $rangeA = $pRange;
205
                $rangeB = $pRange;
206
            } else {
207
                [$rangeA, $rangeB] = explode(':', $pRange);
208
            }
209
210
            // Calculate range outer borders
211
            $rangeStart = Coordinate::coordinateFromString($rangeA);
212
            $rangeEnd = Coordinate::coordinateFromString($rangeB);
213
214
            // Translate column into index
215
            $rangeStart[0] = Coordinate::columnIndexFromString($rangeStart[0]);
216
            $rangeEnd[0] = Coordinate::columnIndexFromString($rangeEnd[0]);
217
218
            // Make sure we can loop upwards on rows and columns
219
            if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
220
                $tmp = $rangeStart;
221
                $rangeStart = $rangeEnd;
222
                $rangeEnd = $tmp;
223
            }
224
225
            // ADVANCED MODE:
226
            if ($pAdvanced && isset($pStyles['borders'])) {
227
                // 'allBorders' is a shorthand property for 'outline' and 'inside' and
228
                //        it applies to components that have not been set explicitly
229
                if (isset($pStyles['borders']['allBorders'])) {
230
                    foreach (['outline', 'inside'] as $component) {
231
                        if (!isset($pStyles['borders'][$component])) {
232
                            $pStyles['borders'][$component] = $pStyles['borders']['allBorders'];
233
                        }
234
                    }
235
                    unset($pStyles['borders']['allBorders']); // not needed any more
236
                }
237
                // 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left'
238
                //        it applies to components that have not been set explicitly
239
                if (isset($pStyles['borders']['outline'])) {
240
                    foreach (['top', 'right', 'bottom', 'left'] as $component) {
241
                        if (!isset($pStyles['borders'][$component])) {
242
                            $pStyles['borders'][$component] = $pStyles['borders']['outline'];
243
                        }
244
                    }
245
                    unset($pStyles['borders']['outline']); // not needed any more
246
                }
247
                // 'inside' is a shorthand property for 'vertical' and 'horizontal'
248
                //        it applies to components that have not been set explicitly
249
                if (isset($pStyles['borders']['inside'])) {
250
                    foreach (['vertical', 'horizontal'] as $component) {
251
                        if (!isset($pStyles['borders'][$component])) {
252
                            $pStyles['borders'][$component] = $pStyles['borders']['inside'];
253
                        }
254
                    }
255
                    unset($pStyles['borders']['inside']); // not needed any more
256
                }
257
                // width and height characteristics of selection, 1, 2, or 3 (for 3 or more)
258
                $xMax = min($rangeEnd[0] - $rangeStart[0] + 1, 3);
259
                $yMax = min($rangeEnd[1] - $rangeStart[1] + 1, 3);
260
261
                // loop through up to 3 x 3 = 9 regions
262
                for ($x = 1; $x <= $xMax; ++$x) {
263
                    // start column index for region
264
                    $colStart = ($x == 3) ?
265
                        Coordinate::stringFromColumnIndex($rangeEnd[0])
266
                            : Coordinate::stringFromColumnIndex($rangeStart[0] + $x - 1);
267
                    // end column index for region
268
                    $colEnd = ($x == 1) ?
269
                        Coordinate::stringFromColumnIndex($rangeStart[0])
270
                            : Coordinate::stringFromColumnIndex($rangeEnd[0] - $xMax + $x);
271
272
                    for ($y = 1; $y <= $yMax; ++$y) {
273
                        // which edges are touching the region
274
                        $edges = [];
275
                        if ($x == 1) {
276
                            // are we at left edge
277
                            $edges[] = 'left';
278
                        }
279
                        if ($x == $xMax) {
280
                            // are we at right edge
281
                            $edges[] = 'right';
282
                        }
283
                        if ($y == 1) {
284
                            // are we at top edge?
285
                            $edges[] = 'top';
286
                        }
287
                        if ($y == $yMax) {
288
                            // are we at bottom edge?
289
                            $edges[] = 'bottom';
290
                        }
291
292
                        // start row index for region
293
                        $rowStart = ($y == 3) ?
294
                            $rangeEnd[1] : $rangeStart[1] + $y - 1;
295
296
                        // end row index for region
297
                        $rowEnd = ($y == 1) ?
298
                            $rangeStart[1] : $rangeEnd[1] - $yMax + $y;
299
300
                        // build range for region
301
                        $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd;
302
303
                        // retrieve relevant style array for region
304
                        $regionStyles = $pStyles;
305
                        unset($regionStyles['borders']['inside']);
306
307
                        // what are the inner edges of the region when looking at the selection
308
                        $innerEdges = array_diff(['top', 'right', 'bottom', 'left'], $edges);
309
310
                        // inner edges that are not touching the region should take the 'inside' border properties if they have been set
311
                        foreach ($innerEdges as $innerEdge) {
312
                            switch ($innerEdge) {
313
                                case 'top':
314
                                case 'bottom':
315
                                    // should pick up 'horizontal' border property if set
316
                                    if (isset($pStyles['borders']['horizontal'])) {
317
                                        $regionStyles['borders'][$innerEdge] = $pStyles['borders']['horizontal'];
318
                                    } else {
319
                                        unset($regionStyles['borders'][$innerEdge]);
320
                                    }
321
322
                                    break;
323
                                case 'left':
324
                                case 'right':
325
                                    // should pick up 'vertical' border property if set
326
                                    if (isset($pStyles['borders']['vertical'])) {
327
                                        $regionStyles['borders'][$innerEdge] = $pStyles['borders']['vertical'];
328
                                    } else {
329
                                        unset($regionStyles['borders'][$innerEdge]);
330
                                    }
331
332
                                    break;
333
                            }
334
                        }
335
336
                        // apply region style to region by calling applyFromArray() in simple mode
337
                        $this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false);
338
                    }
339
                }
340
341
                // restore initial cell selection range
342
                $this->getActiveSheet()->getStyle($pRange);
343
344
                return $this;
345
            }
346
347
            // SIMPLE MODE:
348
            // Selection type, inspect
349
            if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) {
350
                $selectionType = 'COLUMN';
351
            } elseif (preg_match('/^A\d+:XFD\d+$/', $pRange)) {
352
                $selectionType = 'ROW';
353
            } else {
354
                $selectionType = 'CELL';
355
            }
356
357
            // First loop through columns, rows, or cells to find out which styles are affected by this operation
358
            switch ($selectionType) {
359
                case 'COLUMN':
360
                    $oldXfIndexes = [];
361
                    for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
362
                        $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true;
363
                    }
364
365
                    break;
366
                case 'ROW':
367
                    $oldXfIndexes = [];
368
                    for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
369
                        if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() == null) {
370
                            $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style
371
                        } else {
372
                            $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true;
373
                        }
374
                    }
375
376
                    break;
377
                case 'CELL':
378
                    $oldXfIndexes = [];
379
                    for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
380
                        for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
381
                            $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true;
382
                        }
383
                    }
384
385
                    break;
386
            }
387
388
            // clone each of the affected styles, apply the style array, and add the new styles to the workbook
389
            $workbook = $this->getActiveSheet()->getParent();
390
            foreach ($oldXfIndexes as $oldXfIndex => $dummy) {
391
                $style = $workbook->getCellXfByIndex($oldXfIndex);
392
                $newStyle = clone $style;
393
                $newStyle->applyFromArray($pStyles);
394
395
                if ($existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode())) {
396
                    // there is already such cell Xf in our collection
397
                    $newXfIndexes[$oldXfIndex] = $existingStyle->getIndex();
398
                } else {
399
                    // we don't have such a cell Xf, need to add
400
                    $workbook->addCellXf($newStyle);
401
                    $newXfIndexes[$oldXfIndex] = $newStyle->getIndex();
402
                }
403
            }
404
405
            // Loop through columns, rows, or cells again and update the XF index
406
            switch ($selectionType) {
407
                case 'COLUMN':
408
                    for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
409
                        $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col);
410
                        $oldXfIndex = $columnDimension->getXfIndex();
411
                        $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
412
                    }
413
414
                    break;
415
                case 'ROW':
416
                    for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
417
                        $rowDimension = $this->getActiveSheet()->getRowDimension($row);
418
                        $oldXfIndex = $rowDimension->getXfIndex() === null ?
419
                            0 : $rowDimension->getXfIndex(); // row without explicit style should be formatted based on default style
420
                        $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
421
                    }
422
423
                    break;
424
                case 'CELL':
425
                    for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
426
                        for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
427
                            $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row);
428
                            $oldXfIndex = $cell->getXfIndex();
429
                            $cell->setXfIndex($newXfIndexes[$oldXfIndex]);
430
                        }
431
                    }
432
433
                    break;
434
            }
435
        } else {
436
            // not a supervisor, just apply the style array directly on style object
437
            if (isset($pStyles['fill'])) {
438
                $this->getFill()->applyFromArray($pStyles['fill']);
439
            }
440
            if (isset($pStyles['font'])) {
441
                $this->getFont()->applyFromArray($pStyles['font']);
442
            }
443
            if (isset($pStyles['borders'])) {
444
                $this->getBorders()->applyFromArray($pStyles['borders']);
445
            }
446
            if (isset($pStyles['alignment'])) {
447
                $this->getAlignment()->applyFromArray($pStyles['alignment']);
448
            }
449
            if (isset($pStyles['numberFormat'])) {
450
                $this->getNumberFormat()->applyFromArray($pStyles['numberFormat']);
451
            }
452
            if (isset($pStyles['protection'])) {
453
                $this->getProtection()->applyFromArray($pStyles['protection']);
454
            }
455
            if (isset($pStyles['quotePrefix'])) {
456
                $this->quotePrefix = $pStyles['quotePrefix'];
457
            }
458
        }
459
460
        return $this;
461
    }
462
463
    /**
464
     * Get Fill.
465
     *
466
     * @return Fill
467
     */
468
    public function getFill()
469
    {
470
        return $this->fill;
471
    }
472
473
    /**
474
     * Get Font.
475
     *
476
     * @return Font
477
     */
478
    public function getFont()
479
    {
480
        return $this->font;
481
    }
482
483
    /**
484
     * Set font.
485
     *
486
     * @param Font $font
487
     *
488
     * @return $this
489
     */
490
    public function setFont(Font $font)
491
    {
492
        $this->font = $font;
493
494
        return $this;
495
    }
496
497
    /**
498
     * Get Borders.
499
     *
500
     * @return Borders
501
     */
502
    public function getBorders()
503
    {
504
        return $this->borders;
505
    }
506
507
    /**
508
     * Get Alignment.
509
     *
510
     * @return Alignment
511
     */
512
    public function getAlignment()
513
    {
514
        return $this->alignment;
515
    }
516
517
    /**
518
     * Get Number Format.
519
     *
520
     * @return NumberFormat
521
     */
522
    public function getNumberFormat()
523
    {
524
        return $this->numberFormat;
525
    }
526
527
    /**
528
     * Get Conditional Styles. Only used on supervisor.
529
     *
530
     * @return Conditional[]
531
     */
532
    public function getConditionalStyles()
533
    {
534
        return $this->getActiveSheet()->getConditionalStyles($this->getActiveCell());
535
    }
536
537
    /**
538
     * Set Conditional Styles. Only used on supervisor.
539
     *
540
     * @param Conditional[] $pValue Array of conditional styles
541
     *
542
     * @return $this
543
     */
544
    public function setConditionalStyles(array $pValue)
545
    {
546
        $this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $pValue);
547
548
        return $this;
549
    }
550
551
    /**
552
     * Get Protection.
553
     *
554
     * @return Protection
555
     */
556
    public function getProtection()
557
    {
558
        return $this->protection;
559
    }
560
561
    /**
562
     * Get quote prefix.
563
     *
564
     * @return bool
565
     */
566
    public function getQuotePrefix()
567
    {
568
        if ($this->isSupervisor) {
569
            return $this->getSharedComponent()->getQuotePrefix();
570
        }
571
572
        return $this->quotePrefix;
573
    }
574
575
    /**
576
     * Set quote prefix.
577
     *
578
     * @param bool $pValue
579
     *
580
     * @return $this
581
     */
582
    public function setQuotePrefix($pValue)
583
    {
584
        if ($pValue == '') {
585
            $pValue = false;
586
        }
587
        if ($this->isSupervisor) {
588
            $styleArray = ['quotePrefix' => $pValue];
589
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
590
        } else {
591
            $this->quotePrefix = (bool) $pValue;
592
        }
593
594
        return $this;
595
    }
596
597
    /**
598
     * Get hash code.
599
     *
600
     * @return string Hash code
601
     */
602
    public function getHashCode()
603
    {
604
        $hashConditionals = '';
605
        foreach ($this->conditionalStyles as $conditional) {
606
            $hashConditionals .= $conditional->getHashCode();
607
        }
608
609
        return md5(
610
            $this->fill->getHashCode() .
611
            $this->font->getHashCode() .
612
            $this->borders->getHashCode() .
613
            $this->alignment->getHashCode() .
614
            $this->numberFormat->getHashCode() .
615
            $hashConditionals .
616
            $this->protection->getHashCode() .
617
            ($this->quotePrefix ? 't' : 'f') .
618
            __CLASS__
619
        );
620
    }
621
622
    /**
623
     * Get own index in style collection.
624
     *
625
     * @return int
626
     */
627
    public function getIndex()
628
    {
629
        return $this->index;
630
    }
631
632
    /**
633
     * Set own index in style collection.
634
     *
635
     * @param int $pValue
636
     */
637
    public function setIndex($pValue)
638
    {
639
        $this->index = $pValue;
640
    }
641
}
642