Passed
Pull Request — master (#4185)
by Owen
15:32
created

Spreadsheet::addExternalSheet()   B

Complexity

Conditions 7
Paths 25

Size

Total Lines 37
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

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