Failed Conditions
Push — master ( b01a48...e5185e )
by Adrien
190:50 queued 106:17
created

Spreadsheet::garbageCollect()   F

Complexity

Conditions 17
Paths 4056

Size

Total Lines 74
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 17.1819

Importance

Changes 0
Metric Value
cc 17
eloc 35
c 0
b 0
f 0
nc 4056
nop 0
dl 0
loc 74
ccs 32
cts 35
cp 0.9143
crap 17.1819
rs 1.0499

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;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
7
use PhpOffice\PhpSpreadsheet\Style\Style;
8
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
9
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
10
11
class Spreadsheet
12
{
13
    // Allowable values for workbook window visilbity
14
    const VISIBILITY_VISIBLE = 'visible';
15
    const VISIBILITY_HIDDEN = 'hidden';
16
    const VISIBILITY_VERY_HIDDEN = 'veryHidden';
17
18
    private const DEFINED_NAME_IS_RANGE = false;
19
    private const DEFINED_NAME_IS_FORMULA = true;
20
21
    private static $workbookViewVisibilityValues = [
22
        self::VISIBILITY_VISIBLE,
23
        self::VISIBILITY_HIDDEN,
24
        self::VISIBILITY_VERY_HIDDEN,
25
    ];
26
27
    /**
28
     * Unique ID.
29
     *
30
     * @var string
31
     */
32
    private $uniqueID;
33
34
    /**
35
     * Document properties.
36
     *
37
     * @var Document\Properties
38
     */
39
    private $properties;
40
41
    /**
42
     * Document security.
43
     *
44
     * @var Document\Security
45
     */
46
    private $security;
47
48
    /**
49
     * Collection of Worksheet objects.
50
     *
51
     * @var Worksheet[]
52
     */
53
    private $workSheetCollection = [];
54
55
    /**
56
     * Calculation Engine.
57
     *
58
     * @var null|Calculation
59
     */
60
    private $calculationEngine;
61
62
    /**
63
     * Active sheet index.
64
     *
65
     * @var int
66
     */
67
    private $activeSheetIndex = 0;
68
69
    /**
70
     * Named ranges.
71
     *
72
     * @var DefinedName[]
73
     */
74
    private $definedNames = [];
75
76
    /**
77
     * CellXf supervisor.
78
     *
79
     * @var Style
80
     */
81
    private $cellXfSupervisor;
82
83
    /**
84
     * CellXf collection.
85
     *
86
     * @var Style[]
87
     */
88
    private $cellXfCollection = [];
89
90
    /**
91
     * CellStyleXf collection.
92
     *
93
     * @var Style[]
94
     */
95
    private $cellStyleXfCollection = [];
96
97
    /**
98
     * hasMacros : this workbook have macros ?
99
     *
100
     * @var bool
101
     */
102
    private $hasMacros = false;
103
104
    /**
105
     * macrosCode : all macros code as binary data (the vbaProject.bin file, this include form, code,  etc.), null if no macro.
106
     *
107
     * @var null|string
108
     */
109
    private $macrosCode;
110
111
    /**
112
     * macrosCertificate : if macros are signed, contains binary data vbaProjectSignature.bin file, null if not signed.
113
     *
114
     * @var null|string
115
     */
116
    private $macrosCertificate;
117
118
    /**
119
     * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI.
120
     *
121
     * @var null|array{target: string, data: string}
122
     */
123
    private $ribbonXMLData;
124
125
    /**
126
     * ribbonBinObjects : null if workbook is'nt Excel 2007 or not contain embedded objects (picture(s)) for Ribbon Elements
127
     * ignored if $ribbonXMLData is null.
128
     *
129
     * @var null|array
130
     */
131
    private $ribbonBinObjects;
132
133
    /**
134
     * List of unparsed loaded data for export to same format with better compatibility.
135
     * It has to be minimized when the library start to support currently unparsed data.
136
     *
137
     * @var array
138
     */
139
    private $unparsedLoadedData = [];
140
141
    /**
142
     * Controls visibility of the horizonal scroll bar in the application.
143
     *
144
     * @var bool
145
     */
146
    private $showHorizontalScroll = true;
147
148
    /**
149
     * Controls visibility of the horizonal scroll bar in the application.
150
     *
151
     * @var bool
152
     */
153
    private $showVerticalScroll = true;
154
155
    /**
156
     * Controls visibility of the sheet tabs in the application.
157
     *
158
     * @var bool
159
     */
160
    private $showSheetTabs = true;
161
162
    /**
163
     * Specifies a boolean value that indicates whether the workbook window
164
     * is minimized.
165
     *
166
     * @var bool
167
     */
168
    private $minimized = false;
169
170
    /**
171
     * Specifies a boolean value that indicates whether to group dates
172
     * when presenting the user with filtering optiomd in the user
173
     * interface.
174
     *
175
     * @var bool
176
     */
177
    private $autoFilterDateGrouping = true;
178
179
    /**
180
     * Specifies the index to the first sheet in the book view.
181
     *
182
     * @var int
183
     */
184
    private $firstSheetIndex = 0;
185
186
    /**
187
     * Specifies the visible status of the workbook.
188
     *
189
     * @var string
190
     */
191
    private $visibility = self::VISIBILITY_VISIBLE;
192
193
    /**
194
     * Specifies the ratio between the workbook tabs bar and the horizontal
195
     * scroll bar.  TabRatio is assumed to be out of 1000 of the horizontal
196
     * window width.
197
     *
198
     * @var int
199
     */
200
    private $tabRatio = 600;
201
202
    /**
203
     * The workbook has macros ?
204
     *
205
     * @return bool
206
     */
207 134
    public function hasMacros()
208
    {
209 134
        return $this->hasMacros;
210
    }
211
212
    /**
213
     * Define if a workbook has macros.
214
     *
215
     * @param bool $hasMacros true|false
216
     */
217 1
    public function setHasMacros($hasMacros): void
218
    {
219 1
        $this->hasMacros = (bool) $hasMacros;
220 1
    }
221
222
    /**
223
     * Set the macros code.
224
     *
225
     * @param string $macroCode string|null
226
     */
227 1
    public function setMacrosCode($macroCode): void
228
    {
229 1
        $this->macrosCode = $macroCode;
230 1
        $this->setHasMacros($macroCode !== null);
231 1
    }
232
233
    /**
234
     * Return the macros code.
235
     *
236
     * @return null|string
237
     */
238 1
    public function getMacrosCode()
239
    {
240 1
        return $this->macrosCode;
241
    }
242
243
    /**
244
     * Set the macros certificate.
245
     *
246
     * @param null|string $certificate
247
     */
248
    public function setMacrosCertificate($certificate): void
249
    {
250
        $this->macrosCertificate = $certificate;
251
    }
252
253
    /**
254
     * Is the project signed ?
255
     *
256
     * @return bool true|false
257
     */
258 1
    public function hasMacrosCertificate()
259
    {
260 1
        return $this->macrosCertificate !== null;
261
    }
262
263
    /**
264
     * Return the macros certificate.
265
     *
266
     * @return null|string
267
     */
268
    public function getMacrosCertificate()
269
    {
270
        return $this->macrosCertificate;
271
    }
272
273
    /**
274
     * Remove all macros, certificate from spreadsheet.
275
     */
276
    public function discardMacros(): void
277
    {
278
        $this->hasMacros = false;
279
        $this->macrosCode = null;
280
        $this->macrosCertificate = null;
281
    }
282
283
    /**
284
     * set ribbon XML data.
285
     *
286
     * @param null|mixed $target
287
     * @param null|mixed $xmlData
288
     */
289
    public function setRibbonXMLData($target, $xmlData): void
290
    {
291
        if ($target !== null && $xmlData !== null) {
292
            $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData];
293
        } else {
294
            $this->ribbonXMLData = null;
295
        }
296
    }
