Completed
Push — master ( fe79f7...a3187a )
by Mark
36s queued 32s
created

Spreadsheet   F

Complexity

Total Complexity 197

Size/Duplication

Total Lines 1641
Duplicated Lines 0 %

Test Coverage

Coverage 97.46%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 197
eloc 393
c 1
b 0
f 0
dl 0
loc 1641
ccs 422
cts 433
cp 0.9746
rs 2

96 Methods

Rating   Name   Duplication   Size   Complexity  
A getActiveSheet() 0 3 1
A getSheetCount() 0 3 1
A getActiveSheetIndex() 0 3 1
A __construct() 0 26 1
A getSheetByCodeName() 0 10 3
A getProperties() 0 3 1
A getSecurity() 0 3 1
A getCalculationEngine() 0 3 1
A hasRibbon() 0 3 1
A createSheet() 0 6 1
A hasRibbonBinObjects() 0 3 1
A sheetCodeNameExists() 0 3 1
A sheetNameExists() 0 3 1
A getMacrosCertificate() 0 3 1
A hasMacrosCertificate() 0 3 1
A hasMacros() 0 3 1
A setUnparsedLoadedData() 0 3 1
A discardMacros() 0 5 1
A getUnparsedLoadedData() 0 3 1
A setRibbonBinObjects() 0 6 3
A getMacrosCode() 0 3 1
A setMacrosCertificate() 0 3 1
A setRibbonXMLData() 0 6 3
A setHasMacros() 0 3 1
A getRibbonXMLData() 0 19 5
A setMacrosCode() 0 4 1
A addNamedRange() 0 3 1
A setShowVerticalScroll() 0 3 1
A getShowHorizontalScroll() 0 3 1
A removeDefinedName() 0 17 5
A setShowHorizontalScroll() 0 3 1
A getAllSheets() 0 3 1
A getLocalDefinedNameByType() 0 10 4
A removeCellStyleXfByIndex() 0 6 2
A setAutoFilterDateGrouping() 0 3 1
A getDefinedNames() 0 3 1
A getVisibility() 0 3 1
A removeNamedRange() 0 7 2
A getCellStyleXfByIndex() 0 3 1
A addDefinedName() 0 9 2
A getNamedRange() 0 13 4
A addCellStyleXf() 0 4 1
A getSheetByName() 0 10 3
A setFirstSheetIndex() 0 6 2
A getShowSheetTabs() 0 3 1
A removeSheetByIndex() 0 16 5
A getCellStyleXfCollection() 0 3 1
A disconnectWorksheets() 0 7 2
A getExtensionOnly() 0 5 1
A getTabRatio() 0 3 1
A jsonSerialize() 0 3 1
A __serialize() 0 3 1
A getNamedFormulae() 0 6 1
A getCellStyleXfByHashCode() 0 9 3
A setShowSheetTabs() 0 3 1
F garbageCollect() 0 74 17
A getCellXfCollection() 0 3 1
A removeNamedFormula() 0 7 2
A getDefinedName() 0 18 5
A setMinimized() 0 3 1
A setIndexByName() 0 16 1
A getSharedComponent() 0 3 1
A getSheetByNameOrThrow() 0 8 2
A cellXfExists() 0 3 1
A getCellXfByHashCode() 0 9 3
A addCellXf() 0 4 1
A getGlobalDefinedNameByType() 0 7 3
A getSheet() 0 11 2
A __clone() 0 4 1
A getID() 0 3 1
A getDefaultStyle() 0 7 2
A getIndex() 0 9 3
A getNamedFormula() 0 13 4
B getRibbonBinObjects() 0 29 10
A setProperties() 0 3 1
A addNamedFormula() 0 3 1
A getSheetNames() 0 9 2
A getWorksheetIterator() 0 3 1
B addExternalSheet() 0 37 7
A setTabRatio() 0 6 3
A copy() 0 13 1
A getAutoFilterDateGrouping() 0 3 1
A getShowVerticalScroll() 0 3 1
A getNamedRanges() 0 6 1
A getFirstSheetIndex() 0 3 1
A getCellXfByIndex() 0 3 1
A setActiveSheetIndex() 0 12 2
A reevaluateAutoFilters() 0 9 4
A setVisibility() 0 10 3
A addSheet() 0 33 6
A setSecurity() 0 3 1
A getCellXfSupervisor() 0 3 1
A getMinimized() 0 3 1
A __destruct() 0 6 1
A removeCellXfByIndex() 0 20 6
A setActiveSheetIndexByName() 0 9 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
use JsonSerializable;
6
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
7
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
8
use PhpOffice\PhpSpreadsheet\Shared\File;
9
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
10
use PhpOffice\PhpSpreadsheet\Style\Style;
11
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
12
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
13
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
14
15
class Spreadsheet implements JsonSerializable
16
{
17
    // Allowable values for workbook window visilbity
18
    const VISIBILITY_VISIBLE = 'visible';
19
    const VISIBILITY_HIDDEN = 'hidden';
20
    const VISIBILITY_VERY_HIDDEN = 'veryHidden';
21
22
    private const DEFINED_NAME_IS_RANGE = false;
23
    private const DEFINED_NAME_IS_FORMULA = true;
24
25
    private const WORKBOOK_VIEW_VISIBILITY_VALUES = [
26
        self::VISIBILITY_VISIBLE,
27
        self::VISIBILITY_HIDDEN,
28
        self::VISIBILITY_VERY_HIDDEN,
29
    ];
30
31
    /**
32
     * Unique ID.
33
     *
34
     * @var string
35
     */
36
    private $uniqueID;
37
38
    /**
39
     * Document properties.
40
     *
41
     * @var Document\Properties
42
     */
43
    private $properties;
44
45
    /**
46
     * Document security.
47
     *
48
     * @var Document\Security
49
     */
50
    private $security;
51
52
    /**
53
     * Collection of Worksheet objects.
54
     *
55
     * @var Worksheet[]
56
     */
57
    private $workSheetCollection = [];
58
59
    /**
60
     * Calculation Engine.
61
     *
62
     * @var null|Calculation
63
     */
64
    private $calculationEngine;
65
66
    /**
67
     * Active sheet index.
68
     *
69
     * @var int
70
     */
71
    private $activeSheetIndex = 0;
72
73
    /**
74
     * Named ranges.
75
     *
76
     * @var DefinedName[]
77
     */
78
    private $definedNames = [];
79
80
    /**
81
     * CellXf supervisor.
82
     *
83
     * @var Style
84
     */
85
    private $cellXfSupervisor;
86
87
    /**
88
     * CellXf collection.
89
     *
90
     * @var Style[]
91
     */
92
    private $cellXfCollection = [];
93
94
    /**
95
     * CellStyleXf collection.
96
     *
97
     * @var Style[]
98
     */
99
    private $cellStyleXfCollection = [];
100
101
    /**
102
     * hasMacros : this workbook have macros ?
103
     *
104
     * @var bool
105
     */
106
    private $hasMacros = false;
107
108
    /**
109
     * macrosCode : all macros code as binary data (the vbaProject.bin file, this include form, code,  etc.), null if no macro.
110
     *
111
     * @var null|string
112
     */
113
    private $macrosCode;
114
115
    /**
116
     * macrosCertificate : if macros are signed, contains binary data vbaProjectSignature.bin file, null if not signed.
117
     *
118
     * @var null|string
119
     */
120
    private $macrosCertificate;
121
122
    /**
123
     * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI.
124
     *
125
     * @var null|array{target: string, data: string}
126
     */
127
    private $ribbonXMLData;
128
129
    /**
130
     * ribbonBinObjects : null if workbook is'nt Excel 2007 or not contain embedded objects (picture(s)) for Ribbon Elements
131
     * ignored if $ribbonXMLData is null.
132
     *
133
     * @var null|array
134
     */
135
    private $ribbonBinObjects;
136
137
    /**
138
     * List of unparsed loaded data for export to same format with better compatibility.
139
     * It has to be minimized when the library start to support currently unparsed data.
140
     *
141
     * @var array
142
     */
143
    private $unparsedLoadedData = [];
144
145
    /**
146
     * Controls visibility of the horizonal scroll bar in the application.
147
     *
148
     * @var bool
149
     */
150
    private $showHorizontalScroll = true;
151
152
    /**
153
     * Controls visibility of the horizonal scroll bar in the application.
154
     *
155
     * @var bool
156
     */
157
    private $showVerticalScroll = true;
158
159
    /**
160
     * Controls visibility of the sheet tabs in the application.
161
     *
162
     * @var bool
163
     */
164
    private $showSheetTabs = true;
165
166
    /**
167
     * Specifies a boolean value that indicates whether the workbook window
168
     * is minimized.
169
     *
170
     * @var bool
171
     */
172
    private $minimized = false;
173
174
    /**
175
     * Specifies a boolean value that indicates whether to group dates
176
     * when presenting the user with filtering optiomd in the user
177
     * interface.
178
     *
179
     * @var bool
180
     */
181
    private $autoFilterDateGrouping = true;
182
183
    /**
184
     * Specifies the index to the first sheet in the book view.
185
     *
186
     * @var int
187
     */
188
    private $firstSheetIndex = 0;
189
190
    /**
191
     * Specifies the visible status of the workbook.
192
     *
193
     * @var string
194
     */
195
    private $visibility = self::VISIBILITY_VISIBLE;
196
197
    /**
198
     * Specifies the ratio between the workbook tabs bar and the horizontal
199
     * scroll bar.  TabRatio is assumed to be out of 1000 of the horizontal
200
     * window width.
201
     *
202
     * @var int
203
     */
204
    private $tabRatio = 600;
205
206
    /**
207
     * The workbook has macros ?
208
     *
209
     * @return bool
210
     */
211 281
    public function hasMacros()
212
    {
213 281
        return $this->hasMacros;
214
    }
215
216
    /**
217
     * Define if a workbook has macros.
218
     *
219
     * @param bool $hasMacros true|false
220
     */
221 3
    public function setHasMacros($hasMacros): void
222
    {
223 3
        $this->hasMacros = (bool) $hasMacros;
224
    }
225
226
    /**
227
     * Set the macros code.
228
     *
229
     * @param string $macroCode string|null
230
     */
231 3
    public function setMacrosCode($macroCode): void
232
    {
233 3
        $this->macrosCode = $macroCode;
234 3
        $this->setHasMacros($macroCode !== null);
235
    }
236
237
    /**
238
     * Return the macros code.
239
     *
240
     * @return null|string
241
     */
242 3
    public function getMacrosCode()
243
    {
244 3
        return $this->macrosCode;
245
    }
246
247
    /**
248
     * Set the macros certificate.
249
     *
250
     * @param null|string $certificate
251
     */
252 3
    public function setMacrosCertificate($certificate): void
253
    {
254 3
        $this->macrosCertificate = $certificate;
255
    }
256
257
    /**
258
     * Is the project signed ?
259
     *
260
     * @return bool true|false
261
     */
262 2
    public function hasMacrosCertificate()
263
    {
264 2
        return $this->macrosCertificate !== null;
265
    }
266
267
    /**
268
     * Return the macros certificate.
269
     *
270
     * @return null|string
271
     */
272 2
    public function getMacrosCertificate()
273
    {
274 2
        return $this->macrosCertificate;
275
    }
276
277
    /**
278
     * Remove all macros, certificate from spreadsheet.
279
     */
280 1
    public function discardMacros(): void
281
    {
282 1
        $this->hasMacros = false;
283 1
        $this->macrosCode = null;
284 1
        $this->macrosCertificate = null;
285
    }
286
287
    /**
288
     * set ribbon XML data.
289
     *
290
     * @param null|mixed $target
291
     * @param null|mixed $xmlData
292
     */
293 2
    public function setRibbonXMLData($target, $xmlData): void
294
    {
295 2
        if ($target !== null && $xmlData !== null) {
296 2
            $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData];
297
        } else {
298
            $this->ribbonXMLData = null;
299
        }
300
    }
