Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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