297
298
    /**
299
     * retrieve ribbon XML Data.
300
     *
301
     * @param string $what
302
     *
303
     * @return null|array|string
304
     */
305
    public function getRibbonXMLData($what = 'all') //we need some constants here...
306
    {
307
        $returnData = null;
308
        $what = strtolower($what);
309
        switch ($what) {
310
            case 'all':
311
                $returnData = $this->ribbonXMLData;
312
313
                break;
314
            case 'target':
315
            case 'data':
316
                if (is_array($this->ribbonXMLData) && isset($this->ribbonXMLData[$what])) {
317
                    $returnData = $this->ribbonXMLData[$what];
318
                }
319
320
                break;
321
        }
322
323
        return $returnData;
324
    }
325
326
    /**
327
     * store binaries ribbon objects (pictures).
328
     *
329
     * @param null|mixed $BinObjectsNames
330
     * @param null|mixed $BinObjectsData
331
     */
332
    public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData): void
333
    {
334
        if ($BinObjectsNames !== null && $BinObjectsData !== null) {
335
            $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData];
336
        } else {
337
            $this->ribbonBinObjects = null;
338
        }
339
    }
340
341
    /**
342
     * List of unparsed loaded data for export to same format with better compatibility.
343
     * It has to be minimized when the library start to support currently unparsed data.
344
     *
345
     * @internal
346
     *
347
     * @return array
348
     */
349 134
    public function getUnparsedLoadedData()
350
    {
351 134
        return $this->unparsedLoadedData;
352
    }
353
354
    /**
355
     * List of unparsed loaded data for export to same format with better compatibility.
356
     * It has to be minimized when the library start to support currently unparsed data.
357
     *
358
     * @internal
359
     */
360 133
    public function setUnparsedLoadedData(array $unparsedLoadedData): void
361
    {
362 133
        $this->unparsedLoadedData = $unparsedLoadedData;
363 133
    }
364
365
    /**
366
     * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
367
     *
368
     * @param mixed $path
369
     *
370
     * @return string
371
     */
372
    private function getExtensionOnly($path)
373
    {
374
        $extension = pathinfo($path, PATHINFO_EXTENSION);
375
376
        return is_array($extension) ? '' : $extension;
377
    }
378
379
    /**
380
     * retrieve Binaries Ribbon Objects.
381
     *
382
     * @param string $what
383
     *
384
     * @return null|array
385
     */
386
    public function getRibbonBinObjects($what = 'all')
387
    {
388
        $ReturnData = null;
389
        $what = strtolower($what);
390
        switch ($what) {
391
            case 'all':
392
                return $this->ribbonBinObjects;
393
394
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
395
            case 'names':
396
            case 'data':
397
                if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) {
398
                    $ReturnData = $this->ribbonBinObjects[$what];
399
                }
400
401
                break;
402
            case 'types':
403
                if (
404
                    is_array($this->ribbonBinObjects) &&
405
                    isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data'])
406
                ) {
407
                    $tmpTypes = array_keys($this->ribbonBinObjects['data']);
408
                    $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes));
409
                } else {
410
                    $ReturnData = []; // the caller want an array... not null if empty
411
                }
412
413
                break;
414
        }
415
416
        return $ReturnData;
417
    }
418
419
    /**
420
     * This workbook have a custom UI ?
421
     *
422
     * @return bool
423
     */
424 133
    public function hasRibbon()
425
    {
426 133
        return $this->ribbonXMLData !== null;
427
    }
428
429
    /**
430
     * This workbook have additionnal object for the ribbon ?
431
     *
432
     * @return bool
433
     */
434 133
    public function hasRibbonBinObjects()
435
    {
436 133
        return $this->ribbonBinObjects !== null;
437
    }
438
439
    /**
440
     * Check if a sheet with a specified code name already exists.
441
     *
442
     * @param string $pSheetCodeName Name of the worksheet to check
443
     *
444
     * @return bool
445
     */
446 4111
    public function sheetCodeNameExists($pSheetCodeName)
447
    {
448 4111
        return $this->getSheetByCodeName($pSheetCodeName) !== null;
449
    }
450
451
    /**
452
     * Get sheet by code name. Warning : sheet don't have always a code name !
453
     *
454
     * @param string $pName Sheet name
455
     *
456
     * @return null|Worksheet
457
     */
458 4111
    public function getSheetByCodeName($pName)