301
302
    /**
303
     * retrieve ribbon XML Data.
304
     *
305
     * @param string $what
306
     *
307
     * @return null|array|string
308
     */
309 248
    public function getRibbonXMLData($what = 'all') //we need some constants here...
310
    {
311 248
        $returnData = null;
312 248
        $what = strtolower($what);
313
        switch ($what) {
314 248
            case 'all':
315 2
                $returnData = $this->ribbonXMLData;
316
317 2
                break;
318 248
            case 'target':
319 2
            case 'data':
320 248
                if (is_array($this->ribbonXMLData)) {
321 2
                    $returnData = $this->ribbonXMLData[$what];
322
                }
323
324 248
                break;
325
        }
326
327 248
        return $returnData;
328
    }
329
330
    /**
331
     * store binaries ribbon objects (pictures).
332
     *
333
     * @param null|mixed $BinObjectsNames
334
     * @param null|mixed $BinObjectsData
335
     */
336 2
    public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData): void
337
    {
338 2
        if ($BinObjectsNames !== null && $BinObjectsData !== null) {
339
            $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData];
340
        } else {
341 2
            $this->ribbonBinObjects = null;
342
        }
343
    }
344
345
    /**
346
     * List of unparsed loaded data for export to same format with better compatibility.
347
     * It has to be minimized when the library start to support currently unparsed data.
348
     *
349
     * @internal
350
     *
351
     * @return array
352
     */
353 281
    public function getUnparsedLoadedData()
354
    {
355 281
        return $this->unparsedLoadedData;
356
    }
357
358
    /**
359
     * List of unparsed loaded data for export to same format with better compatibility.
360
     * It has to be minimized when the library start to support currently unparsed data.
361
     *
362
     * @internal
363
     */
364 461
    public function setUnparsedLoadedData(array $unparsedLoadedData): void
365
    {
366 461
        $this->unparsedLoadedData = $unparsedLoadedData;
367
    }
