1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpOffice\PhpSpreadsheet\Style; |
4
|
|
|
|
5
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions; |
6
|
|
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; |
7
|
|
|
use PhpOffice\PhpSpreadsheet\Exception; |
8
|
|
|
use PhpOffice\PhpSpreadsheet\Shared\StringHelper; |
9
|
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet; |
10
|
|
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; |
11
|
|
|
|
12
|
|
|
class Style extends Supervisor |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* Font. |
16
|
|
|
*/ |
17
|
|
|
protected Font $font; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Fill. |
21
|
|
|
*/ |
22
|
|
|
protected Fill $fill; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Borders. |
26
|
|
|
*/ |
27
|
|
|
protected Borders $borders; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Alignment. |
31
|
|
|
*/ |
32
|
|
|
protected Alignment $alignment; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Number Format. |
36
|
|
|
*/ |
37
|
|
|
protected NumberFormat $numberFormat; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Protection. |
41
|
|
|
*/ |
42
|
|
|
protected Protection $protection; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Index of style in collection. Only used for real style. |
46
|
|
|
*/ |
47
|
|
|
protected int $index; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Use Quote Prefix when displaying in cell editor. Only used for real style. |
51
|
|
|
*/ |
52
|
|
|
protected bool $quotePrefix = false; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Internal cache for styles |
56
|
|
|
* Used when applying style on range of cells (column or row) and cleared when |
57
|
|
|
* all cells in range is styled. |
58
|
|
|
* |
59
|
|
|
* PhpSpreadsheet will always minimize the amount of styles used. So cells with |
60
|
|
|
* same styles will reference the same Style instance. To check if two styles |
61
|
|
|
* are similar Style::getHashCode() is used. This call is expensive. To minimize |
62
|
|
|
* the need to call this method we can cache the internal PHP object id of the |
63
|
|
|
* Style in the range. Style::getHashCode() will then only be called when we |
64
|
|
|
* encounter a unique style. |
65
|
|
|
* |
66
|
|
|
* @see Style::applyFromArray() |
67
|
|
|
* @see Style::getHashCode() |
68
|
|
|
* |
69
|
|
|
* @var null|array<string, array> |
70
|
|
|
*/ |
71
|
|
|
private static ?array $cachedStyles = null; |
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
|
10568 |
|
public function __construct(bool $isSupervisor = false, bool $isConditional = false) |
84
|
|
|
{ |
85
|
10568 |
|
parent::__construct($isSupervisor); |
86
|
|
|
|
87
|
|
|
// Initialise values |
88
|
10568 |
|
$this->font = new Font($isSupervisor, $isConditional); |
89
|
10568 |
|
$this->fill = new Fill($isSupervisor, $isConditional); |
90
|
10568 |
|
$this->borders = new Borders($isSupervisor, $isConditional); |
91
|
10568 |
|
$this->alignment = new Alignment($isSupervisor, $isConditional); |
92
|
10568 |
|
$this->numberFormat = new NumberFormat($isSupervisor, $isConditional); |
93
|
10568 |
|
$this->protection = new Protection($isSupervisor, $isConditional); |
94
|
|
|
|
95
|
|
|
// bind parent if we are a supervisor |
96
|
10568 |
|
if ($isSupervisor) { |
97
|
10505 |
|
$this->font->bindParent($this); |
98
|
10505 |
|
$this->fill->bindParent($this); |
99
|
10505 |
|
$this->borders->bindParent($this); |
100
|
10505 |
|
$this->alignment->bindParent($this); |
101
|
10505 |
|
$this->numberFormat->bindParent($this); |
102
|
10505 |
|
$this->protection->bindParent($this); |
103
|
|
|
} |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Get the shared style component for the currently active cell in currently active sheet. |
108
|
|
|
* Only used for style supervisor. |
109
|
|
|
*/ |
110
|
10073 |
|
public function getSharedComponent(): self |
111
|
|
|
{ |
112
|
10073 |
|
$activeSheet = $this->getActiveSheet(); |
113
|
10073 |
|
$selectedCell = Functions::trimSheetFromCellReference($this->getActiveCell()); // e.g. 'A1' |
114
|
|
|
|
115
|
10073 |
|
if ($activeSheet->cellExists($selectedCell)) { |
116
|
10073 |
|
$xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex(); |
117
|
|
|
} else { |
118
|
6 |
|
$xfIndex = 0; |
119
|
|
|
} |
120
|
|
|
|
121
|
10073 |
|
return $activeSheet->getParentOrThrow()->getCellXfByIndex($xfIndex); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Get parent. Only used for style supervisor. |
126
|
|
|
*/ |
127
|
1 |
|
public function getParent(): Spreadsheet |
128
|
|
|
{ |
129
|
1 |
|
return $this->getActiveSheet()->getParentOrThrow(); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Build style array from subcomponents. |
134
|
|
|
*/ |
135
|
1 |
|
public function getStyleArray(array $array): array |
136
|
|
|
{ |
137
|
1 |
|
return ['quotePrefix' => $array]; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Apply styles from array. |
142
|
|
|
* |
143
|
|
|
* <code> |
144
|
|
|
* $spreadsheet->getActiveSheet()->getStyle('B2')->applyFromArray( |
145
|
|
|
* [ |
146
|
|
|
* 'font' => [ |
147
|
|
|
* 'name' => 'Arial', |
148
|
|
|
* 'bold' => true, |
149
|
|
|
* 'italic' => false, |
150
|
|
|
* 'underline' => Font::UNDERLINE_DOUBLE, |
151
|
|
|
* 'strikethrough' => false, |
152
|
|
|
* 'color' => [ |
153
|
|
|
* 'rgb' => '808080' |
154
|
|
|
* ] |
155
|
|
|
* ], |
156
|
|
|
* 'borders' => [ |
157
|
|
|
* 'bottom' => [ |
158
|
|
|
* 'borderStyle' => Border::BORDER_DASHDOT, |
159
|
|
|
* 'color' => [ |
160
|
|
|
* 'rgb' => '808080' |
161
|
|
|
* ] |
162
|
|
|
* ], |
163
|
|
|
* 'top' => [ |
164
|
|
|
* 'borderStyle' => Border::BORDER_DASHDOT, |
165
|
|
|
* 'color' => [ |
166
|
|
|
* 'rgb' => '808080' |
167
|
|
|
* ] |
168
|
|
|
* ] |
169
|
|
|
* ], |
170
|
|
|
* 'alignment' => [ |
171
|
|
|
* 'horizontal' => Alignment::HORIZONTAL_CENTER, |
172
|
|
|
* 'vertical' => Alignment::VERTICAL_CENTER, |
173
|
|
|
* 'wrapText' => true, |
174
|
|
|
* ], |
175
|
|
|
* 'quotePrefix' => true |
176
|
|
|
* ] |
177
|
|
|
* ); |
178
|
|
|
* </code> |
179
|
|
|
* |
180
|
|
|
* @param array $styleArray Array containing style information |
181
|
|
|
* @param bool $advancedBorders advanced mode for setting borders |
182
|
|
|
* |
183
|
|
|
* @return $this |
184
|
|
|
*/ |
185
|
960 |
|
public function applyFromArray(array $styleArray, bool $advancedBorders = true): static |
186
|
|
|
{ |
187
|
960 |
|
if ($this->isSupervisor) { |
188
|
906 |
|
$pRange = $this->getSelectedCells(); |
189
|
|
|
|
190
|
|
|
// Uppercase coordinate and strip any Worksheet reference from the selected range |
191
|
906 |
|
$pRange = strtoupper($pRange); |
192
|
906 |
|
if (str_contains($pRange, '!')) { |
193
|
5 |
|
$pRangeWorksheet = StringHelper::strToUpper(substr($pRange, 0, (int) strrpos($pRange, '!'))); |
194
|
5 |
|
$pRangeWorksheet = Worksheet::unApostrophizeTitle($pRangeWorksheet); |
195
|
5 |
|
if ($pRangeWorksheet !== '' && StringHelper::strToUpper($this->getActiveSheet()->getTitle()) !== $pRangeWorksheet) { |
196
|
2 |
|
throw new Exception('Invalid Worksheet for specified Range'); |
197
|
|
|
} |
198
|
3 |
|
$pRange = strtoupper(Functions::trimSheetFromCellReference($pRange)); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
// Is it a cell range or a single cell? |
202
|
904 |
|
if (!str_contains($pRange, ':')) { |
203
|
787 |
|
$rangeA = $pRange; |
204
|
787 |
|
$rangeB = $pRange; |
205
|
|
|
} else { |
206
|
218 |
|
[$rangeA, $rangeB] = explode(':', $pRange); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
// Calculate range outer borders |
210
|
904 |
|
$rangeStart = Coordinate::coordinateFromString($rangeA); |
211
|
896 |
|
$rangeEnd = Coordinate::coordinateFromString($rangeB); |
212
|
896 |
|
$rangeStartIndexes = Coordinate::indexesFromString($rangeA); |
213
|
896 |
|
$rangeEndIndexes = Coordinate::indexesFromString($rangeB); |
214
|
|
|
|
215
|
896 |
|
$columnStart = $rangeStart[0]; |
216
|
896 |
|
$columnEnd = $rangeEnd[0]; |
217
|
|
|
|
218
|
|
|
// Make sure we can loop upwards on rows and columns |
219
|
896 |
|
if ($rangeStartIndexes[0] > $rangeEndIndexes[0] && $rangeStartIndexes[1] > $rangeEndIndexes[1]) { |
220
|
1 |
|
$tmp = $rangeStartIndexes; |
221
|
1 |
|
$rangeStartIndexes = $rangeEndIndexes; |
222
|
1 |
|
$rangeEndIndexes = $tmp; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
// ADVANCED MODE: |
226
|
896 |
|
if ($advancedBorders && isset($styleArray['borders'])) { |
227
|
|
|
// 'allBorders' is a shorthand property for 'outline' and 'inside' and |
228
|
|
|
// it applies to components that have not been set explicitly |
229
|
87 |
|
if (isset($styleArray['borders']['allBorders'])) { |
230
|
10 |
|
foreach (['outline', 'inside'] as $component) { |
231
|
10 |
|
if (!isset($styleArray['borders'][$component])) { |
232
|
10 |
|
$styleArray['borders'][$component] = $styleArray['borders']['allBorders']; |
233
|
|
|
} |
234
|
|
|
} |
235
|
10 |
|
unset($styleArray['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
|
87 |
|
if (isset($styleArray['borders']['outline'])) { |
240
|
32 |
|
foreach (['top', 'right', 'bottom', 'left'] as $component) { |
241
|
32 |
|
if (!isset($styleArray['borders'][$component])) { |
242
|
32 |
|
$styleArray['borders'][$component] = $styleArray['borders']['outline']; |
243
|
|
|
} |
244
|
|
|
} |
245
|
32 |
|
unset($styleArray['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
|
87 |
|
if (isset($styleArray['borders']['inside'])) { |
250
|
11 |
|
foreach (['vertical', 'horizontal'] as $component) { |
251
|
11 |
|
if (!isset($styleArray['borders'][$component])) { |
252
|
11 |
|
$styleArray['borders'][$component] = $styleArray['borders']['inside']; |
253
|
|
|
} |
254
|
|
|
} |
255
|
11 |
|
unset($styleArray['borders']['inside']); // not needed any more |
256
|
|
|
} |
257
|
|
|
// width and height characteristics of selection, 1, 2, or 3 (for 3 or more) |
258
|
87 |
|
$xMax = min($rangeEndIndexes[0] - $rangeStartIndexes[0] + 1, 3); |
259
|
87 |
|
$yMax = min($rangeEndIndexes[1] - $rangeStartIndexes[1] + 1, 3); |
260
|
|
|
|
261
|
|
|
// loop through up to 3 x 3 = 9 regions |
262
|
87 |
|
for ($x = 1; $x <= $xMax; ++$x) { |
263
|
|
|
// start column index for region |
264
|
87 |
|
$colStart = ($x == 3) |
265
|
22 |
|
? Coordinate::stringFromColumnIndex($rangeEndIndexes[0]) |
266
|
87 |
|
: Coordinate::stringFromColumnIndex($rangeStartIndexes[0] + $x - 1); |
267
|
|
|
// end column index for region |
268
|
87 |
|
$colEnd = ($x == 1) |
269
|
87 |
|
? Coordinate::stringFromColumnIndex($rangeStartIndexes[0]) |
270
|
48 |
|
: Coordinate::stringFromColumnIndex($rangeEndIndexes[0] - $xMax + $x); |
271
|
|
|
|
272
|
87 |
|
for ($y = 1; $y <= $yMax; ++$y) { |
273
|
|
|
// which edges are touching the region |
274
|
87 |
|
$edges = []; |
275
|
87 |
|
if ($x == 1) { |
276
|
|
|
// are we at left edge |
277
|
87 |
|
$edges[] = 'left'; |
278
|
|
|
} |
279
|
87 |
|
if ($x == $xMax) { |
280
|
|
|
// are we at right edge |
281
|
87 |
|
$edges[] = 'right'; |
282
|
|
|
} |
283
|
87 |
|
if ($y == 1) { |
284
|
|
|
// are we at top edge? |
285
|
87 |
|
$edges[] = 'top'; |
286
|
|
|
} |
287
|
87 |
|
if ($y == $yMax) { |
288
|
|
|
// are we at bottom edge? |
289
|
87 |
|
$edges[] = 'bottom'; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
// start row index for region |
293
|
87 |
|
$rowStart = ($y == 3) |
294
|
87 |
|
? $rangeEndIndexes[1] : $rangeStartIndexes[1] + $y - 1; |
295
|
|
|
|
296
|
|
|
// end row index for region |
297
|
87 |
|
$rowEnd = ($y == 1) |
298
|
87 |
|
? $rangeStartIndexes[1] : $rangeEndIndexes[1] - $yMax + $y; |
299
|
|
|
|
300
|
|
|
// build range for region |
301
|
87 |
|
$range = $colStart . $rowStart . ':' . $colEnd . $rowEnd; |
302
|
|
|
|
303
|
|
|
// retrieve relevant style array for region |
304
|
87 |
|
$regionStyles = $styleArray; |
305
|
87 |
|
unset($regionStyles['borders']['inside']); |
306
|
|
|
|
307
|
|
|
// what are the inner edges of the region when looking at the selection |
308
|
87 |
|
$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
|
87 |
|
foreach ($innerEdges as $innerEdge) { |
312
|
|
|
switch ($innerEdge) { |
313
|
48 |
|
case 'top': |
314
|
48 |
|
case 'bottom': |
315
|
|
|
// should pick up 'horizontal' border property if set |
316
|
46 |
|
if (isset($styleArray['borders']['horizontal'])) { |
317
|
8 |
|
$regionStyles['borders'][$innerEdge] = $styleArray['borders']['horizontal']; |
318
|
|
|
} else { |
319
|
38 |
|
unset($regionStyles['borders'][$innerEdge]); |
320
|
|
|
} |
321
|
|
|
|
322
|
46 |
|
break; |
323
|
48 |
|
case 'left': |
324
|
48 |
|
case 'right': |
325
|
|
|
// should pick up 'vertical' border property if set |
326
|
48 |
|
if (isset($styleArray['borders']['vertical'])) { |
327
|
9 |
|
$regionStyles['borders'][$innerEdge] = $styleArray['borders']['vertical']; |
328
|
|
|
} else { |
329
|
39 |
|
unset($regionStyles['borders'][$innerEdge]); |
330
|
|
|
} |
331
|
|
|
|
332
|
48 |
|
break; |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
// apply region style to region by calling applyFromArray() in simple mode |
337
|
87 |
|
$this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false); |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
// restore initial cell selection range |
342
|
87 |
|
$this->getActiveSheet()->getStyle($pRange); |
343
|
|
|
$this->updateHashBeforeUse(); |
344
|
87 |
|
|
345
|
|
|
return $this; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
// SIMPLE MODE: |
349
|
896 |
|
// Selection type, inspect |
350
|
7 |
|
if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) { |
351
|
|
|
$selectionType = 'COLUMN'; |
352
|
|
|
|
353
|
7 |
|
// Enable caching of styles |
354
|
894 |
|
self::$cachedStyles = ['hashByObjId' => [], 'styleByHash' => []]; |
355
|
3 |
|
} elseif (preg_match('/^A\d+:XFD\d+$/', $pRange)) { |
356
|
|
|
$selectionType = 'ROW'; |
357
|
|
|
|
358
|
3 |
|
// Enable caching of styles |
359
|
|
|
self::$cachedStyles = ['hashByObjId' => [], 'styleByHash' => []]; |
360
|
892 |
|
} else { |
361
|
|
|
$selectionType = 'CELL'; |
362
|
|
|
} |
363
|
|
|
|
364
|
896 |
|
// First loop through columns, rows, or cells to find out which styles are affected by this operation |
365
|
|
|
$oldXfIndexes = $this->getOldXfIndexes($selectionType, $rangeStartIndexes, $rangeEndIndexes, $columnStart, $columnEnd, $styleArray); |
366
|
|
|
|
367
|
896 |
|
// clone each of the affected styles, apply the style array, and add the new styles to the workbook |
368
|
896 |
|
$workbook = $this->getActiveSheet()->getParentOrThrow(); |
369
|
896 |
|
$newXfIndexes = []; |
370
|
896 |
|
foreach ($oldXfIndexes as $oldXfIndex => $dummy) { |
371
|
|
|
$style = $workbook->getCellXfByIndex($oldXfIndex); |
372
|
|
|
|
373
|
896 |
|
// $cachedStyles is set when applying style for a range of cells, either column or row |
374
|
|
|
if (self::$cachedStyles === null) { |
375
|
890 |
|
// Clone the old style and apply style-array |
376
|
890 |
|
$newStyle = clone $style; |
377
|
|
|
$newStyle->applyFromArray($styleArray); |
378
|
|
|
|
379
|
890 |
|
// Look for existing style we can use instead (reduce memory usage) |
380
|
|
|
$existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode()); |
381
|
|
|
} else { |
382
|
|
|
// Style cache is stored by Style::getHashCode(). But calling this method is |
383
|
10 |
|
// expensive. So we cache the php obj id -> hash. |
384
|
|
|
$objId = spl_object_id($style); |
385
|
|
|
|
386
|
10 |
|
// Look for the original HashCode |
387
|
10 |
|
$styleHash = self::$cachedStyles['hashByObjId'][$objId] ?? null; |
388
|
|
|
if ($styleHash === null) { |
389
|
10 |
|
// This object_id is not cached, store the hashcode in case encounter again |
390
|
|
|
$styleHash = self::$cachedStyles['hashByObjId'][$objId] = $style->getHashCode(); |
391
|
|
|
} |
392
|
|
|
|
393
|
10 |
|
// Find existing style by hash. |
394
|
|
|
$existingStyle = self::$cachedStyles['styleByHash'][$styleHash] ?? null; |
395
|
10 |
|
|
396
|
|
|
if (!$existingStyle) { |
397
|
10 |
|
// The old style combined with the new style array is not cached, so we create it now |
398
|
10 |
|
$newStyle = clone $style; |
399
|
|
|
$newStyle->applyFromArray($styleArray); |
400
|
|
|
|
401
|
10 |
|
// Look for similar style in workbook to reduce memory usage |
402
|
|
|
$existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode()); |
403
|
|
|
|
404
|
10 |
|
// Cache the new style by original hashcode |
405
|
|
|
self::$cachedStyles['styleByHash'][$styleHash] = $existingStyle instanceof self ? $existingStyle : $newStyle; |
406
|
|
|
} |
407
|
|
|
} |
408
|
896 |
|
|
409
|
|
|
if ($existingStyle) { |
410
|
260 |
|
// there is already such cell Xf in our collection |
411
|
|
|
$newXfIndexes[$oldXfIndex] = $existingStyle->getIndex(); |
412
|
840 |
|
} else { |
413
|
|
|
if (!isset($newStyle)) { |
414
|
|
|
// Handle bug in PHPStan, see https://github.com/phpstan/phpstan/issues/5805 |
415
|
|
|
// $newStyle should always be defined. |
416
|
|
|
// This block might not be needed in the future |
417
|
|
|
// @codeCoverageIgnoreStart |
418
|
|
|
$newStyle = clone $style; |
419
|
|
|
$newStyle->applyFromArray($styleArray); |
420
|
|
|
// @codeCoverageIgnoreEnd |
421
|
|
|
} |
422
|
|
|
|
423
|
840 |
|
// we don't have such a cell Xf, need to add |
424
|
840 |
|
$workbook->addCellXf($newStyle); |
425
|
|
|
$newXfIndexes[$oldXfIndex] = $newStyle->getIndex(); |
426
|
|
|
} |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
// Loop through columns, rows, or cells again and update the XF index |
430
|
896 |
|
switch ($selectionType) { |
431
|
7 |
|
case 'COLUMN': |
432
|
7 |
|
for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) { |
433
|
7 |
|
$columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col); |
434
|
7 |
|
$oldXfIndex = $columnDimension->getXfIndex(); |
435
|
|
|
$columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]); |
436
|
|
|
} |
437
|
|
|
|
438
|
7 |
|
// Disable caching of styles |
439
|
|
|
self::$cachedStyles = null; |
440
|
7 |
|
|
441
|
894 |
|
break; |
442
|
3 |
|
case 'ROW': |
443
|
3 |
|
for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) { |
444
|
|
|
$rowDimension = $this->getActiveSheet()->getRowDimension($row); |
445
|
3 |
|
// row without explicit style should be formatted based on default style |
446
|
3 |
|
$oldXfIndex = $rowDimension->getXfIndex() ?? 0; |
447
|
|
|
$rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]); |
448
|
|
|
} |
449
|
|
|
|
450
|
3 |
|
// Disable caching of styles |
451
|
|
|
self::$cachedStyles = null; |
452
|
3 |
|
|
453
|
892 |
|
break; |
454
|
892 |
|
case 'CELL': |
455
|
892 |
|
for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) { |
456
|
892 |
|
for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) { |
457
|
892 |
|
$cell = $this->getActiveSheet()->getCell([$col, $row]); |
458
|
892 |
|
$oldXfIndex = $cell->getXfIndex(); |
459
|
|
|
$cell->setXfIndex($newXfIndexes[$oldXfIndex]); |
460
|
|
|
} |
461
|
|
|
} |
462
|
892 |
|
|
463
|
|
|
break; |
464
|
|
|
} |
465
|
|
|
} else { |
466
|
950 |
|
// not a supervisor, just apply the style array directly on style object |
467
|
132 |
|
if (isset($styleArray['fill'])) { |
468
|
|
|
$this->getFill()->applyFromArray($styleArray['fill']); |
469
|
950 |
|
} |
470
|
190 |
|
if (isset($styleArray['font'])) { |
471
|
|
|
$this->getFont()->applyFromArray($styleArray['font']); |
472
|
950 |
|
} |
473
|
93 |
|
if (isset($styleArray['borders'])) { |
474
|
|
|
$this->getBorders()->applyFromArray($styleArray['borders']); |
475
|
950 |
|
} |
476
|
129 |
|
if (isset($styleArray['alignment'])) { |
477
|
|
|
$this->getAlignment()->applyFromArray($styleArray['alignment']); |
478
|
950 |
|
} |
479
|
712 |
|
if (isset($styleArray['numberFormat'])) { |
480
|
|
|
$this->getNumberFormat()->applyFromArray($styleArray['numberFormat']); |
481
|
950 |
|
} |
482
|
38 |
|
if (isset($styleArray['protection'])) { |
483
|
|
|
$this->getProtection()->applyFromArray($styleArray['protection']); |
484
|
950 |
|
} |
485
|
56 |
|
if (isset($styleArray['quotePrefix'])) { |
486
|
|
|
$this->quotePrefix = $styleArray['quotePrefix']; |
487
|
|
|
} |
488
|
|
|
} |
489
|
950 |
|
$this->updateHashBeforeUse(); |
490
|
|
|
|
491
|
|
|
return $this; |
492
|
896 |
|
} |
493
|
|
|
|
494
|
896 |
|
private function getOldXfIndexes(string $selectionType, array $rangeStart, array $rangeEnd, string $columnStart, string $columnEnd, array $styleArray): array |
495
|
|
|
{ |
496
|
896 |
|
$oldXfIndexes = []; |
497
|
7 |
|
switch ($selectionType) { |
498
|
7 |
|
case 'COLUMN': |
499
|
|
|
for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { |
500
|
7 |
|
$oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true; |
501
|
7 |
|
} |
502
|
7 |
|
foreach ($this->getActiveSheet()->getColumnIterator($columnStart, $columnEnd) as $columnIterator) { |
503
|
7 |
|
$cellIterator = $columnIterator->getCellIterator(); |
504
|
4 |
|
$cellIterator->setIterateOnlyExistingCells(true); |
505
|
4 |
|
foreach ($cellIterator as $columnCell) { |
506
|
|
|
$columnCell->getStyle() |
507
|
|
|
->applyFromArray($styleArray); |
508
|
|
|
} |
509
|
7 |
|
} |
510
|
894 |
|
|
511
|
3 |
|
break; |
512
|
3 |
|
case 'ROW': |
513
|
3 |
|
for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { |
514
|
|
|
if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() === null) { |
515
|
1 |
|
$oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style |
516
|
|
|
} else { |
517
|
|
|
$oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true; |
518
|
3 |
|
} |
519
|
3 |
|
} |
520
|
3 |
|
foreach ($this->getActiveSheet()->getRowIterator((int) $rangeStart[1], (int) $rangeEnd[1]) as $rowIterator) { |
521
|
3 |
|
$cellIterator = $rowIterator->getCellIterator(); |
522
|
1 |
|
$cellIterator->setIterateOnlyExistingCells(true); |
523
|
1 |
|
foreach ($cellIterator as $rowCell) { |
524
|
|
|
$rowCell->getStyle() |
525
|
|
|
->applyFromArray($styleArray); |
526
|
|
|
} |
527
|
3 |
|
} |
528
|
892 |
|
|
529
|
892 |
|
break; |
530
|
892 |
|
case 'CELL': |
531
|
892 |
|
for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { |
532
|
|
|
for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { |
533
|
|
|
$oldXfIndexes[$this->getActiveSheet()->getCell([$col, $row])->getXfIndex()] = true; |
534
|
|
|
} |
535
|
892 |
|
} |
536
|
|
|
|
537
|
|
|
break; |
538
|
896 |
|
} |
539
|
|
|
|
540
|
|
|
return $oldXfIndexes; |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
/** |
544
|
1553 |
|
* Get Fill. |
545
|
|
|
*/ |
546
|
1553 |
|
public function getFill(): Fill |
547
|
|
|
{ |
548
|
|
|
return $this->fill; |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
/** |
552
|
1574 |
|
* Get Font. |
553
|
|
|
*/ |
554
|
1574 |
|
public function getFont(): Font |
555
|
|
|
{ |
556
|
|
|
return $this->font; |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
/** |
560
|
|
|
* Set font. |
561
|
|
|
* |
562
|
116 |
|
* @return $this |
563
|
|
|
*/ |
564
|
116 |
|
public function setFont(Font $font): static |
565
|
|
|
{ |
566
|
116 |
|
$this->font = $font; |
567
|
|
|
$this->updateHashBeforeUse(); |
568
|
|
|
|
569
|
|
|
return $this; |
570
|
|
|
} |
571
|
|
|
|
572
|
1503 |
|
/** |
573
|
|
|
* Get Borders. |
574
|
1503 |
|
*/ |
575
|
|
|
public function getBorders(): Borders |
576
|
|
|
{ |
577
|
|
|
return $this->borders; |
578
|
|
|
} |
579
|
|
|
|
580
|
1535 |
|
/** |
581
|
|
|
* Get Alignment. |
582
|
1535 |
|
*/ |
583
|
|
|
public function getAlignment(): Alignment |
584
|
|
|
{ |
585
|
|
|
return $this->alignment; |
586
|
|
|
} |
587
|
|
|
|
588
|
1780 |
|
/** |
589
|
|
|
* Get Number Format. |
590
|
1780 |
|
*/ |
591
|
|
|
public function getNumberFormat(): NumberFormat |
592
|
|
|
{ |
593
|
|
|
return $this->numberFormat; |
594
|
|
|
} |
595
|
|
|
|
596
|
|
|
/** |
597
|
|
|
* Get Conditional Styles. Only used on supervisor. |
598
|
584 |
|
* |
599
|
|
|
* @return Conditional[] |
600
|
584 |
|
*/ |
601
|
|
|
public function getConditionalStyles(): array |
602
|
|
|
{ |
603
|
|
|
return $this->getActiveSheet()->getConditionalStyles($this->getActiveCell()); |
604
|
|
|
} |
605
|
|
|
|
606
|
|
|
/** |
607
|
|
|
* Set Conditional Styles. Only used on supervisor. |
608
|
|
|
* |
609
|
|
|
* @param Conditional[] $conditionalStyleArray Array of conditional styles |
610
|
281 |
|
* |
611
|
|
|
* @return $this |
612
|
281 |
|
*/ |
613
|
|
|
public function setConditionalStyles(array $conditionalStyleArray): static |
614
|
281 |
|
{ |
615
|
|
|
$this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $conditionalStyleArray); |
616
|
|
|
|
617
|
|
|
return $this; |
618
|
|
|
} |
619
|
|
|
|
620
|
546 |
|
/** |
621
|
|
|
* Get Protection. |
622
|
546 |
|
*/ |
623
|
|
|
public function getProtection(): Protection |
624
|
|
|
{ |
625
|
|
|
return $this->protection; |
626
|
|
|
} |
627
|
|
|
|
628
|
10101 |
|
/** |
629
|
|
|
* Get quote prefix. |
630
|
10101 |
|
*/ |
631
|
10058 |
|
public function getQuotePrefix(): bool |
632
|
|
|
{ |
633
|
|
|
if ($this->isSupervisor) { |
634
|
10101 |
|
return $this->getSharedComponent()->getQuotePrefix(); |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
return $this->quotePrefix; |
638
|
|
|
} |
639
|
|
|
|
640
|
|
|
/** |
641
|
|
|
* Set quote prefix. |
642
|
710 |
|
* |
643
|
|
|
* @return $this |
644
|
710 |
|
*/ |
645
|
672 |
|
public function setQuotePrefix(bool $quotePrefix): static |
646
|
|
|
{ |
647
|
710 |
|
if ($quotePrefix == '') { |
648
|
45 |
|
$quotePrefix = false; |
649
|
45 |
|
} |
650
|
|
|
if ($this->isSupervisor) { |
651
|
672 |
|
$styleArray = ['quotePrefix' => $quotePrefix]; |
652
|
|
|
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); |
653
|
|
|
} else { |
654
|
710 |
|
$this->quotePrefix = (bool) $quotePrefix; |
655
|
|
|
} |
656
|
|
|
$this->updateHashBeforeUse(); |
657
|
|
|
|
658
|
|
|
return $this; |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
/** |
662
|
1245 |
|
* Update Hash when something changes. |
663
|
|
|
*/ |
664
|
1245 |
|
protected function updateHash(): void |
665
|
1245 |
|
{ |
666
|
1245 |
|
$this->md5Sum = md5( |
667
|
1245 |
|
$this->fill->getHashCode() |
668
|
1245 |
|
. $this->font->getHashCode() |
669
|
1245 |
|
. $this->borders->getHashCode() |
670
|
1245 |
|
. $this->alignment->getHashCode() |
671
|
1245 |
|
. $this->numberFormat->getHashCode() |
672
|
1245 |
|
. $this->protection->getHashCode() |
673
|
1245 |
|
. ($this->quotePrefix ? 't' : 'f') |
674
|
|
|
. __CLASS__ |
675
|
|
|
); |
676
|
|
|
$this->updateMd5Sum = false; |
677
|
|
|
} |
678
|
|
|
|
679
|
906 |
|
/** |
680
|
|
|
* Get hash code. |
681
|
906 |
|
* |
682
|
|
|
* @return string Hash code |
683
|
|
|
*/ |
684
|
|
|
public function getHashCode(): string |
685
|
|
|
{ |
686
|
|
|
if ($this->updateMd5Sum) { |
687
|
10505 |
|
$this->updateHash(); |
688
|
|
|
} |
689
|
10505 |
|
|
690
|
|
|
return $this->md5Sum; |
691
|
|
|
} |
692
|
14 |
|
|
693
|
|
|
/** |
694
|
14 |
|
* Get own index in style collection. |
695
|
14 |
|
*/ |
696
|
14 |
|
public function getIndex(): int |
697
|
14 |
|
{ |
698
|
14 |
|
return $this->index; |
699
|
14 |
|
} |
700
|
14 |
|
|
701
|
14 |
|
/** |
702
|
|
|
* Set own index in style collection. |
703
|
14 |
|
*/ |
704
|
|
|
public function setIndex(int $index): void |
705
|
|
|
{ |
706
|
|
|
$this->index = $index; |
707
|
|
|
} |
708
|
|
|
|
709
|
|
|
protected function exportArray1(): array |
710
|
|
|
{ |
711
|
|
|
$exportedArray = []; |
712
|
|
|
$this->exportArray2($exportedArray, 'alignment', $this->getAlignment()); |
713
|
|
|
$this->exportArray2($exportedArray, 'borders', $this->getBorders()); |
714
|
|
|
$this->exportArray2($exportedArray, 'fill', $this->getFill()); |
715
|
|
|
$this->exportArray2($exportedArray, 'font', $this->getFont()); |
716
|
|
|
$this->exportArray2($exportedArray, 'numberFormat', $this->getNumberFormat()); |
717
|
|
|
$this->exportArray2($exportedArray, 'protection', $this->getProtection()); |
718
|
|
|
$this->exportArray2($exportedArray, 'quotePrefix', $this->getQuotePrefix()); |
719
|
|
|
|
720
|
|
|
return $exportedArray; |
721
|
|
|
} |
722
|
|
|
} |
723
|
|
|
|