459
    {
460 4111
        $worksheetCount = count($this->workSheetCollection);
461 4111
        for ($i = 0; $i < $worksheetCount; ++$i) {
462 259
            if ($this->workSheetCollection[$i]->getCodeName() == $pName) {
463 169
                return $this->workSheetCollection[$i];
464
            }
465
        }
466
467 4111
        return null;
468
    }
469
470
    /**
471
     * Create a new PhpSpreadsheet with one Worksheet.
472
     */
473 4111
    public function __construct()
474
    {
475 4111
        $this->uniqueID = uniqid('', true);
476 4111
        $this->calculationEngine = new Calculation($this);
477
478
        // Initialise worksheet collection and add one worksheet
479 4111
        $this->workSheetCollection = [];
480 4111
        $this->workSheetCollection[] = new Worksheet($this);
481 4111
        $this->activeSheetIndex = 0;
482
483
        // Create document properties
484 4111
        $this->properties = new Document\Properties();
485
486
        // Create document security
487 4111
        $this->security = new Document\Security();
488
489
        // Set defined names
490 4111
        $this->definedNames = [];
491
492
        // Create the cellXf supervisor
493 4111
        $this->cellXfSupervisor = new Style(true);
494 4111
        $this->cellXfSupervisor->bindParent($this);
495
496
        // Create the default style
497 4111
        $this->addCellXf(new Style());
498 4111
        $this->addCellStyleXf(new Style());
499 4111
    }
500
501
    /**
502
     * Code to execute when this worksheet is unset().
503
     */
504 31
    public function __destruct()
505
    {
506 31
        $this->disconnectWorksheets();
507 31
        $this->calculationEngine = null;
508 31
        $this->cellXfCollection = [];
509 31
        $this->cellStyleXfCollection = [];
510 31
    }
511
512
    /**
513
     * Disconnect all worksheets from this PhpSpreadsheet workbook object,
514
     * typically so that the PhpSpreadsheet object can be unset.
515
     */
516 2477
    public function disconnectWorksheets(): void
517
    {
518 2477
        foreach ($this->workSheetCollection as $worksheet) {
519 2477
            $worksheet->disconnectCells();
520 2477
            unset($worksheet);
521
        }
522 2477
        $this->workSheetCollection = [];
523 2477
    }
524
525
    /**
526
     * Return the calculation engine for this worksheet.
527
     *
528
     * @return null|Calculation
529
     */
530 4111
    public function getCalculationEngine()
531
    {
532 4111
        return $this->calculationEngine;
533
    }
534
535
    /**
536
     * Get properties.
537
     *
538
     * @return Document\Properties
539
     */
540 446
    public function getProperties()
541
    {
542 446
        return $this->properties;
543
    }
544
545
    /**
546
     * Set properties.
547
     */
548
    public function setProperties(Document\Properties $pValue): void
549
    {
550
        $this->properties = $pValue;
551
    }
552
553
    /**
554
     * Get security.
555
     *
556
     * @return Document\Security
557
     */
558 133
    public function getSecurity()
559
    {
560 133
        return $this->security;
561
    }
562
563
    /**
564
     * Set security.
565
     */
566
    public function setSecurity(Document\Security $pValue): void
567
    {
568
        $this->security = $pValue;
569
    }
570
571
    /**
572
     * Get active sheet.
573
     *
574
     * @return Worksheet
575
     */
576 4076
    public function getActiveSheet()
577
    {
578 4076
        return $this->getSheet($this->activeSheetIndex);
579
    }
580
581
    /**
582
     * Create sheet and add it to this workbook.
583
     *
584
     * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
585
     *
586
     * @return Worksheet
587
     */
588 359
    public function createSheet($sheetIndex = null)
589
    {
590 359
        $newSheet = new Worksheet($this);
591 359
        $this->addSheet($newSheet, $sheetIndex);
592
593 359
        return $newSheet;
594
    }
595
596
    /**
597
     * Check if a sheet with a specified name already exists.
598
     *
599
     * @param string $pSheetName Name of the worksheet to check
600
     *
601
     * @return bool
602
     */
603 4111
    public function sheetNameExists($pSheetName)
604
    {
605 4111
        return $this->getSheetByName($pSheetName) !== null;
606
    }
607
608
    /**
609
     * Add sheet.
610
     *
611
     * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
612
     *
613
     * @return Worksheet
614
     */
615 432
    public function addSheet(Worksheet $pSheet, $iSheetIndex = null)
616
    {
617 432
        if ($this->sheetNameExists($pSheet->getTitle())) {
618 1
            throw new Exception(
619 1
                "Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename this worksheet first."
620
            );
621
        }
622
623 432
        if ($iSheetIndex === null) {
624 403
            if ($this->activeSheetIndex < 0) {
625 198
                $this->activeSheetIndex = 0;
626
            }
627 403
            $this->workSheetCollection[] = $pSheet;
628
        } else {
629
            // Insert the sheet at the requested index
630 32
            array_splice(
631 32
                $this->workSheetCollection,
632
                $iSheetIndex,
633 32
                0,
634 32
                [$pSheet]
635
            );
636
637
            // Adjust active sheet index if necessary
638 32
            if ($this->activeSheetIndex >= $iSheetIndex) {
639 28
                ++$this->activeSheetIndex;
640
            }
641
        }
642
643 432
        if ($pSheet->getParent() === null) {
644 45
            $pSheet->rebindParent($this);
645
        }
646
647 432
        return $pSheet;
648
    }
649
650
    /**
651
     * Remove sheet by index.
652
     *
653
     * @param int $pIndex Active sheet index
654
     */
655 206
    public function removeSheetByIndex($pIndex): void
656
    {
657 206
        $numSheets = count($this->workSheetCollection);
658 206
        if ($pIndex > $numSheets - 1) {
659 1
            throw new Exception(
660 1
                "You tried to remove a sheet by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
661
            );
662
        }
663 205
        array_splice($this->workSheetCollection, $pIndex, 1);
664
665
        // Adjust active sheet index if necessary
666
        if (
667 205
            ($this->activeSheetIndex >= $pIndex) &&
668 205
            ($this->activeSheetIndex > 0 || $numSheets <= 1)
669
        ) {
670 203
            --$this->activeSheetIndex;
671
        }
672 205
    }