368
369
    /**
370
     * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
371
     *
372
     * @param mixed $path
373
     *
374
     * @return string
375
     */
376
    private function getExtensionOnly($path)
377
    {
378
        $extension = pathinfo($path, PATHINFO_EXTENSION);
379
380
        return substr(/** @scrutinizer ignore-type */$extension, 0);
381
    }
382
383
    /**
384
     * retrieve Binaries Ribbon Objects.
385
     *
386
     * @param string $what
387
     *
388
     * @return null|array
389
     */
390 2
    public function getRibbonBinObjects($what = 'all')
391
    {
392 2
        $ReturnData = null;
393 2
        $what = strtolower($what);
394
        switch ($what) {
395 2
            case 'all':
396 2
                return $this->ribbonBinObjects;
397 1
            case 'names':
398 1
            case 'data':
399 1
                if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) {
400
                    $ReturnData = $this->ribbonBinObjects[$what];
401
                }
402
403 1
                break;
404 1
            case 'types':
405
                if (
406 1
                    is_array($this->ribbonBinObjects) &&
407 1
                    isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data'])
408
                ) {
409
                    $tmpTypes = array_keys($this->ribbonBinObjects['data']);
410
                    $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes));
411
                } else {
412 1
                    $ReturnData = []; // the caller want an array... not null if empty
413
                }
414
415 1
                break;
416
        }
417
418 1
        return $ReturnData;
419
    }
420
421
    /**
422
     * This workbook have a custom UI ?
423
     *
424
     * @return bool
425
     */
426 248
    public function hasRibbon()
427
    {
428 248
        return $this->ribbonXMLData !== null;
429
    }
430
431
    /**
432
     * This workbook have additionnal object for the ribbon ?
433
     *
434
     * @return bool
435
     */
436 248
    public function hasRibbonBinObjects()
437
    {
438 248
        return $this->ribbonBinObjects !== null;
439
    }
440
441
    /**
442
     * Check if a sheet with a specified code name already exists.
443
     *
444
     * @param string $codeName Name of the worksheet to check
445
     *
446
     * @return bool
447
     */
448 6502
    public function sheetCodeNameExists($codeName)
449
    {
450 6502
        return $this->getSheetByCodeName($codeName) !== null;
451
    }
452
453
    /**
454
     * Get sheet by code name. Warning : sheet don't have always a code name !
455
     *
456
     * @param string $codeName Sheet name
457
     *
458
     * @return null|Worksheet
459
     */
460 6502
    public function getSheetByCodeName($codeName)
461
    {
462 6502
        $worksheetCount = count($this->workSheetCollection);
463 6502
        for ($i = 0; $i < $worksheetCount; ++$i) {
464 620
            if ($this->workSheetCollection[$i]->getCodeName() == $codeName) {
465 453
                return $this->workSheetCollection[$i];
466
            }
467
        }
468
469 6502
        return null;
470
    }
471
472
    /**
473
     * Create a new PhpSpreadsheet with one Worksheet.
474
     */
475 6502
    public function __construct()
476
    {
477 6502
        $this->uniqueID = uniqid('', true);
478 6502
        $this->calculationEngine = new Calculation($this);
479
480
        // Initialise worksheet collection and add one worksheet
481 6502
        $this->workSheetCollection = [];
482 6502
        $this->workSheetCollection[] = new Worksheet($this);
483 6502
        $this->activeSheetIndex = 0;
484
485
        // Create document properties
486 6502
        $this->properties = new Document\Properties();
487
488
        // Create document security
489 6502
        $this->security = new Document\Security();
490
491
        // Set defined names
492 6502
        $this->definedNames = [];
493
494
        // Create the cellXf supervisor
495 6502
        $this->cellXfSupervisor = new Style(true);
496 6502
        $this->cellXfSupervisor->bindParent($this);
497
498
        // Create the default style
499 6502
        $this->addCellXf(new Style());
500 6502
        $this->addCellStyleXf(new Style());
501
    }
502
503
    /**
504
     * Code to execute when this worksheet is unset().
505
     */
506 84
    public function __destruct()
507
    {
508 84
        $this->disconnectWorksheets();
509 84
        $this->calculationEngine = null;
510 84
        $this->cellXfCollection = [];
511 84
        $this->cellStyleXfCollection = [];
512
    }
513
514
    /**
515
     * Disconnect all worksheets from this PhpSpreadsheet workbook object,
516
     * typically so that the PhpSpreadsheet object can be unset.
517
     */
518 4306
    public function disconnectWorksheets(): void
519
    {
520 4306
        foreach ($this->workSheetCollection as $worksheet) {
521 4303
            $worksheet->disconnectCells();
522 4303
            unset($worksheet);
523
        }
524 4306
        $this->workSheetCollection = [];
525
    }
526
527
    /**
528
     * Return the calculation engine for this worksheet.
529
     *
530
     * @return null|Calculation
531
     */
532 6502
    public function getCalculationEngine()
533
    {
534 6502
        return $this->calculationEngine;
535
    }
536
537
    /**
538
     * Get properties.
539
     *
540
     * @return Document\Properties
541
     */
542 1105
    public function getProperties()
543
    {
544 1105
        return $this->properties;
545
    }
546
547
    /**
548
     * Set properties.
549
     */
550 1
    public function setProperties(Document\Properties $documentProperties): void
551
    {
552 1
        $this->properties = $documentProperties;
553
    }
554
555
    /**
556
     * Get security.
557
     *
558
     * @return Document\Security
559
     */
560 257
    public function getSecurity()
561
    {
562 257
        return $this->security;
563
    }
564
565
    /**
566
     * Set security.
567
     */
568 1
    public function setSecurity(Document\Security $documentSecurity): void
569
    {
570 1
        $this->security = $documentSecurity;
571
    }
572
573
    /**
574
     * Get active sheet.
575
     *
576
     * @return Worksheet
577
     */
578 6447
    public function getActiveSheet()
579
    {
580 6447
        return $this->getSheet($this->activeSheetIndex);
581
    }
582
583
    /**
584
     * Create sheet and add it to this workbook.
585
     *
586
     * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
587
     *
588
     * @return Worksheet
589
     */
590 843
    public function createSheet($sheetIndex = null)
591
    {
592 843
        $newSheet = new Worksheet($this);
593 843
        $this->addSheet($newSheet, $sheetIndex);
594
595 843
        return $newSheet;
596
    }
597
598
    /**
599
     * Check if a sheet with a specified name already exists.
600
     *
601
     * @param string $worksheetName Name of the worksheet to check
602
     *
603
     * @return bool
604
     */
605 6502
    public function sheetNameExists($worksheetName)
606
    {
607 6502
        return $this->getSheetByName($worksheetName) !== null;
608
    }
609
610
    /**
611
     * Add sheet.
612
     *
613
     * @param Worksheet $worksheet The worksheet to add
614
     * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
615
     *
616
     * @return Worksheet
617
     */
618 924
    public function addSheet(Worksheet $worksheet, $sheetIndex = null)
619
    {
620 924
        if ($this->sheetNameExists($worksheet->getTitle())) {
621 1
            throw new Exception(
622 1
                "Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename this worksheet first."
623
            );
624
        }
625
626 924
        if ($sheetIndex === null) {
627 895
            if ($this->activeSheetIndex < 0) {
628 559
                $this->activeSheetIndex = 0;
629
            }
630 895
            $this->workSheetCollection[] = $worksheet;
631
        } else {
632
            // Insert the sheet at the requested index
633 32
            array_splice(
634 32
                $this->workSheetCollection,
635
                $sheetIndex,
636
                0,
637 32
                [$worksheet]
638
            );
639
640
            // Adjust active sheet index if necessary
641 32
            if ($this->activeSheetIndex >= $sheetIndex) {
642 28
                ++$this->activeSheetIndex;
643
            }
644
        }
645
646 924
        if ($worksheet->getParent() === null) { // @phpstan-ignore-line
647 50
            $worksheet->rebindParent($this);
648
        }
649
650 924
        return $worksheet;
651
    }
652
653
    /**
654
     * Remove sheet by index.
655
     *
656
     * @param int $sheetIndex Index position of the worksheet to remove
657
     */
658 570
    public function removeSheetByIndex($sheetIndex): void
659
    {
660 570
        $numSheets = count($this->workSheetCollection);
661 570
        if ($sheetIndex > $numSheets - 1) {
662 1
            throw new Exception(
663 1
                "You tried to remove a sheet by the out of bounds index: {$sheetIndex}. The actual number of sheets is {$numSheets}."
664
            );
665
        }
666 569
        array_splice($this->workSheetCollection, $sheetIndex, 1);
667
668
        // Adjust active sheet index if necessary
669
        if (
670 569
            ($this->activeSheetIndex >= $sheetIndex) &&
671 569
            ($this->activeSheetIndex > 0 || $numSheets <= 1)
672
        ) {
673 567
            --$this->activeSheetIndex;
674
        }
675
    }
676
677
    /**
678
     * Get sheet by index.
679
     *
680
     * @param int $sheetIndex Sheet index
681
     *
682
     * @return Worksheet
683
     */
684 6463
    public function getSheet($sheetIndex)
685
    {
686 6463
        if (!isset($this->workSheetCollection[$sheetIndex])) {
687 1
            $numSheets = $this->getSheetCount();
688
689 1
            throw new Exception(
690 1
                "Your requested sheet index: {$sheetIndex} is out of bounds. The actual number of sheets is {$numSheets}."
691
            );
692
        }
693
694 6463
        return $this->workSheetCollection[$sheetIndex];
695
    }
696
697
    /**
698
     * Get all sheets.
699
     *
700
     * @return Worksheet[]
701
     */
702 117
    public function getAllSheets()
703
    {
704 117
        return $this->workSheetCollection;
705
    }
706
707
    /**
708
     * Get sheet by name.
709
     *
710
     * @param string $worksheetName Sheet name
711
     *
712
     * @return null|Worksheet
713
     */
714 6502
    public function getSheetByName($worksheetName)
715
    {
716 6502
        $worksheetCount = count($this->workSheetCollection);
717 6502
        for ($i = 0; $i < $worksheetCount; ++$i) {
718 5519
            if ($this->workSheetCollection[$i]->getTitle() === trim($worksheetName, "'")) {
719 5042
                return $this->workSheetCollection[$i];
720
            }
721
        }
722
723 6502
        return null;
724
    }
725
726
    /**
727
     * Get sheet by name, throwing exception if not found.
728
     */
729 206
    public function getSheetByNameOrThrow(string $worksheetName): Worksheet
730
    {
731 206
        $worksheet = $this->getSheetByName($worksheetName);
732 206
        if ($worksheet === null) {
733 1
            throw new Exception("Sheet $worksheetName does not exist.");
734
        }
735
736 205
        return $worksheet;
737
    }
738
739
    /**
740
     * Get index for sheet.
741
     *
742
     * @return int index
743
     */
744 5851
    public function getIndex(Worksheet $worksheet)
745
    {
746 5851
        foreach ($this->workSheetCollection as $key => $value) {
747 5851
            if ($value->getHashCode() === $worksheet->getHashCode()) {
748 5850
                return $key;
749
            }
750
        }
751
752 2
        throw new Exception('Sheet does not exist.');
753
    }
754
755
    /**
756
     * Set index for sheet by sheet name.
757
     *
758
     * @param string $worksheetName Sheet name to modify index for
759
     * @param int $newIndexPosition New index for the sheet
760
     *
761
     * @return int New sheet index
762
     */
763 1
    public function setIndexByName($worksheetName, $newIndexPosition)
764
    {
765 1
        $oldIndex = $this->getIndex($this->getSheetByNameOrThrow($worksheetName));
766 1
        $worksheet = array_splice(
767 1
            $this->workSheetCollection,
768
            $oldIndex,
769
            1
770
        );
771 1
        array_splice(
772 1
            $this->workSheetCollection,
773
            $newIndexPosition,
774
            0,
775
            $worksheet
776
        );
777
778 1
        return $newIndexPosition;
779
    }
780
781
    /**
782
     * Get sheet count.
783
     *
784
     * @return int
785
     */
786 1413
    public function getSheetCount()
787
    {
788 1413
        return count($this->workSheetCollection);
789
    }
790
791
    /**
792
     * Get active sheet index.
793
     *
794
     * @return int Active sheet index
795
     */
796 4952
    public function getActiveSheetIndex()
797
    {
798 4952
        return $this->activeSheetIndex;
799
    }
800
801
    /**
802
     * Set active sheet index.
803
     *
804
     * @param int $worksheetIndex Active sheet index
805
     *
806
     * @return Worksheet
807
     */
808 5908
    public function setActiveSheetIndex($worksheetIndex)
809
    {
810 5908
        $numSheets = count($this->workSheetCollection);
811
812 5908
        if ($worksheetIndex > $numSheets - 1) {
813 1
            throw new Exception(
814 1
                "You tried to set a sheet active by the out of bounds index: {$worksheetIndex}. The actual number of sheets is {$numSheets}."
815
            );
816
        }
817 5907
        $this->activeSheetIndex = $worksheetIndex;
818
819 5907
        return $this->getActiveSheet();
820
    }
821
822
    /**
823
     * Set active sheet index by name.
824
     *
825
     * @param string $worksheetName Sheet title
826
     *
827
     * @return Worksheet
828
     */
829 72
    public function setActiveSheetIndexByName($worksheetName)
830
    {
831 72
        if (($worksheet = $this->getSheetByName($worksheetName)) instanceof Worksheet) {
832 70
            $this->setActiveSheetIndex($this->getIndex($worksheet));
833
834 70
            return $worksheet;
835
        }
836
837 2
        throw new Exception('Workbook does not contain sheet:' . $worksheetName);
838
    }