673
674
    /**
675
     * Get sheet by index.
676
     *
677
     * @param int $pIndex Sheet index
678
     *
679
     * @return Worksheet
680
     */
681 4092
    public function getSheet($pIndex)
682
    {
683 4092
        if (!isset($this->workSheetCollection[$pIndex])) {
684 1
            $numSheets = $this->getSheetCount();
685
686 1
            throw new Exception(
687 1
                "Your requested sheet index: {$pIndex} is out of bounds. The actual number of sheets is {$numSheets}."
688
            );
689
        }
690
691 4092
        return $this->workSheetCollection[$pIndex];
692
    }
693
694
    /**
695
     * Get all sheets.
696
     *
697
     * @return Worksheet[]
698
     */
699 78
    public function getAllSheets()
700
    {
701 78
        return $this->workSheetCollection;
702
    }
703
704
    /**
705
     * Get sheet by name.
706
     *
707
     * @param string $pName Sheet name
708
     *
709
     * @return null|Worksheet
710
     */
711 4111
    public function getSheetByName($pName)
712
    {
713 4111
        $worksheetCount = count($this->workSheetCollection);
714 4111
        for ($i = 0; $i < $worksheetCount; ++$i) {
715 3808
            if ($this->workSheetCollection[$i]->getTitle() === trim($pName, "'")) {
716 3621
                return $this->workSheetCollection[$i];
717
            }
718
        }
719
720 4111
        return null;
721
    }
722
723
    /**
724
     * Get index for sheet.
725
     *
726
     * @return int index
727
     */
728 3902
    public function getIndex(Worksheet $pSheet)
729
    {
730 3902
        foreach ($this->workSheetCollection as $key => $value) {
731 3902
            if ($value->getHashCode() === $pSheet->getHashCode()) {
732 3901
                return $key;
733
            }
734
        }
735
736 1
        throw new Exception('Sheet does not exist.');
737
    }
738
739
    /**
740
     * Set index for sheet by sheet name.
741
     *
742
     * @param string $sheetName Sheet name to modify index for
743
     * @param int $newIndex New index for the sheet
744
     *
745
     * @return int New sheet index
746
     */
747 1
    public function setIndexByName($sheetName, $newIndex)
748
    {
749 1
        $oldIndex = $this->getIndex($this->getSheetByName($sheetName));
1 ignored issue
show
Bug introduced by
It seems like $this->getSheetByName($sheetName) can also be of type null; however, parameter $pSheet of PhpOffice\PhpSpreadsheet\Spreadsheet::getIndex() does only seem to accept PhpOffice\PhpSpreadsheet\Worksheet\Worksheet, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

749
        $oldIndex = $this->getIndex(/** @scrutinizer ignore-type */ $this->getSheetByName($sheetName));
Loading history...
750 1
        $pSheet = array_splice(
751 1
            $this->workSheetCollection,
752
            $oldIndex,
753 1
            1
754
        );
755 1
        array_splice(
756 1
            $this->workSheetCollection,
757
            $newIndex,
758 1
            0,
759
            $pSheet
760
        );
761
762 1
        return $newIndex;
763
    }
764
765
    /**
766
     * Get sheet count.
767
     *
768
     * @return int
769
     */
770 588
    public function getSheetCount()
771
    {
772 588
        return count($this->workSheetCollection);
773
    }
774
775
    /**
776
     * Get active sheet index.
777
     *
778
     * @return int Active sheet index
779
     */
780 3636
    public function getActiveSheetIndex()
781
    {
782 3636
        return $this->activeSheetIndex;
783
    }
784
785
    /**
786
     * Set active sheet index.
787
     *
788
     * @param int $pIndex Active sheet index
789
     *
790
     * @return Worksheet
791
     */
792 3982
    public function setActiveSheetIndex($pIndex)
793
    {
794 3982
        $numSheets = count($this->workSheetCollection);
795
796 3982
        if ($pIndex > $numSheets - 1) {
797 1
            throw new Exception(
798 1
                "You tried to set a sheet active by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
799
            );
800
        }
801 3981
        $this->activeSheetIndex = $pIndex;
802
803 3981
        return $this->getActiveSheet();
804
    }
805
806
    /**
807
     * Set active sheet index by name.
808
     *
809
     * @param string $pValue Sheet title
810
     *
811
     * @return Worksheet
812
     */
813 57
    public function setActiveSheetIndexByName($pValue)
814
    {
815 57
        if (($worksheet = $this->getSheetByName($pValue)) instanceof Worksheet) {
816 55
            $this->setActiveSheetIndex($this->getIndex($worksheet));
817
818 55
            return $worksheet;
819
        }
820
821 2
        throw new Exception('Workbook does not contain sheet:' . $pValue);
822
    }
823
824
    /**
825
     * Get sheet names.
826
     *
827
     * @return string[]
828
     */
829 9
    public function getSheetNames()
830
    {
831 9
        $returnValue = [];
832 9
        $worksheetCount = $this->getSheetCount();
833 9
        for ($i = 0; $i < $worksheetCount; ++$i) {
834 9
            $returnValue[] = $this->getSheet($i)->getTitle();
835
        }
836
837 9
        return $returnValue;
838
    }
839
840
    /**
841
     * Add external sheet.
842
     *
843
     * @param Worksheet $pSheet External sheet to add
844
     * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
845
     *
846
     * @return Worksheet
847
     */
848 3
    public function addExternalSheet(Worksheet $pSheet, $iSheetIndex = null)
849
    {
850 3
        if ($this->sheetNameExists($pSheet->getTitle())) {
851 1
            throw new Exception("Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename the external sheet first.");
852
        }
853
854
        // count how many cellXfs there are in this workbook currently, we will need this below
855 2
        $countCellXfs = count($this->cellXfCollection);
856
857
        // copy all the shared cellXfs from the external workbook and append them to the current
858 2
        foreach ($pSheet->getParent()->getCellXfCollection() as $cellXf) {
859 2
            $this->addCellXf(clone $cellXf);
860
        }
861
862
        // move sheet to this workbook
863 2
        $pSheet->rebindParent($this);
864
865
        // update the cellXfs
866 2
        foreach ($pSheet->getCoordinates(false) as $coordinate) {
867 2
            $cell = $pSheet->getCell($coordinate);
868 2
            $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
869
        }
870
871 2
        return $this->addSheet($pSheet, $iSheetIndex);
872
    }