839
840
    /**
841
     * Get sheet names.
842
     *
843
     * @return string[]
844
     */
845 9
    public function getSheetNames()
846
    {
847 9
        $returnValue = [];
848 9
        $worksheetCount = $this->getSheetCount();
849 9
        for ($i = 0; $i < $worksheetCount; ++$i) {
850 9
            $returnValue[] = $this->getSheet($i)->getTitle();
851
        }
852
853 9
        return $returnValue;
854
    }
855
856
    /**
857
     * Add external sheet.
858
     *
859
     * @param Worksheet $worksheet External sheet to add
860
     * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
861
     *
862
     * @return Worksheet
863
     */
864 5
    public function addExternalSheet(Worksheet $worksheet, $sheetIndex = null)
865
    {
866 5
        if ($this->sheetNameExists($worksheet->getTitle())) {
867 1
            throw new Exception("Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename the external sheet first.");
868
        }
869
870
        // count how many cellXfs there are in this workbook currently, we will need this below
871 4
        $countCellXfs = count($this->cellXfCollection);
872
873
        // copy all the shared cellXfs from the external workbook and append them to the current
874 4
        foreach ($worksheet->getParent()->getCellXfCollection() as $cellXf) {
875 4
            $this->addCellXf(clone $cellXf);
876
        }
877
878
        // move sheet to this workbook
879 4
        $worksheet->rebindParent($this);
880
881
        // update the cellXfs
882 4
        foreach ($worksheet->getCoordinates(false) as $coordinate) {
883 4
            $cell = $worksheet->getCell($coordinate);
884 4
            $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
885
        }
886
887
        // update the column dimensions Xfs
888 4
        foreach ($worksheet->getColumnDimensions() as $columnDimension) {
889 1
            $columnDimension->setXfIndex($columnDimension->getXfIndex() + $countCellXfs);
890
        }
891
892
        // update the row dimensions Xfs
893 4
        foreach ($worksheet->getRowDimensions() as $rowDimension) {
894 1
            $xfIndex = $rowDimension->getXfIndex();
895 1
            if ($xfIndex !== null) {
896 1
                $rowDimension->setXfIndex($xfIndex + $countCellXfs);
897
            }
898
        }
899
900 4
        return $this->addSheet($worksheet, $sheetIndex);
901
    }
902
903
    /**
904
     * Get an array of all Named Ranges.
905
     *
906
     * @return DefinedName[]
907
     */
908 9
    public function getNamedRanges(): array
909
    {
910 9
        return array_filter(
911 9
            $this->definedNames,
912 9
            function (DefinedName $definedName) {
913 8
                return $definedName->isFormula() === self::DEFINED_NAME_IS_RANGE;
914
            }
915
        );
916
    }
917
918
    /**
919
     * Get an array of all Named Formulae.
920
     *
921
     * @return DefinedName[]
922
     */
923 9
    public function getNamedFormulae(): array
924
    {
925 9
        return array_filter(
926 9
            $this->definedNames,
927 9
            function (DefinedName $definedName) {
928 8
                return $definedName->isFormula() === self::DEFINED_NAME_IS_FORMULA;
929
            }
930
        );
931
    }
932
933
    /**
934
     * Get an array of all Defined Names (both named ranges and named formulae).
935
     *
936
     * @return DefinedName[]
937
     */
938 372
    public function getDefinedNames(): array
939
    {
940 372
        return $this->definedNames;
941
    }
942
943
    /**
944
     * Add a named range.
945
     * If a named range with this name already exists, then this will replace the existing value.
946
     */
947 204
    public function addNamedRange(NamedRange $namedRange): void
948
    {
949 204
        $this->addDefinedName($namedRange);
950
    }
951
952
    /**
953
     * Add a named formula.
954
     * If a named formula with this name already exists, then this will replace the existing value.
955
     */
956 12
    public function addNamedFormula(NamedFormula $namedFormula): void
957
    {
958 12
        $this->addDefinedName($namedFormula);
959
    }
960
961
    /**
962
     * Add a defined name (either a named range or a named formula).
963
     * If a defined named with this name already exists, then this will replace the existing value.
964
     */
965 299
    public function addDefinedName(DefinedName $definedName): void
966
    {
967 299
        $upperCaseName = StringHelper::strToUpper($definedName->getName());
968 299
        if ($definedName->getScope() == null) {
969
            // global scope
970 286
            $this->definedNames[$upperCaseName] = $definedName;
971
        } else {
972
            // local scope
973 119
            $this->definedNames[$definedName->getScope()->getTitle() . '!' . $upperCaseName] = $definedName;
974
        }
975
    }
976
977
    /**
978
     * Get named range.
979
     *
980
     * @param null|Worksheet $worksheet Scope. Use null for global scope
981
     */
982 23
    public function getNamedRange(string $namedRange, ?Worksheet $worksheet = null): ?NamedRange
983
    {
984 23
        $returnValue = null;
985
986 23
        if ($namedRange !== '') {
987 23
            $namedRange = StringHelper::strToUpper($namedRange);
988
            // first look for global named range
989 23
            $returnValue = $this->getGlobalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE);
990
            // then look for local named range (has priority over global named range if both names exist)
991 23
            $returnValue = $this->getLocalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE, $worksheet) ?: $returnValue;
992
        }
993
994 23
        return $returnValue instanceof NamedRange ? $returnValue : null;
995
    }
996
997
    /**
998
     * Get named formula.
999
     *
1000
     * @param null|Worksheet $worksheet Scope. Use null for global scope
1001
     */
1002 9
    public function getNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): ?NamedFormula
1003
    {
1004 9
        $returnValue = null;
1005
1006 9
        if ($namedFormula !== '') {
1007 9
            $namedFormula = StringHelper::strToUpper($namedFormula);
1008
            // first look for global named formula
1009 9
            $returnValue = $this->getGlobalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA);
1010
            // then look for local named formula (has priority over global named formula if both names exist)
1011 9
            $returnValue = $this->getLocalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA, $worksheet) ?: $returnValue;
1012
        }
1013
1014 9
        return $returnValue instanceof NamedFormula ? $returnValue : null;
1015
    }
1016
1017 32
    private function getGlobalDefinedNameByType(string $name, bool $type): ?DefinedName
1018
    {
1019 32
        if (isset($this->definedNames[$name]) && $this->definedNames[$name]->isFormula() === $type) {
1020 25
            return $this->definedNames[$name];
1021
        }
1022
1023 9
        return null;
1024
    }
1025
1026 32
    private function getLocalDefinedNameByType(string $name, bool $type, ?Worksheet $worksheet = null): ?DefinedName
1027
    {
1028
        if (
1029 32
            ($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $name])
1030 32
            && $this->definedNames[$worksheet->getTitle() . '!' . $name]->isFormula() === $type
1031
        ) {
1032 8
            return $this->definedNames[$worksheet->getTitle() . '!' . $name];
1033
        }
1034
1035 30
        return null;
1036
    }
1037
1038
    /**
1039
     * Get named range.
1040
     *
1041
     * @param null|Worksheet $worksheet Scope. Use null for global scope
1042
     */
1043 5826
    public function getDefinedName(string $definedName, ?Worksheet $worksheet = null): ?DefinedName
1044
    {
1045 5826
        $returnValue = null;
1046
1047 5826
        if ($definedName !== '') {
1048 5826
            $definedName = StringHelper::strToUpper($definedName);
1049
            // first look for global defined name
1050 5826
            if (isset($this->definedNames[$definedName])) {
1051 113
                $returnValue = $this->definedNames[$definedName];
1052
            }
1053
1054
            // then look for local defined name (has priority over global defined name if both names exist)
1055 5826
            if (($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) {
1056 20
                $returnValue = $this->definedNames[$worksheet->getTitle() . '!' . $definedName];
1057
            }
1058
        }
1059
1060 5826
        return $returnValue;
1061
    }
1062
1063
    /**
1064
     * Remove named range.
1065
     *
1066
     * @param null|Worksheet $worksheet scope: use null for global scope
1067
     *
1068
     * @return $this
1069
     */
1070 5
    public function removeNamedRange(string $namedRange, ?Worksheet $worksheet = null): self
1071
    {
1072 5
        if ($this->getNamedRange($namedRange, $worksheet) === null) {
1073 1
            return $this;
1074
        }
1075
1076 4
        return $this->removeDefinedName($namedRange, $worksheet);
1077
    }
1078
1079
    /**
1080
     * Remove named formula.
1081
     *
1082
     * @param null|Worksheet $worksheet scope: use null for global scope
1083
     *
1084
     * @return $this
1085
     */
1086 4
    public function removeNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): self
1087
    {
1088 4
        if ($this->getNamedFormula($namedFormula, $worksheet) === null) {
1089 1
            return $this;
1090
        }
1091
1092 3
        return $this->removeDefinedName($namedFormula, $worksheet);
1093
    }
1094
1095
    /**
1096
     * Remove defined name.
1097
     *
1098
     * @param null|Worksheet $worksheet scope: use null for global scope
1099
     *
1100
     * @return $this
1101
     */
1102 11
    public function removeDefinedName(string $definedName, ?Worksheet $worksheet = null): self
1103
    {
1104 11
        $definedName = StringHelper::strToUpper($definedName);
1105
1106 11
        if ($worksheet === null) {
1107 1
            if (isset($this->definedNames[$definedName])) {
1108 1
                unset($this->definedNames[$definedName]);
1109
            }
1110
        } else {
1111 10
            if (isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) {
1112 3
                unset($this->definedNames[$worksheet->getTitle() . '!' . $definedName]);
1113 7
            } elseif (isset($this->definedNames[$definedName])) {
1114 7
                unset($this->definedNames[$definedName]);
1115
            }
1116
        }
1117
1118 11
        return $this;
1119
    }
1120
1121
    /**
1122
     * Get worksheet iterator.
1123
     *
1124
     * @return Iterator
1125
     */
1126 958
    public function getWorksheetIterator()
1127
    {
1128 958
        return new Iterator($this);
1129
    }
1130
1131
    /**
1132
     * Copy workbook (!= clone!).
1133
     *
1134
     * @return Spreadsheet
1135
     */
1136 1
    public function copy()
1137
    {
1138 1
        $filename = File::temporaryFilename();
1139 1
        $writer = new XlsxWriter($this);
1140 1
        $writer->setIncludeCharts(true);
1141 1
        $writer->save($filename);
1142
1143 1
        $reader = new XlsxReader();
1144 1
        $reader->setIncludeCharts(true);
1145 1
        $reloadedSpreadsheet = $reader->load($filename);
1146 1
        unlink($filename);
1147
1148 1
        return $reloadedSpreadsheet;
1149
    }
1150
1151 1
    public function __clone()
1152
    {
1153 1
        throw new Exception(
1154
            'Do not use clone on spreadsheet. Use spreadsheet->copy() instead.'
1155
        );
1156
    }
1157
1158
    /**
1159
     * Get the workbook collection of cellXfs.
1160
     *
1161
     * @return Style[]
1162
     */
1163 759
    public function getCellXfCollection()
1164
    {
1165 759
        return $this->cellXfCollection;
1166
    }
1167
1168
    /**
1169
     * Get cellXf by index.
1170
     *
1171
     * @param int $cellStyleIndex
1172
     *
1173
     * @return Style
1174
     */
1175 5621
    public function getCellXfByIndex($cellStyleIndex)
1176
    {
1177 5621
        return $this->cellXfCollection[$cellStyleIndex];
1178
    }
1179
1180
    /**
1181
     * Get cellXf by hash code.
1182
     *
1183
     * @param string $hashcode
1184
     *
1185
     * @return false|Style
1186
     */
1187 769
    public function getCellXfByHashCode($hashcode)
1188
    {
1189 769
        foreach ($this->cellXfCollection as $cellXf) {
1190 769
            if ($cellXf->getHashCode() === $hashcode) {
1191 252
                return $cellXf;
1192
            }
1193
        }
1194
1195 644
        return false;
1196
    }
1197
1198
    /**
1199
     * Check if style exists in style collection.
1200
     *
1201
     * @return bool
1202
     */
1203 1
    public function cellXfExists(Style $cellStyleIndex)
1204
    {
1205 1
        return in_array($cellStyleIndex, $this->cellXfCollection, true);
1206
    }
1207
1208
    /**
1209
     * Get default style.
1210
     *
1211
     * @return Style
1212
     */
1213 714
    public function getDefaultStyle()
1214
    {
1215 714
        if (isset($this->cellXfCollection[0])) {
1216 713
            return $this->cellXfCollection[0];
1217
        }
1218
1219 1
        throw new Exception('No default style found for this workbook');
1220
    }
1221
1222
    /**
1223
     * Add a cellXf to the workbook.
1224
     */
1225 6502
    public function addCellXf(Style $style): void
1226
    {
1227 6502
        $this->cellXfCollection[] = $style;
1228 6502
        $style->setIndex(count($this->cellXfCollection) - 1);
1229
    }
1230
1231
    /**
1232
     * Remove cellXf by index. It is ensured that all cells get their xf index updated.
1233
     *
1234
     * @param int $cellStyleIndex Index to cellXf
1235
     */
1236 534
    public function removeCellXfByIndex($cellStyleIndex): void
1237
    {
1238 534
        if ($cellStyleIndex > count($this->cellXfCollection) - 1) {
1239 1
            throw new Exception('CellXf index is out of bounds.');
1240
        }
1241
1242
        // first remove the cellXf
1243 533
        array_splice($this->cellXfCollection, $cellStyleIndex, 1);
1244
1245
        // then update cellXf indexes for cells
1246 533
        foreach ($this->workSheetCollection as $worksheet) {
1247 2
            foreach ($worksheet->getCoordinates(false) as $coordinate) {
1248 1
                $cell = $worksheet->getCell($coordinate);
1249 1
                $xfIndex = $cell->getXfIndex();
1250 1
                if ($xfIndex > $cellStyleIndex) {
1251
                    // decrease xf index by 1
1252 1
                    $cell->setXfIndex($xfIndex - 1);
1253 1
                } elseif ($xfIndex == $cellStyleIndex) {
1254
                    // set to default xf index 0
1255 1
                    $cell->setXfIndex(0);
1256
                }
1257
            }
1258
        }
1259
    }