873
874
    /**
875
     * Get an array of all Named Ranges.
876
     *
877
     * @return DefinedName[]
878
     */
879 8
    public function getNamedRanges(): array
880
    {
881 8
        return array_filter(
882 8
            $this->definedNames,
883
            function (DefinedName $definedName) {
884 8
                return $definedName->isFormula() === self::DEFINED_NAME_IS_RANGE;
885 8
            }
886
        );
887
    }
888
889
    /**
890
     * Get an array of all Named Formulae.
891
     *
892
     * @return DefinedName[]
893
     */
894 8
    public function getNamedFormulae(): array
895
    {
896 8
        return array_filter(
897 8
            $this->definedNames,
898
            function (DefinedName $definedName) {
899 8
                return $definedName->isFormula() === self::DEFINED_NAME_IS_FORMULA;
900 8
            }
901
        );
902
    }
903
904
    /**
905
     * Get an array of all Defined Names (both named ranges and named formulae).
906
     *
907
     * @return DefinedName[]
908
     */
909 195
    public function getDefinedNames(): array
910
    {
911 195
        return $this->definedNames;
912
    }
913
914
    /**
915
     * Add a named range.
916
     * If a named range with this name already exists, then this will replace the existing value.
917
     */
918 147
    public function addNamedRange(NamedRange $namedRange): void
919
    {
920 147
        $this->addDefinedName($namedRange);
921 147
    }
922
923
    /**
924
     * Add a named formula.
925
     * If a named formula with this name already exists, then this will replace the existing value.
926
     */
927 9
    public function addNamedFormula(NamedFormula $namedFormula): void
928
    {
929 9
        $this->addDefinedName($namedFormula);
930 9
    }
931
932
    /**
933
     * Add a defined name (either a named range or a named formula).
934
     * If a defined named with this name already exists, then this will replace the existing value.
935
     */
936 229
    public function addDefinedName(DefinedName $definedName): void
937
    {
938 229
        $upperCaseName = StringHelper::strToUpper($definedName->getName());
939 229
        if ($definedName->getScope() == null) {
940
            // global scope
941 220
            $this->definedNames[$upperCaseName] = $definedName;
942
        } else {
943
            // local scope
944 108
            $this->definedNames[$definedName->getScope()->getTitle() . '!' . $upperCaseName] = $definedName;
945
        }
946 229
    }
947
948
    /**
949
     * Get named range.
950
     *
951
     * @param null|Worksheet $pSheet Scope. Use null for global scope
952
     */
953 9
    public function getNamedRange(string $namedRange, ?Worksheet $pSheet = null): ?NamedRange
954
    {
955 9
        $returnValue = null;
956
957 9
        if ($namedRange !== '') {
958 9
            $namedRange = StringHelper::strToUpper($namedRange);
959
            // first look for global named range
960 9
            $returnValue = $this->getGlobalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE);
961
            // then look for local named range (has priority over global named range if both names exist)
962 9
            $returnValue = $this->getLocalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE, $pSheet) ?: $returnValue;
963
        }
964
965 9
        return $returnValue instanceof NamedRange ? $returnValue : null;
966
    }
967
968
    /**
969
     * Get named formula.
970
     *
971
     * @param null|Worksheet $pSheet Scope. Use null for global scope
972
     */
973 8
    public function getNamedFormula(string $namedFormula, ?Worksheet $pSheet = null): ?NamedFormula
974
    {
975 8
        $returnValue = null;
976
977 8
        if ($namedFormula !== '') {
978 8
            $namedFormula = StringHelper::strToUpper($namedFormula);
979
            // first look for global named formula
980 8
            $returnValue = $this->getGlobalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA);
981
            // then look for local named formula (has priority over global named formula if both names exist)
982 8
            $returnValue = $this->getLocalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA, $pSheet) ?: $returnValue;
983
        }
984
985 8
        return $returnValue instanceof NamedFormula ? $returnValue : null;
986
    }
987
988 17
    private function getGlobalDefinedNameByType(string $name, bool $type): ?DefinedName
989
    {
990 17
        if (isset($this->definedNames[$name]) && $this->definedNames[$name]->isFormula() === $type) {
991 17
            return $this->definedNames[$name];
992
        }
993
994 2
        return null;
995
    }
996
997 17
    private function getLocalDefinedNameByType(string $name, bool $type, ?Worksheet $pSheet = null): ?DefinedName
998
    {
999
        if (
1000 17
            ($pSheet !== null) && isset($this->definedNames[$pSheet->getTitle() . '!' . $name])
1001 17
            && $this->definedNames[$pSheet->getTitle() . '!' . $name]->isFormula() === $type
1002
        ) {
1003 8
            return $this->definedNames[$pSheet->getTitle() . '!' . $name];
1004
        }
1005
1006 15
        return null;
1007
    }
1008
1009
    /**
1010
     * Get named range.
1011
     *
1012
     * @param null|Worksheet $pSheet Scope. Use null for global scope
1013
     */
1014 178
    public function getDefinedName(string $definedName, ?Worksheet $pSheet = null): ?DefinedName
1015
    {
1016 178
        $returnValue = null;
1017
1018 178
        if ($definedName !== '') {
1019 178
            $definedName = StringHelper::strToUpper($definedName);
1020
            // first look for global defined name
1021 178
            if (isset($this->definedNames[$definedName])) {
1022 91
                $returnValue = $this->definedNames[$definedName];
1023
            }
1024
1025
            // then look for local defined name (has priority over global defined name if both names exist)
1026 178
            if (($pSheet !== null) && isset($this->definedNames[$pSheet->getTitle() . '!' . $definedName])) {
1027 17
                $returnValue = $this->definedNames[$pSheet->getTitle() . '!' . $definedName];
1028
            }
1029
        }
1030
1031 178
        return $returnValue;
1032
    }
1033
1034
    /**
1035
     * Remove named range.
1036
     *
1037
     * @param null|Worksheet $pSheet scope: use null for global scope
1038
     *
1039
     * @return $this
1040
     */
1041 4
    public function removeNamedRange(string $namedRange, ?Worksheet $pSheet = null): self