1260
1261
    /**
1262
     * Get the cellXf supervisor.
1263
     *
1264
     * @return Style
1265
     */
1266 5620
    public function getCellXfSupervisor()
1267
    {
1268 5620
        return $this->cellXfSupervisor;
1269
    }
1270
1271
    /**
1272
     * Get the workbook collection of cellStyleXfs.
1273
     *
1274
     * @return Style[]
1275
     */
1276 84
    public function getCellStyleXfCollection()
1277
    {
1278 84
        return $this->cellStyleXfCollection;
1279
    }
1280
1281
    /**
1282
     * Get cellStyleXf by index.
1283
     *
1284
     * @param int $cellStyleIndex Index to cellXf
1285
     *
1286
     * @return Style
1287
     */
1288 1
    public function getCellStyleXfByIndex($cellStyleIndex)
1289
    {
1290 1
        return $this->cellStyleXfCollection[$cellStyleIndex];
1291
    }
1292
1293
    /**
1294
     * Get cellStyleXf by hash code.
1295
     *
1296
     * @param string $hashcode
1297
     *
1298
     * @return false|Style
1299
     */
1300 1
    public function getCellStyleXfByHashCode($hashcode)
1301
    {
1302 1
        foreach ($this->cellStyleXfCollection as $cellStyleXf) {
1303 1
            if ($cellStyleXf->getHashCode() === $hashcode) {
1304 1
                return $cellStyleXf;
1305
            }
1306
        }
1307
1308 1
        return false;
1309
    }
1310
1311
    /**
1312
     * Add a cellStyleXf to the workbook.
1313
     */
1314 6502
    public function addCellStyleXf(Style $style): void
1315
    {
1316 6502
        $this->cellStyleXfCollection[] = $style;
1317 6502
        $style->setIndex(count($this->cellStyleXfCollection) - 1);
1318
    }
1319
1320
    /**
1321
     * Remove cellStyleXf by index.
1322
     *
1323
     * @param int $cellStyleIndex Index to cellXf
1324
     */
1325 532
    public function removeCellStyleXfByIndex($cellStyleIndex): void
1326
    {
1327 532
        if ($cellStyleIndex > count($this->cellStyleXfCollection) - 1) {
1328 1
            throw new Exception('CellStyleXf index is out of bounds.');
1329
        }
1330 531
        array_splice($this->cellStyleXfCollection, $cellStyleIndex, 1);
1331
    }
1332
1333
    /**
1334
     * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
1335
     * and columns in the workbook.
1336
     */
1337 716
    public function garbageCollect(): void
1338
    {
1339
        // how many references are there to each cellXf ?
1340 716
        $countReferencesCellXf = [];
1341 716
        foreach ($this->cellXfCollection as $index => $cellXf) {
1342 716
            $countReferencesCellXf[$index] = 0;
1343
        }
1344
1345 716
        foreach ($this->getWorksheetIterator() as $sheet) {
1346
            // from cells
1347 716
            foreach ($sheet->getCoordinates(false) as $coordinate) {
1348 703
                $cell = $sheet->getCell($coordinate);
1349 703
                ++$countReferencesCellXf[$cell->getXfIndex()];
1350
            }
1351
1352
            // from row dimensions
1353 716
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1354 86
                if ($rowDimension->getXfIndex() !== null) {
1355 2
                    ++$countReferencesCellXf[$rowDimension->getXfIndex()];
1356
                }
1357
            }
1358
1359
            // from column dimensions
1360 716
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1361 93
                ++$countReferencesCellXf[$columnDimension->getXfIndex()];
1362
            }
1363
        }
1364
1365
        // remove cellXfs without references and create mapping so we can update xfIndex
1366
        // for all cells and columns
1367 716
        $countNeededCellXfs = 0;
1368 716
        $map = [];
1369 716
        foreach ($this->cellXfCollection as $index => $cellXf) {
1370 716
            if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
1371 716
                ++$countNeededCellXfs;
1372
            } else {
1373 25
                unset($this->cellXfCollection[$index]);
1374
            }
1375 716
            $map[$index] = $countNeededCellXfs - 1;
1376
        }
1377 716
        $this->cellXfCollection = array_values($this->cellXfCollection);
1378
1379
        // update the index for all cellXfs
1380 716
        foreach ($this->cellXfCollection as $i => $cellXf) {
1381 716
            $cellXf->setIndex($i);
1382
        }
1383
1384
        // make sure there is always at least one cellXf (there should be)
1385 716
        if (empty($this->cellXfCollection)) {
1386
            $this->cellXfCollection[] = new Style();
1387
        }
1388
1389
        // update the xfIndex for all cells, row dimensions, column dimensions
1390 716
        foreach ($this->getWorksheetIterator() as $sheet) {
1391
            // for all cells
1392 716
            foreach ($sheet->getCoordinates(false) as $coordinate) {
1393 703
                $cell = $sheet->getCell($coordinate);
1394 703
                $cell->setXfIndex($map[$cell->getXfIndex()]);
1395
            }
1396
1397
            // for all row dimensions
1398 716
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1399 86
                if ($rowDimension->getXfIndex() !== null) {
1400 2
                    $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
1401
                }
1402
            }
1403
1404
            // for all column dimensions
1405 716
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1406 93
                $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
1407
            }
1408
1409
            // also do garbage collection for all the sheets
1410 716
            $sheet->garbageCollect();
1411
        }
1412
    }
1413
1414
    /**
1415
     * Return the unique ID value assigned to this spreadsheet workbook.
1416
     *
1417
     * @return string
1418
     */
1419
    public function getID()
1420
    {
1421
        return $this->uniqueID;
1422
    }
1423
1424
    /**
1425
     * Get the visibility of the horizonal scroll bar in the application.
1426
     *
1427
     * @return bool True if horizonal scroll bar is visible
1428
     */
1429 248
    public function getShowHorizontalScroll()
1430
    {
1431 248
        return $this->showHorizontalScroll;
1432
    }
1433
1434
    /**
1435
     * Set the visibility of the horizonal scroll bar in the application.
1436
     *
1437
     * @param bool $showHorizontalScroll True if horizonal scroll bar is visible
1438
     */
1439 154
    public function setShowHorizontalScroll($showHorizontalScroll): void
1440
    {
1441 154
        $this->showHorizontalScroll = (bool) $showHorizontalScroll;
1442
    }
1443
1444
    /**
1445
     * Get the visibility of the vertical scroll bar in the application.
1446
     *
1447
     * @return bool True if vertical scroll bar is visible
1448
     */