1042
    {
1043 4
        if ($this->getNamedRange($namedRange, $pSheet) === null) {
1044
            return $this;
1045
        }
1046
1047 4
        return $this->removeDefinedName($namedRange, $pSheet);
1048
    }
1049
1050
    /**
1051
     * Remove named formula.
1052
     *
1053
     * @param null|Worksheet $pSheet scope: use null for global scope
1054
     *
1055
     * @return $this
1056
     */
1057 3
    public function removeNamedFormula(string $namedFormula, ?Worksheet $pSheet = null): self
1058
    {
1059 3
        if ($this->getNamedFormula($namedFormula, $pSheet) === null) {
1060
            return $this;
1061
        }
1062
1063 3
        return $this->removeDefinedName($namedFormula, $pSheet);
1064
    }
1065
1066
    /**
1067
     * Remove defined name.
1068
     *
1069
     * @param null|Worksheet $pSheet scope: use null for global scope
1070
     *
1071
     * @return $this
1072
     */
1073 10
    public function removeDefinedName(string $definedName, ?Worksheet $pSheet = null): self
1074
    {
1075 10
        $definedName = StringHelper::strToUpper($definedName);
1076
1077 10
        if ($pSheet === null) {
1078
            if (isset($this->definedNames[$definedName])) {
1079
                unset($this->definedNames[$definedName]);
1080
            }
1081
        } else {
1082 10
            if (isset($this->definedNames[$pSheet->getTitle() . '!' . $definedName])) {
1083 3
                unset($this->definedNames[$pSheet->getTitle() . '!' . $definedName]);
1084 7
            } elseif (isset($this->definedNames[$definedName])) {
1085 7
                unset($this->definedNames[$definedName]);
1086
            }
1087
        }
1088
1089 10
        return $this;
1090
    }
1091
1092
    /**
1093
     * Get worksheet iterator.
1094
     *
1095
     * @return Iterator
1096
     */
1097 496
    public function getWorksheetIterator()
1098
    {
1099 496
        return new Iterator($this);
1100
    }
1101
1102
    /**
1103
     * Copy workbook (!= clone!).
1104
     *
1105
     * @return Spreadsheet
1106
     */
1107
    public function copy()
1108
    {
1109
        $copied = clone $this;
1110
1111
        $worksheetCount = count($this->workSheetCollection);
1112
        for ($i = 0; $i < $worksheetCount; ++$i) {
1113
            $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy();
1114
            $this->workSheetCollection[$i]->rebindParent($this);
1115
        }
1116
1117
        return $copied;
1118
    }
1119
1120
    /**
1121
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
1122
     */
1123
    public function __clone()
1124
    {
1125
        // @phpstan-ignore-next-line
1126
        foreach ($this as $key => $val) {
1127
            if (is_object($val) || (is_array($val))) {
1128
                $this->{$key} = unserialize(serialize($val));
1129
            }
1130
        }
1131
    }
1132
1133
    /**
1134
     * Get the workbook collection of cellXfs.
1135
     *
1136
     * @return Style[]
1137
     */
1138 347
    public function getCellXfCollection()
1139
    {
1140 347
        return $this->cellXfCollection;
1141
    }
1142
1143
    /**
1144
     * Get cellXf by index.
1145
     *
1146
     * @param int $pIndex
1147
     *
1148
     * @return Style
1149
     */
1150 3826
    public function getCellXfByIndex($pIndex)
1151
    {
1152 3826
        return $this->cellXfCollection[$pIndex];
1153
    }
1154
1155
    /**
1156
     * Get cellXf by hash code.
1157
     *
1158
     * @param string $pValue
1159
     *
1160
     * @return false|Style
1161
     */
1162 284
    public function getCellXfByHashCode($pValue)
1163
    {
1164 284
        foreach ($this->cellXfCollection as $cellXf) {
1165 284
            if ($cellXf->getHashCode() === $pValue) {
1166 126
                return $cellXf;
1167
            }
1168
        }
1169
1170 265
        return false;
1171
    }
1172
1173
    /**
1174
     * Check if style exists in style collection.
1175
     *
1176
     * @param Style $pCellStyle
1177
     *
1178
     * @return bool
1179
     */
1180
    public function cellXfExists($pCellStyle)
1181
    {
1182
        return in_array($pCellStyle, $this->cellXfCollection, true);
1183
    }
1184
1185
    /**
1186
     * Get default style.
1187
     *
1188
     * @return Style
1189
     */
1190 332
    public function getDefaultStyle()
1191
    {
1192 332
        if (isset($this->cellXfCollection[0])) {
1193 332
            return $this->cellXfCollection[0];
1194
        }
1195
1196
        throw new Exception('No default style found for this workbook');
1197
    }
1198
1199
    /**
1200
     * Add a cellXf to the workbook.
1201
     */
1202 4111
    public function addCellXf(Style $style): void
1203
    {
1204 4111
        $this->cellXfCollection[] = $style;
1205 4111
        $style->setIndex(count($this->cellXfCollection) - 1);
1206 4111
    }
1207
1208
    /**
1209
     * Remove cellXf by index. It is ensured that all cells get their xf index updated.
1210
     *
1211
     * @param int $pIndex Index to cellXf
1212
     */
1213 172
    public function removeCellXfByIndex($pIndex): void
1214
    {
1215 172
        if ($pIndex > count($this->cellXfCollection) - 1) {
1216
            throw new Exception('CellXf index is out of bounds.');
1217
        }
1218
1219
        // first remove the cellXf
1220 172
        array_splice($this->cellXfCollection, $pIndex, 1);
1221
1222
        // then update cellXf indexes for cells
1223 172
        foreach ($this->workSheetCollection as $worksheet) {
1224
            foreach ($worksheet->getCoordinates(false) as $coordinate) {
1225
                $cell = $worksheet->getCell($coordinate);
1226
                $xfIndex = $cell->getXfIndex();
1227
                if ($xfIndex > $pIndex) {
1228
                    // decrease xf index by 1
1229
                    $cell->setXfIndex($xfIndex - 1);
1230
                } elseif ($xfIndex == $pIndex) {
1231
                    // set to default xf index 0
1232
                    $cell->setXfIndex(0);
1233
                }
1234
            }
1235
        }
1236 172
    }