1449 248
    public function getShowVerticalScroll()
1450
    {
1451 248
        return $this->showVerticalScroll;
1452
    }
1453
1454
    /**
1455
     * Set the visibility of the vertical scroll bar in the application.
1456
     *
1457
     * @param bool $showVerticalScroll True if vertical scroll bar is visible
1458
     */
1459 154
    public function setShowVerticalScroll($showVerticalScroll): void
1460
    {
1461 154
        $this->showVerticalScroll = (bool) $showVerticalScroll;
1462
    }
1463
1464
    /**
1465
     * Get the visibility of the sheet tabs in the application.
1466
     *
1467
     * @return bool True if the sheet tabs are visible
1468
     */
1469 248
    public function getShowSheetTabs()
1470
    {
1471 248
        return $this->showSheetTabs;
1472
    }
1473
1474
    /**
1475
     * Set the visibility of the sheet tabs  in the application.
1476
     *
1477
     * @param bool $showSheetTabs True if sheet tabs are visible
1478
     */
1479 154
    public function setShowSheetTabs($showSheetTabs): void
1480
    {
1481 154
        $this->showSheetTabs = (bool) $showSheetTabs;
1482
    }
1483
1484
    /**
1485
     * Return whether the workbook window is minimized.
1486
     *
1487
     * @return bool true if workbook window is minimized
1488
     */
1489 248
    public function getMinimized()
1490
    {
1491 248
        return $this->minimized;
1492
    }
1493
1494
    /**
1495
     * Set whether the workbook window is minimized.
1496
     *
1497
     * @param bool $minimized true if workbook window is minimized
1498
     */
1499 150
    public function setMinimized($minimized): void
1500
    {
1501 150
        $this->minimized = (bool) $minimized;
1502
    }
1503
1504
    /**
1505
     * Return whether to group dates when presenting the user with
1506
     * filtering optiomd in the user interface.
1507
     *
1508
     * @return bool true if workbook window is minimized
1509
     */
1510 248
    public function getAutoFilterDateGrouping()
1511
    {
1512 248
        return $this->autoFilterDateGrouping;
1513
    }
1514
1515
    /**
1516
     * Set whether to group dates when presenting the user with
1517
     * filtering optiomd in the user interface.
1518
     *
1519
     * @param bool $autoFilterDateGrouping true if workbook window is minimized
1520
     */
1521 150
    public function setAutoFilterDateGrouping($autoFilterDateGrouping): void
1522
    {
1523 150
        $this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping;
1524
    }
1525
1526
    /**
1527
     * Return the first sheet in the book view.
1528
     *
1529
     * @return int First sheet in book view
1530
     */
1531 248
    public function getFirstSheetIndex()
1532
    {
1533 248
        return $this->firstSheetIndex;
1534
    }
1535
1536
    /**
1537
     * Set the first sheet in the book view.
1538
     *
1539
     * @param int $firstSheetIndex First sheet in book view
1540
     */
1541 155
    public function setFirstSheetIndex($firstSheetIndex): void
1542
    {
1543 155
        if ($firstSheetIndex >= 0) {
1544 154
            $this->firstSheetIndex = (int) $firstSheetIndex;
1545
        } else {
1546 1
            throw new Exception('First sheet index must be a positive integer.');
1547
        }
1548
    }
1549
1550
    /**
1551
     * Return the visibility status of the workbook.
1552
     *
1553
     * This may be one of the following three values:
1554
     * - visibile
1555
     *
1556
     * @return string Visible status
1557
     */
1558 249
    public function getVisibility()
1559
    {
1560 249
        return $this->visibility;
1561
    }
1562
1563
    /**
1564
     * Set the visibility status of the workbook.
1565
     *
1566
     * Valid values are:
1567
     *  - 'visible' (self::VISIBILITY_VISIBLE):
1568
     *       Workbook window is visible
1569
     *  - 'hidden' (self::VISIBILITY_HIDDEN):
1570
     *       Workbook window is hidden, but can be shown by the user
1571
     *       via the user interface
1572
     *  - 'veryHidden' (self::VISIBILITY_VERY_HIDDEN):
1573
     *       Workbook window is hidden and cannot be shown in the
1574
     *       user interface.
1575
     *
1576
     * @param null|string $visibility visibility status of the workbook
1577
     */
1578 151
    public function setVisibility($visibility): void
1579
    {
1580 151
        if ($visibility === null) {
1581 1
            $visibility = self::VISIBILITY_VISIBLE;
1582
        }
1583
1584 151
        if (in_array($visibility, self::WORKBOOK_VIEW_VISIBILITY_VALUES)) {
1585 151
            $this->visibility = $visibility;
1586
        } else {
1587 1
            throw new Exception('Invalid visibility value.');
1588
        }
1589
    }
1590
1591
    /**
1592
     * Get the ratio between the workbook tabs bar and the horizontal scroll bar.
1593
     * TabRatio is assumed to be out of 1000 of the horizontal window width.
1594
     *
1595
     * @return int Ratio between the workbook tabs bar and the horizontal scroll bar
1596
     */
1597 248
    public function getTabRatio()
1598
    {
1599 248
        return $this->tabRatio;
1600
    }
1601
1602
    /**
1603
     * Set the ratio between the workbook tabs bar and the horizontal scroll bar
1604
     * TabRatio is assumed to be out of 1000 of the horizontal window width.
1605
     *
1606
     * @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar
1607
     */
1608 158
    public function setTabRatio($tabRatio): void
1609
    {
1610 158
        if ($tabRatio >= 0 && $tabRatio <= 1000) {
1611 157
            $this->tabRatio = (int) $tabRatio;
1612
        } else {
1613 1
            throw new Exception('Tab ratio must be between 0 and 1000.');
1614
        }
1615
    }
1616
1617 2
    public function reevaluateAutoFilters(bool $resetToMax): void
1618
    {
1619 2
        foreach ($this->workSheetCollection as $sheet) {
1620 2
            $filter = $sheet->getAutoFilter();
1621 2
            if (!empty($filter->getRange())) {
1622 2
                if ($resetToMax) {
1623 1
                    $filter->setRangeToMaxRow();
1624
                }
1625 2
                $filter->showHideRows();
1626
            }
1627
        }
1628
    }
1629
1630
    /**
1631
     * Silliness to mollify Scrutinizer.
1632
     *
1633
     * @codeCoverageIgnore
1634
     */
1635
    public function getSharedComponent(): Style
1636
    {
1637
        return new Style();
1638
    }
1639
1640
    /**
1641
     * @throws Exception
1642
     *
1643
     * @return mixed
1644
     */
1645 1
    public function __serialize()
1646
    {
1647 1
        throw new Exception('Spreadsheet objects cannot be serialized');
1648
    }
1649
1650
    /**
1651
     * @throws Exception
1652
     */
1653 1
    public function jsonSerialize(): mixed
1654
    {
1655 1
        throw new Exception('Spreadsheet objects cannot be json encoded');
1656
    }
1657
}
1658