1237
1238
    /**
1239
     * Get the cellXf supervisor.
1240
     *
1241
     * @return Style
1242
     */
1243 3774
    public function getCellXfSupervisor()
1244
    {
1245 3774
        return $this->cellXfSupervisor;
1246
    }
1247
1248
    /**
1249
     * Get the workbook collection of cellStyleXfs.
1250
     *
1251
     * @return Style[]
1252
     */
1253 58
    public function getCellStyleXfCollection()
1254
    {
1255 58
        return $this->cellStyleXfCollection;
1256
    }
1257
1258
    /**
1259
     * Get cellStyleXf by index.
1260
     *
1261
     * @param int $pIndex Index to cellXf
1262
     *
1263
     * @return Style
1264
     */
1265
    public function getCellStyleXfByIndex($pIndex)
1266
    {
1267
        return $this->cellStyleXfCollection[$pIndex];
1268
    }
1269
1270
    /**
1271
     * Get cellStyleXf by hash code.
1272
     *
1273
     * @param string $pValue
1274
     *
1275
     * @return false|Style
1276
     */
1277
    public function getCellStyleXfByHashCode($pValue)
1278
    {
1279
        foreach ($this->cellStyleXfCollection as $cellStyleXf) {
1280
            if ($cellStyleXf->getHashCode() === $pValue) {
1281
                return $cellStyleXf;
1282
            }
1283
        }
1284
1285
        return false;
1286
    }
1287
1288
    /**
1289
     * Add a cellStyleXf to the workbook.
1290
     */
1291 4111
    public function addCellStyleXf(Style $pStyle): void
1292
    {
1293 4111
        $this->cellStyleXfCollection[] = $pStyle;
1294 4111
        $pStyle->setIndex(count($this->cellStyleXfCollection) - 1);
1295 4111
    }
1296
1297
    /**
1298
     * Remove cellStyleXf by index.
1299
     *
1300
     * @param int $pIndex Index to cellXf
1301
     */
1302 172
    public function removeCellStyleXfByIndex($pIndex): void
1303
    {
1304 172
        if ($pIndex > count($this->cellStyleXfCollection) - 1) {
1305
            throw new Exception('CellStyleXf index is out of bounds.');
1306
        }
1307 172
        array_splice($this->cellStyleXfCollection, $pIndex, 1);
1308 172
    }
1309
1310
    /**
1311
     * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
1312
     * and columns in the workbook.
1313
     */
1314 327
    public function garbageCollect(): void
1315
    {
1316
        // how many references are there to each cellXf ?
1317 327
        $countReferencesCellXf = [];
1318 327
        foreach ($this->cellXfCollection as $index => $cellXf) {
1319 327
            $countReferencesCellXf[$index] = 0;
1320
        }
1321
1322 327
        foreach ($this->getWorksheetIterator() as $sheet) {
1323
            // from cells
1324 327
            foreach ($sheet->getCoordinates(false) as $coordinate) {
1325 317
                $cell = $sheet->getCell($coordinate);
1326 317
                ++$countReferencesCellXf[$cell->getXfIndex()];
1327
            }
1328
1329
            // from row dimensions
1330 327
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1331 54
                if ($rowDimension->getXfIndex() !== null) {
1332
                    ++$countReferencesCellXf[$rowDimension->getXfIndex()];
1333
                }
1334
            }
1335
1336
            // from column dimensions
1337 327
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1338 44
                ++$countReferencesCellXf[$columnDimension->getXfIndex()];
1339
            }
1340
        }
1341
1342
        // remove cellXfs without references and create mapping so we can update xfIndex
1343
        // for all cells and columns
1344 327
        $countNeededCellXfs = 0;
1345 327
        $map = [];
1346 327
        foreach ($this->cellXfCollection as $index => $cellXf) {
1347 327
            if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
1348 327
                ++$countNeededCellXfs;
1349
            } else {
1350 20
                unset($this->cellXfCollection[$index]);
1351
            }
1352 327
            $map[$index] = $countNeededCellXfs - 1;
1353
        }
1354 327
        $this->cellXfCollection = array_values($this->cellXfCollection);
1355
1356
        // update the index for all cellXfs
1357 327
        foreach ($this->cellXfCollection as $i => $cellXf) {
1358 327
            $cellXf->setIndex($i);
1359
        }
1360
1361
        // make sure there is always at least one cellXf (there should be)
1362 327
        if (empty($this->cellXfCollection)) {
1363
            $this->cellXfCollection[] = new Style();
1364
        }
1365
1366
        // update the xfIndex for all cells, row dimensions, column dimensions
1367 327
        foreach ($this->getWorksheetIterator() as $sheet) {
1368
            // for all cells
1369 327
            foreach ($sheet->getCoordinates(false) as $coordinate) {
1370 317
                $cell = $sheet->getCell($coordinate);
1371 317
                $cell->setXfIndex($map[$cell->getXfIndex()]);
1372
            }
1373
1374
            // for all row dimensions
1375 327
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1376 54
                if ($rowDimension->getXfIndex() !== null) {
1377
                    $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
1378
                }
1379
            }
1380
1381
            // for all column dimensions
1382 327
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1383 44
                $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
1384
            }
1385
1386
            // also do garbage collection for all the sheets
1387 327
            $sheet->garbageCollect();
1388
        }
1389 327
    }
1390
1391
    /**
1392
     * Return the unique ID value assigned to this spreadsheet workbook.
1393
     *
1394
     * @return string
1395
     */
1396
    public function getID()
1397
    {
1398
        return $this->uniqueID;
1399
    }
1400
1401
    /**
1402
     * Get the visibility of the horizonal scroll bar in the application.
1403
     *
1404
     * @return bool True if horizonal scroll bar is visible
1405
     */
1406 133
    public function getShowHorizontalScroll()
1407
    {
1408 133
        return $this->showHorizontalScroll;
1409
    }
1410
1411
    /**
1412
     * Set the visibility of the horizonal scroll bar in the application.
1413
     *
1414
     * @param bool $showHorizontalScroll True if horizonal scroll bar is visible
1415
     */
1416 66
    public function setShowHorizontalScroll($showHorizontalScroll): void
1417
    {
1418 66
        $this->showHorizontalScroll = (bool) $showHorizontalScroll;
1419 66
    }
1420
1421
    /**
1422
     * Get the visibility of the vertical scroll bar in the application.
1423
     *
1424
     * @return bool True if vertical scroll bar is visible
1425
     */
1426 133
    public function getShowVerticalScroll()
1427
    {
1428 133
        return $this->showVerticalScroll;
1429
    }
1430
1431
    /**
1432
     * Set the visibility of the vertical scroll bar in the application.
1433
     *
1434
     * @param bool $showVerticalScroll True if vertical scroll bar is visible
1435
     */
1436 66
    public function setShowVerticalScroll($showVerticalScroll): void
1437
    {
1438 66
        $this->showVerticalScroll = (bool) $showVerticalScroll;
1439 66
    }
1440
1441
    /**
1442
     * Get the visibility of the sheet tabs in the application.
1443
     *
1444
     * @return bool True if the sheet tabs are visible
1445
     */
1446 133
    public function getShowSheetTabs()
1447
    {
1448 133
        return $this->showSheetTabs;
1449
    }
1450
1451
    /**
1452
     * Set the visibility of the sheet tabs  in the application.
1453
     *
1454
     * @param bool $showSheetTabs True if sheet tabs are visible
1455
     */
1456 66
    public function setShowSheetTabs($showSheetTabs): void
1457
    {
1458 66
        $this->showSheetTabs = (bool) $showSheetTabs;
1459 66
    }
1460
1461
    /**
1462
     * Return whether the workbook window is minimized.
1463
     *
1464
     * @return bool true if workbook window is minimized
1465
     */
1466 133
    public function getMinimized()
1467
    {
1468 133
        return $this->minimized;
1469
    }
1470
1471
    /**
1472
     * Set whether the workbook window is minimized.
1473
     *
1474
     * @param bool $minimized true if workbook window is minimized
1475
     */
1476 66
    public function setMinimized($minimized): void
1477
    {
1478 66
        $this->minimized = (bool) $minimized;
1479 66
    }
1480
1481
    /**
1482
     * Return whether to group dates when presenting the user with
1483
     * filtering optiomd in the user interface.
1484
     *
1485
     * @return bool true if workbook window is minimized
1486
     */
1487 133
    public function getAutoFilterDateGrouping()
1488
    {
1489 133
        return $this->autoFilterDateGrouping;
1490
    }
1491
1492
    /**
1493
     * Set whether to group dates when presenting the user with
1494
     * filtering optiomd in the user interface.
1495
     *
1496
     * @param bool $autoFilterDateGrouping true if workbook window is minimized
1497
     */
1498 66
    public function setAutoFilterDateGrouping($autoFilterDateGrouping): void
1499
    {
1500 66
        $this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping;
1501 66
    }
1502
1503
    /**
1504
     * Return the first sheet in the book view.
1505
     *
1506
     * @return int First sheet in book view
1507
     */
1508 133
    public function getFirstSheetIndex()
1509
    {
1510 133
        return $this->firstSheetIndex;
1511
    }
1512
1513
    /**
1514
     * Set the first sheet in the book view.
1515
     *
1516
     * @param int $firstSheetIndex First sheet in book view
1517
     */
1518 66
    public function setFirstSheetIndex($firstSheetIndex): void
1519
    {
1520 66
        if ($firstSheetIndex >= 0) {
1521 66
            $this->firstSheetIndex = (int) $firstSheetIndex;
1522
        } else {
1523
            throw new Exception('First sheet index must be a positive integer.');
1524
        }
1525 66
    }
1526
1527
    /**
1528
     * Return the visibility status of the workbook.
1529
     *
1530
     * This may be one of the following three values:
1531
     * - visibile
1532
     *
1533
     * @return string Visible status
1534
     */
1535 133
    public function getVisibility()
1536
    {
1537 133
        return $this->visibility;
1538
    }
1539
1540
    /**
1541
     * Set the visibility status of the workbook.
1542
     *
1543
     * Valid values are:
1544
     *  - 'visible' (self::VISIBILITY_VISIBLE):
1545
     *       Workbook window is visible
1546
     *  - 'hidden' (self::VISIBILITY_HIDDEN):
1547
     *       Workbook window is hidden, but can be shown by the user
1548
     *       via the user interface
1549
     *  - 'veryHidden' (self::VISIBILITY_VERY_HIDDEN):
1550
     *       Workbook window is hidden and cannot be shown in the
1551
     *       user interface.
1552
     *
1553
     * @param string $visibility visibility status of the workbook
1554
     */
1555 66
    public function setVisibility($visibility): void
1556
    {
1557 66
        if ($visibility === null) {
0 ignored issues
show
introduced by
The condition $visibility === null is always false.
Loading history...
1558
            $visibility = self::VISIBILITY_VISIBLE;
1559
        }
1560
1561 66
        if (in_array($visibility, self::$workbookViewVisibilityValues)) {
1562 66
            $this->visibility = $visibility;
1563
        } else {
1564
            throw new Exception('Invalid visibility value.');
1565
        }
1566 66
    }
1567
1568
    /**
1569
     * Get the ratio between the workbook tabs bar and the horizontal scroll bar.
1570
     * TabRatio is assumed to be out of 1000 of the horizontal window width.
1571
     *
1572
     * @return int Ratio between the workbook tabs bar and the horizontal scroll bar
1573
     */
1574 133
    public function getTabRatio()
1575
    {
1576 133
        return $this->tabRatio;
1577
    }
1578
1579
    /**
1580
     * Set the ratio between the workbook tabs bar and the horizontal scroll bar
1581
     * TabRatio is assumed to be out of 1000 of the horizontal window width.
1582
     *
1583
     * @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar
1584
     */
1585 66
    public function setTabRatio($tabRatio): void
1586
    {
1587 66
        if ($tabRatio >= 0 || $tabRatio <= 1000) {
1588 66
            $this->tabRatio = (int) $tabRatio;
1589
        } else {
1590
            throw new Exception('Tab ratio must be between 0 and 1000.');
1591
        }
1592 66
    }
1593
}
1594