Passed
Push — master ( 6e76d4...3dcc5c )
by Adrien
72:30 queued 66:24
created

Worksheet::calculateColumnWidths()   C

Complexity

Conditions 16
Paths 6

Size

Total Lines 71
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 16.747

Importance

Changes 0
Metric Value
cc 16
eloc 35
c 0
b 0
f 0
nc 6
nop 0
dl 0
loc 71
ccs 30
cts 35
cp 0.8571
crap 16.747
rs 5.5666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Worksheet;
4
5
use ArrayObject;
6
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
7
use PhpOffice\PhpSpreadsheet\Cell\Cell;
8
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
9
use PhpOffice\PhpSpreadsheet\Cell\DataType;
10
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
11
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
12
use PhpOffice\PhpSpreadsheet\Chart\Chart;
13
use PhpOffice\PhpSpreadsheet\Collection\Cells;
14
use PhpOffice\PhpSpreadsheet\Collection\CellsFactory;
15
use PhpOffice\PhpSpreadsheet\Comment;
16
use PhpOffice\PhpSpreadsheet\Exception;
17
use PhpOffice\PhpSpreadsheet\IComparable;
18
use PhpOffice\PhpSpreadsheet\NamedRange;
19
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
20
use PhpOffice\PhpSpreadsheet\RichText\RichText;
21
use PhpOffice\PhpSpreadsheet\Shared;
22
use PhpOffice\PhpSpreadsheet\Spreadsheet;
23
use PhpOffice\PhpSpreadsheet\Style\Color;
24
use PhpOffice\PhpSpreadsheet\Style\Conditional;
25
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
26
use PhpOffice\PhpSpreadsheet\Style\Style;
27
28
class Worksheet implements IComparable
29
{
30
    // Break types
31
    const BREAK_NONE = 0;
32
    const BREAK_ROW = 1;
33
    const BREAK_COLUMN = 2;
34
35
    // Sheet state
36
    const SHEETSTATE_VISIBLE = 'visible';
37
    const SHEETSTATE_HIDDEN = 'hidden';
38
    const SHEETSTATE_VERYHIDDEN = 'veryHidden';
39
40
    /**
41
     * Maximum 31 characters allowed for sheet title.
42
     *
43
     * @var int
44
     */
45
    const SHEET_TITLE_MAXIMUM_LENGTH = 31;
46
47
    /**
48
     * Invalid characters in sheet title.
49
     *
50
     * @var array
51
     */
52
    private static $invalidCharacters = ['*', ':', '/', '\\', '?', '[', ']'];
53
54
    /**
55
     * Parent spreadsheet.
56
     *
57
     * @var Spreadsheet
58
     */
59
    private $parent;
60
61
    /**
62
     * Collection of cells.
63
     *
64
     * @var Cells
65
     */
66
    private $cellCollection;
67
68
    /**
69
     * Collection of row dimensions.
70
     *
71
     * @var RowDimension[]
72
     */
73
    private $rowDimensions = [];
74
75
    /**
76
     * Default row dimension.
77
     *
78
     * @var RowDimension
79
     */
80
    private $defaultRowDimension;
81
82
    /**
83
     * Collection of column dimensions.
84
     *
85
     * @var ColumnDimension[]
86
     */
87
    private $columnDimensions = [];
88
89
    /**
90
     * Default column dimension.
91
     *
92
     * @var ColumnDimension
93
     */
94
    private $defaultColumnDimension;
95
96
    /**
97
     * Collection of drawings.
98
     *
99
     * @var BaseDrawing[]
100
     */
101
    private $drawingCollection;
102
103
    /**
104
     * Collection of Chart objects.
105
     *
106
     * @var Chart[]
107
     */
108
    private $chartCollection = [];
109
110
    /**
111
     * Worksheet title.
112
     *
113
     * @var string
114
     */
115
    private $title;
116
117
    /**
118
     * Sheet state.
119
     *
120
     * @var string
121
     */
122
    private $sheetState;
123
124
    /**
125
     * Page setup.
126
     *
127
     * @var PageSetup
128
     */
129
    private $pageSetup;
130
131
    /**
132
     * Page margins.
133
     *
134
     * @var PageMargins
135
     */
136
    private $pageMargins;
137
138
    /**
139
     * Page header/footer.
140
     *
141
     * @var HeaderFooter
142
     */
143
    private $headerFooter;
144
145
    /**
146
     * Sheet view.
147
     *
148
     * @var SheetView
149
     */
150
    private $sheetView;
151
152
    /**
153
     * Protection.
154
     *
155
     * @var Protection
156
     */
157
    private $protection;
158
159
    /**
160
     * Collection of styles.
161
     *
162
     * @var Style[]
163
     */
164
    private $styles = [];
165
166
    /**
167
     * Conditional styles. Indexed by cell coordinate, e.g. 'A1'.
168
     *
169
     * @var array
170
     */
171
    private $conditionalStylesCollection = [];
172
173
    /**
174
     * Is the current cell collection sorted already?
175
     *
176
     * @var bool
177
     */
178
    private $cellCollectionIsSorted = false;
179
180
    /**
181
     * Collection of breaks.
182
     *
183
     * @var array
184
     */
185
    private $breaks = [];
186
187
    /**
188
     * Collection of merged cell ranges.
189
     *
190
     * @var array
191
     */
192
    private $mergeCells = [];
193
194
    /**
195
     * Collection of protected cell ranges.
196
     *
197
     * @var array
198
     */
199
    private $protectedCells = [];
200
201
    /**
202
     * Autofilter Range and selection.
203
     *
204
     * @var AutoFilter
205
     */
206
    private $autoFilter;
207
208
    /**
209
     * Freeze pane.
210
     *
211
     * @var null|string
212
     */
213
    private $freezePane;
214
215
    /**
216
     * Default position of the right bottom pane.
217
     *
218
     * @var null|string
219
     */
220
    private $topLeftCell;
221
222
    /**
223
     * Show gridlines?
224
     *
225
     * @var bool
226
     */
227
    private $showGridlines = true;
228
229
    /**
230
     * Print gridlines?
231
     *
232
     * @var bool
233
     */
234
    private $printGridlines = false;
235
236
    /**
237
     * Show row and column headers?
238
     *
239
     * @var bool
240
     */
241
    private $showRowColHeaders = true;
242
243
    /**
244
     * Show summary below? (Row/Column outline).
245
     *
246
     * @var bool
247
     */
248
    private $showSummaryBelow = true;
249
250
    /**
251
     * Show summary right? (Row/Column outline).
252
     *
253
     * @var bool
254
     */
255
    private $showSummaryRight = true;
256
257
    /**
258
     * Collection of comments.
259
     *
260
     * @var Comment[]
261
     */
262
    private $comments = [];
263
264
    /**
265
     * Active cell. (Only one!).
266
     *
267
     * @var string
268
     */
269
    private $activeCell = 'A1';
270
271
    /**
272
     * Selected cells.
273
     *
274
     * @var string
275
     */
276
    private $selectedCells = 'A1';
277
278
    /**
279
     * Cached highest column.
280
     *
281
     * @var string
282
     */
283
    private $cachedHighestColumn = 'A';
284
285
    /**
286
     * Cached highest row.
287
     *
288
     * @var int
289
     */
290
    private $cachedHighestRow = 1;
291
292
    /**
293
     * Right-to-left?
294
     *
295
     * @var bool
296
     */
297
    private $rightToLeft = false;
298
299
    /**
300
     * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'.
301
     *
302
     * @var array
303
     */
304
    private $hyperlinkCollection = [];
305
306
    /**
307
     * Data validation objects. Indexed by cell coordinate, e.g. 'A1'.
308
     *
309
     * @var array
310
     */
311
    private $dataValidationCollection = [];
312
313
    /**
314
     * Tab color.
315
     *
316
     * @var Color
317
     */
318
    private $tabColor;
319
320
    /**
321
     * Dirty flag.
322
     *
323
     * @var bool
324
     */
325
    private $dirty = true;
326
327
    /**
328
     * Hash.
329
     *
330
     * @var string
331
     */
332
    private $hash;
333
334
    /**
335
     * CodeName.
336
     *
337
     * @var string
338
     */
339
    private $codeName;
340
341
    /**
342
     * Create a new worksheet.
343
     *
344
     * @param Spreadsheet $parent
345
     * @param string $pTitle
346
     */
347 316
    public function __construct(Spreadsheet $parent = null, $pTitle = 'Worksheet')
348
    {
349
        // Set parent and title
350 316
        $this->parent = $parent;
351 316
        $this->setTitle($pTitle, false);
352
        // setTitle can change $pTitle
353 316
        $this->setCodeName($this->getTitle());
354 316
        $this->setSheetState(self::SHEETSTATE_VISIBLE);
355
356 316
        $this->cellCollection = CellsFactory::getInstance($this);
357
        // Set page setup
358 316
        $this->pageSetup = new PageSetup();
359
        // Set page margins
360 316
        $this->pageMargins = new PageMargins();
361
        // Set page header/footer
362 316
        $this->headerFooter = new HeaderFooter();
363
        // Set sheet view
364 316
        $this->sheetView = new SheetView();
365
        // Drawing collection
366 316
        $this->drawingCollection = new \ArrayObject();
0 ignored issues
show
Documentation Bug introduced by
It seems like new ArrayObject() of type ArrayObject is incompatible with the declared type PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing[] of property $drawingCollection.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
367
        // Chart collection
368 316
        $this->chartCollection = new \ArrayObject();
0 ignored issues
show
Documentation Bug introduced by
It seems like new ArrayObject() of type ArrayObject is incompatible with the declared type PhpOffice\PhpSpreadsheet\Chart\Chart[] of property $chartCollection.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
369
        // Protection
370 316
        $this->protection = new Protection();
371
        // Default row dimension
372 316
        $this->defaultRowDimension = new RowDimension(null);
373
        // Default column dimension
374 316
        $this->defaultColumnDimension = new ColumnDimension(null);
375 316
        $this->autoFilter = new AutoFilter(null, $this);
376 316
    }
377
378
    /**
379
     * Disconnect all cells from this Worksheet object,
380
     * typically so that the worksheet object can be unset.
381
     */
382 26
    public function disconnectCells()
383
    {
384 26
        if ($this->cellCollection !== null) {
385 3
            $this->cellCollection->unsetWorksheetCells();
386 3
            $this->cellCollection = null;
387
        }
388
        //    detach ourself from the workbook, so that it can then delete this worksheet successfully
389 26
        $this->parent = null;
390 26
    }
391
392
    /**
393
     * Code to execute when this worksheet is unset().
394
     */
395 26
    public function __destruct()
396
    {
397 26
        Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title);
398
399 26
        $this->disconnectCells();
400 26
    }
401
402
    /**
403
     * Return the cell collection.
404
     *
405
     * @return Cells
406
     */
407 265
    public function getCellCollection()
408
    {
409 265
        return $this->cellCollection;
410
    }
411
412
    /**
413
     * Get array of invalid characters for sheet title.
414
     *
415
     * @return array
416
     */
417
    public static function getInvalidCharacters()
418
    {
419
        return self::$invalidCharacters;
420
    }
421
422
    /**
423
     * Check sheet code name for valid Excel syntax.
424
     *
425
     * @param string $pValue The string to check
426
     *
427
     * @throws Exception
428
     *
429
     * @return string The valid string
430
     */
431 316
    private static function checkSheetCodeName($pValue)
432
    {
433 316
        $CharCount = Shared\StringHelper::countCharacters($pValue);
434 316
        if ($CharCount == 0) {
435
            throw new Exception('Sheet code name cannot be empty.');
436
        }
437
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ] and  first and last characters cannot be a "'"
438 316
        if ((str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) ||
439 316
            (Shared\StringHelper::substring($pValue, -1, 1) == '\'') ||
440 316
            (Shared\StringHelper::substring($pValue, 0, 1) == '\'')) {
441 1
            throw new Exception('Invalid character found in sheet code name');
442
        }
443
444
        // Enforce maximum characters allowed for sheet title
445 316
        if ($CharCount > self::SHEET_TITLE_MAXIMUM_LENGTH) {
446 1
            throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet code name.');
447
        }
448
449 316
        return $pValue;
450
    }
451
452
    /**
453
     * Check sheet title for valid Excel syntax.
454
     *
455
     * @param string $pValue The string to check
456
     *
457
     * @throws Exception
458
     *
459
     * @return string The valid string
460
     */
461 316
    private static function checkSheetTitle($pValue)
462
    {
463
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ]
464 316
        if (str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) {
465 1
            throw new Exception('Invalid character found in sheet title');
466
        }
467
468
        // Enforce maximum characters allowed for sheet title
469 316
        if (Shared\StringHelper::countCharacters($pValue) > self::SHEET_TITLE_MAXIMUM_LENGTH) {
470 1
            throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet title.');
471
        }
472
473 316
        return $pValue;
474
    }
475
476
    /**
477
     * Get a sorted list of all cell coordinates currently held in the collection by row and column.
478
     *
479
     * @param bool $sorted Also sort the cell collection?
480
     *
481
     * @return string[]
482
     */
483 160
    public function getCoordinates($sorted = true)
484
    {
485 160
        if ($this->cellCollection == null) {
486
            return [];
487
        }
488
489 160
        if ($sorted) {
490 134
            return $this->cellCollection->getSortedCoordinates();
491
        }
492
493 147
        return $this->cellCollection->getCoordinates();
494
    }
495
496
    /**
497
     * Get collection of row dimensions.
498
     *
499
     * @return RowDimension[]
500
     */
501 147
    public function getRowDimensions()
502
    {
503 147
        return $this->rowDimensions;
504
    }
505
506
    /**
507
     * Get default row dimension.
508
     *
509
     * @return RowDimension
510
     */
511 145
    public function getDefaultRowDimension()
512
    {
513 145
        return $this->defaultRowDimension;
514
    }
515
516
    /**
517
     * Get collection of column dimensions.
518
     *
519
     * @return ColumnDimension[]
520
     */
521 147
    public function getColumnDimensions()
522
    {
523 147
        return $this->columnDimensions;
524
    }
525
526
    /**
527
     * Get default column dimension.
528
     *
529
     * @return ColumnDimension
530
     */
531 120
    public function getDefaultColumnDimension()
532
    {
533 120
        return $this->defaultColumnDimension;
534
    }
535
536
    /**
537
     * Get collection of drawings.
538
     *
539
     * @return BaseDrawing[]
540
     */
541 145
    public function getDrawingCollection()
542
    {
543 145
        return $this->drawingCollection;
544
    }
545
546
    /**
547
     * Get collection of charts.
548
     *
549
     * @return Chart[]
550
     */
551 15
    public function getChartCollection()
552
    {
553 15
        return $this->chartCollection;
554
    }
555
556
    /**
557
     * Add chart.
558
     *
559
     * @param Chart $pChart
560
     * @param null|int $iChartIndex Index where chart should go (0,1,..., or null for last)
561
     *
562
     * @return Chart
563
     */
564 16
    public function addChart(Chart $pChart, $iChartIndex = null)
565
    {
566 16
        $pChart->setWorksheet($this);
567 16
        if ($iChartIndex === null) {
568 16
            $this->chartCollection[] = $pChart;
569
        } else {
570
            // Insert the chart at the requested index
571
            array_splice($this->chartCollection, $iChartIndex, 0, [$pChart]);
572
        }
573
574 16
        return $pChart;
575
    }
576
577
    /**
578
     * Return the count of charts on this worksheet.
579
     *
580
     * @return int The number of charts
581
     */
582 15
    public function getChartCount()
583
    {
584 15
        return count($this->chartCollection);
585
    }
586
587
    /**
588
     * Get a chart by its index position.
589
     *
590
     * @param string $index Chart index position
591
     *
592
     * @return Chart|false
593
     */
594 14
    public function getChartByIndex($index)
595
    {
596 14
        $chartCount = count($this->chartCollection);
597 14
        if ($chartCount == 0) {
598
            return false;
599
        }
600 14
        if ($index === null) {
0 ignored issues
show
introduced by
The condition $index === null is always false.
Loading history...
601
            $index = --$chartCount;
602
        }
603 14
        if (!isset($this->chartCollection[$index])) {
604
            return false;
605
        }
606
607 14
        return $this->chartCollection[$index];
608
    }
609
610
    /**
611
     * Return an array of the names of charts on this worksheet.
612
     *
613
     * @return string[] The names of charts
614
     */
615 2
    public function getChartNames()
616
    {
617 2
        $chartNames = [];
618 2
        foreach ($this->chartCollection as $chart) {
619 2
            $chartNames[] = $chart->getName();
620
        }
621
622 2
        return $chartNames;
623
    }
624
625
    /**
626
     * Get a chart by name.
627
     *
628
     * @param string $chartName Chart name
629
     *
630
     * @return Chart|false
631
     */
632 2
    public function getChartByName($chartName)
633
    {
634 2
        $chartCount = count($this->chartCollection);
635 2
        if ($chartCount == 0) {
636
            return false;
637
        }
638 2
        foreach ($this->chartCollection as $index => $chart) {
639 2
            if ($chart->getName() == $chartName) {
640 2
                return $this->chartCollection[$index];
641
            }
642
        }
643
644
        return false;
645
    }
646
647
    /**
648
     * Refresh column dimensions.
649
     *
650
     * @return $this
651
     */
652 14
    public function refreshColumnDimensions()
653
    {
654 14
        $currentColumnDimensions = $this->getColumnDimensions();
655 14
        $newColumnDimensions = [];
656
657 14
        foreach ($currentColumnDimensions as $objColumnDimension) {
658 14
            $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension;
659
        }
660
661 14
        $this->columnDimensions = $newColumnDimensions;
662
663 14
        return $this;
664
    }
665
666
    /**
667
     * Refresh row dimensions.
668
     *
669
     * @return $this
670
     */
671 2
    public function refreshRowDimensions()
672
    {
673 2
        $currentRowDimensions = $this->getRowDimensions();
674 2
        $newRowDimensions = [];
675
676 2
        foreach ($currentRowDimensions as $objRowDimension) {
677 2
            $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension;
678
        }
679
680 2
        $this->rowDimensions = $newRowDimensions;
681
682 2
        return $this;
683
    }
684
685
    /**
686
     * Calculate worksheet dimension.
687
     *
688
     * @return string String containing the dimension of this worksheet
689
     */
690 117
    public function calculateWorksheetDimension()
691
    {
692
        // Return
693 117
        return 'A1:' . $this->getHighestColumn() . $this->getHighestRow();
694
    }
695
696
    /**
697
     * Calculate worksheet data dimension.
698
     *
699
     * @return string String containing the dimension of this worksheet that actually contain data
700
     */
701
    public function calculateWorksheetDataDimension()
702
    {
703
        // Return
704
        return 'A1:' . $this->getHighestDataColumn() . $this->getHighestDataRow();
705
    }
706
707
    /**
708
     * Calculate widths for auto-size columns.
709
     *
710
     * @return $this
711
     */
712 68
    public function calculateColumnWidths()
713
    {
714
        // initialize $autoSizes array
715 68
        $autoSizes = [];
716 68
        foreach ($this->getColumnDimensions() as $colDimension) {
717 28
            if ($colDimension->getAutoSize()) {
718 13
                $autoSizes[$colDimension->getColumnIndex()] = -1;
719
            }
720
        }
721
722
        // There is only something to do if there are some auto-size columns
723 68
        if (!empty($autoSizes)) {
724
            // build list of cells references that participate in a merge
725 13
            $isMergeCell = [];
726 13
            foreach ($this->getMergeCells() as $cells) {
727 10
                foreach (Coordinate::extractAllCellReferencesInRange($cells) as $cellReference) {
0 ignored issues
show
Bug introduced by
$cells of type array is incompatible with the type string expected by parameter $pRange of PhpOffice\PhpSpreadsheet...CellReferencesInRange(). ( Ignorable by Annotation )

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

727
                foreach (Coordinate::extractAllCellReferencesInRange(/** @scrutinizer ignore-type */ $cells) as $cellReference) {
Loading history...
728 10
                    $isMergeCell[$cellReference] = true;
729
                }
730
            }
731
732
            // loop through all cells in the worksheet
733 13
            foreach ($this->getCoordinates(false) as $coordinate) {
734 13
                $cell = $this->getCell($coordinate, false);
735 13
                if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
736
                    //Determine if cell is in merge range
737 13
                    $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
738
739
                    //By default merged cells should be ignored
740 13
                    $isMergedButProceed = false;
741
742
                    //The only exception is if it's a merge range value cell of a 'vertical' randge (1 column wide)
743 13
                    if ($isMerged && $cell->isMergeRangeValueCell()) {
744
                        $range = $cell->getMergeRange();
745
                        $rangeBoundaries = Coordinate::rangeDimension($range);
746
                        if ($rangeBoundaries[0] == 1) {
747
                            $isMergedButProceed = true;
748
                        }
749
                    }
750
751
                    // Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range
752 13
                    if (!$isMerged || $isMergedButProceed) {
753
                        // Calculated value
754
                        // To formatted string
755 13
                        $cellValue = NumberFormat::toFormattedString(
756 13
                            $cell->getCalculatedValue(),
757 13
                            $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
758
                        );
759
760 13
                        $autoSizes[$this->cellCollection->getCurrentColumn()] = max(
761 13
                            (float) $autoSizes[$this->cellCollection->getCurrentColumn()],
762 13
                            (float) Shared\Font::calculateColumnWidth(
763 13
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
764
                                $cellValue,
765 13
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
766 13
                                $this->getParent()->getDefaultStyle()->getFont()
767
                            )
768
                        );
769
                    }
770
                }
771
            }
772
773
            // adjust column widths
774 13
            foreach ($autoSizes as $columnIndex => $width) {
775 13
                if ($width == -1) {
776
                    $width = $this->getDefaultColumnDimension()->getWidth();
777
                }
778 13
                $this->getColumnDimension($columnIndex)->setWidth($width);
779
            }
780
        }
781
782 68
        return $this;
783
    }
784
785
    /**
786
     * Get parent.
787
     *
788
     * @return Spreadsheet
789
     */
790 316
    public function getParent()
791
    {
792 316
        return $this->parent;
793
    }
794
795
    /**
796
     * Re-bind parent.
797
     *
798
     * @param Spreadsheet $parent
799
     *
800
     * @return $this
801
     */
802 7
    public function rebindParent(Spreadsheet $parent)
803
    {
804 7
        if ($this->parent !== null) {
805 1
            $namedRanges = $this->parent->getNamedRanges();
806 1
            foreach ($namedRanges as $namedRange) {
807
                $parent->addNamedRange($namedRange);
808
            }
809
810 1
            $this->parent->removeSheetByIndex(
811 1
                $this->parent->getIndex($this)
812
            );
813
        }
814 7
        $this->parent = $parent;
815
816 7
        return $this;
817
    }
818
819
    /**
820
     * Get title.
821
     *
822
     * @return string
823
     */
824 316
    public function getTitle()
825
    {
826 316
        return $this->title;
827
    }
828
829
    /**
830
     * Set title.
831
     *
832
     * @param string $pValue String containing the dimension of this worksheet
833
     * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should
834
     *            be updated to reflect the new sheet name.
835
     *          This should be left as the default true, unless you are
836
     *          certain that no formula cells on any worksheet contain
837
     *          references to this worksheet
838
     * @param bool $validate False to skip validation of new title. WARNING: This should only be set
839
     *                       at parse time (by Readers), where titles can be assumed to be valid.
840
     *
841
     * @return $this
842
     */
843 316
    public function setTitle($pValue, $updateFormulaCellReferences = true, $validate = true)
844
    {
845
        // Is this a 'rename' or not?
846 316
        if ($this->getTitle() == $pValue) {
847 64
            return $this;
848
        }
849
850
        // Old title
851 316
        $oldTitle = $this->getTitle();
852
853 316
        if ($validate) {
854
            // Syntax check
855 316
            self::checkSheetTitle($pValue);
856
857 316
            if ($this->parent) {
858
                // Is there already such sheet name?
859 299
                if ($this->parent->sheetNameExists($pValue)) {
860
                    // Use name, but append with lowest possible integer
861
862 8
                    if (Shared\StringHelper::countCharacters($pValue) > 29) {
863
                        $pValue = Shared\StringHelper::substring($pValue, 0, 29);
864
                    }
865 8
                    $i = 1;
866 8
                    while ($this->parent->sheetNameExists($pValue . ' ' . $i)) {
867 3
                        ++$i;
868 3
                        if ($i == 10) {
869
                            if (Shared\StringHelper::countCharacters($pValue) > 28) {
870
                                $pValue = Shared\StringHelper::substring($pValue, 0, 28);
871
                            }
872 3
                        } elseif ($i == 100) {
873
                            if (Shared\StringHelper::countCharacters($pValue) > 27) {
874
                                $pValue = Shared\StringHelper::substring($pValue, 0, 27);
875
                            }
876
                        }
877
                    }
878
879 8
                    $pValue .= " $i";
880
                }
881
            }
882
        }
883
884
        // Set title
885 316
        $this->title = $pValue;
886 316
        $this->dirty = true;
887
888 316
        if ($this->parent && $this->parent->getCalculationEngine()) {
889
            // New title
890 299
            $newTitle = $this->getTitle();
891 299
            $this->parent->getCalculationEngine()
892 299
                ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
893 299
            if ($updateFormulaCellReferences) {
894 51
                ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
895
            }
896
        }
897
898 316
        return $this;
899
    }
900
901
    /**
902
     * Get sheet state.
903
     *
904
     * @return string Sheet state (visible, hidden, veryHidden)
905
     */
906 118
    public function getSheetState()
907
    {
908 118
        return $this->sheetState;
909
    }
910
911
    /**
912
     * Set sheet state.
913
     *
914
     * @param string $value Sheet state (visible, hidden, veryHidden)
915
     *
916
     * @return $this
917
     */
918 316
    public function setSheetState($value)
919
    {
920 316
        $this->sheetState = $value;
921
922 316
        return $this;
923
    }
924
925
    /**
926
     * Get page setup.
927
     *
928
     * @return PageSetup
929
     */
930 164
    public function getPageSetup()
931
    {
932 164
        return $this->pageSetup;
933
    }
934
935
    /**
936
     * Set page setup.
937
     *
938
     * @param PageSetup $pValue
939
     *
940
     * @return $this
941
     */
942
    public function setPageSetup(PageSetup $pValue)
943
    {
944
        $this->pageSetup = $pValue;
945
946
        return $this;
947
    }
948
949
    /**
950
     * Get page margins.
951
     *
952
     * @return PageMargins
953
     */
954 157
    public function getPageMargins()
955
    {
956 157
        return $this->pageMargins;
957
    }
958
959
    /**
960
     * Set page margins.
961
     *
962
     * @param PageMargins $pValue
963
     *
964
     * @return $this
965
     */
966
    public function setPageMargins(PageMargins $pValue)
967
    {
968
        $this->pageMargins = $pValue;
969
970
        return $this;
971
    }
972
973
    /**
974
     * Get page header/footer.
975
     *
976
     * @return HeaderFooter
977
     */
978 127
    public function getHeaderFooter()
979
    {
980 127
        return $this->headerFooter;
981
    }
982
983
    /**
984
     * Set page header/footer.
985
     *
986
     * @param HeaderFooter $pValue
987
     *
988
     * @return $this
989
     */
990
    public function setHeaderFooter(HeaderFooter $pValue)
991
    {
992
        $this->headerFooter = $pValue;
993
994
        return $this;
995
    }
996
997
    /**
998
     * Get sheet view.
999
     *
1000
     * @return SheetView
1001
     */
1002 134
    public function getSheetView()
1003
    {
1004 134
        return $this->sheetView;
1005
    }
1006
1007
    /**
1008
     * Set sheet view.
1009
     *
1010
     * @param SheetView $pValue
1011
     *
1012
     * @return $this
1013
     */
1014
    public function setSheetView(SheetView $pValue)
1015
    {
1016
        $this->sheetView = $pValue;
1017
1018
        return $this;
1019
    }
1020
1021
    /**
1022
     * Get Protection.
1023
     *
1024
     * @return Protection
1025
     */
1026 129
    public function getProtection()
1027
    {
1028 129
        return $this->protection;
1029
    }
1030
1031
    /**
1032
     * Set Protection.
1033
     *
1034
     * @param Protection $pValue
1035
     *
1036
     * @return $this
1037
     */
1038
    public function setProtection(Protection $pValue)
1039
    {
1040
        $this->protection = $pValue;
1041
        $this->dirty = true;
1042
1043
        return $this;
1044
    }
1045
1046
    /**
1047
     * Get highest worksheet column.
1048
     *
1049
     * @param string $row Return the data highest column for the specified row,
1050
     *                                     or the highest column of any row if no row number is passed
1051
     *
1052
     * @return string Highest column name
1053
     */
1054 167
    public function getHighestColumn($row = null)
1055
    {
1056 167
        if ($row == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $row of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1057 167
            return $this->cachedHighestColumn;
1058
        }
1059
1060
        return $this->getHighestDataColumn($row);
1061
    }
1062
1063
    /**
1064
     * Get highest worksheet column that contains data.
1065
     *
1066
     * @param string $row Return the highest data column for the specified row,
1067
     *                                     or the highest data column of any row if no row number is passed
1068
     *
1069
     * @return string Highest column name that contains data
1070
     */
1071 18
    public function getHighestDataColumn($row = null)
1072
    {
1073 18
        return $this->cellCollection->getHighestColumn($row);
1074
    }
1075
1076
    /**
1077
     * Get highest worksheet row.
1078
     *
1079
     * @param string $column Return the highest data row for the specified column,
1080
     *                                     or the highest row of any column if no column letter is passed
1081
     *
1082
     * @return int Highest row number
1083
     */
1084 167
    public function getHighestRow($column = null)
1085
    {
1086 167
        if ($column == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $column of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1087 167
            return $this->cachedHighestRow;
1088
        }
1089
1090
        return $this->getHighestDataRow($column);
1091
    }
1092
1093
    /**
1094
     * Get highest worksheet row that contains data.
1095
     *
1096
     * @param string $column Return the highest data row for the specified column,
1097
     *                                     or the highest data row of any column if no column letter is passed
1098
     *
1099
     * @return int Highest row number that contains data
1100
     */
1101 23
    public function getHighestDataRow($column = null)
1102
    {
1103 23
        return $this->cellCollection->getHighestRow($column);
1104
    }
1105
1106
    /**
1107
     * Get highest worksheet column and highest row that have cell records.
1108
     *
1109
     * @return array Highest column name and highest row number
1110
     */
1111
    public function getHighestRowAndColumn()
1112
    {
1113
        return $this->cellCollection->getHighestRowAndColumn();
1114
    }
1115
1116
    /**
1117
     * Set a cell value.
1118
     *
1119
     * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
1120
     * @param mixed $pValue Value of the cell
1121
     *
1122
     * @return $this
1123
     */
1124 91
    public function setCellValue($pCoordinate, $pValue)
1125
    {
1126 91
        $this->getCell($pCoordinate)->setValue($pValue);
1127
1128 91
        return $this;
1129
    }
1130
1131
    /**
1132
     * Set a cell value by using numeric cell coordinates.
1133
     *
1134
     * @param int $columnIndex Numeric column coordinate of the cell
1135
     * @param int $row Numeric row coordinate of the cell
1136
     * @param mixed $value Value of the cell
1137
     *
1138
     * @return $this
1139
     */
1140
    public function setCellValueByColumnAndRow($columnIndex, $row, $value)
1141
    {
1142
        $this->getCellByColumnAndRow($columnIndex, $row)->setValue($value);
1143
1144
        return $this;
1145
    }
1146
1147
    /**
1148
     * Set a cell value.
1149
     *
1150
     * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
1151
     * @param mixed $pValue Value of the cell
1152
     * @param string $pDataType Explicit data type, see DataType::TYPE_*
1153
     *
1154
     * @return $this
1155
     */
1156 1
    public function setCellValueExplicit($pCoordinate, $pValue, $pDataType)
1157
    {
1158
        // Set value
1159 1
        $this->getCell($pCoordinate)->setValueExplicit($pValue, $pDataType);
1160
1161 1
        return $this;
1162
    }
1163
1164
    /**
1165
     * Set a cell value by using numeric cell coordinates.
1166
     *
1167
     * @param int $columnIndex Numeric column coordinate of the cell
1168
     * @param int $row Numeric row coordinate of the cell
1169
     * @param mixed $value Value of the cell
1170
     * @param string $dataType Explicit data type, see DataType::TYPE_*
1171
     *
1172
     * @return $this
1173
     */
1174
    public function setCellValueExplicitByColumnAndRow($columnIndex, $row, $value, $dataType)
1175
    {
1176
        $this->getCellByColumnAndRow($columnIndex, $row)->setValueExplicit($value, $dataType);
1177
1178
        return $this;
1179
    }
1180
1181
    /**
1182
     * Get cell at a specific coordinate.
1183
     *
1184
     * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
1185
     * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
1186
     *                                       already exist, or a null should be returned instead
1187
     *
1188
     * @throws Exception
1189
     *
1190
     * @return null|Cell Cell that was found/created or null
1191
     */
1192 263
    public function getCell($pCoordinate, $createIfNotExists = true)
1193
    {
1194
        // Uppercase coordinate
1195 263
        $pCoordinateUpper = strtoupper($pCoordinate);
1196
1197
        // Check cell collection
1198 263
        if ($this->cellCollection->has($pCoordinateUpper)) {
1199 226
            return $this->cellCollection->get($pCoordinateUpper);
1200
        }
1201
1202
        // Worksheet reference?
1203 257
        if (strpos($pCoordinate, '!') !== false) {
1204 1
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1205
1206 1
            return $this->parent->getSheetByName($worksheetReference[0])->getCell(strtoupper($worksheetReference[1]), $createIfNotExists);
1207
        }
1208
1209
        // Named range?
1210 257
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1211 257
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1212
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1213
            if ($namedRange !== null) {
1214
                $pCoordinate = $namedRange->getRange();
1215
1216
                return $namedRange->getWorksheet()->getCell($pCoordinate, $createIfNotExists);
1217
            }
1218
        }
1219
1220 257
        if (Coordinate::coordinateIsRange($pCoordinate)) {
1221
            throw new Exception('Cell coordinate can not be a range of cells.');
1222 257
        } elseif (strpos($pCoordinate, '$') !== false) {
1223
            throw new Exception('Cell coordinate must not be absolute.');
1224
        }
1225
1226
        // Create new cell object, if required
1227 257
        return $createIfNotExists ? $this->createNewCell($pCoordinateUpper) : null;
1228
    }
1229
1230
    /**
1231
     * Get cell at a specific coordinate by using numeric cell coordinates.
1232
     *
1233
     * @param int $columnIndex Numeric column coordinate of the cell
1234
     * @param int $row Numeric row coordinate of the cell
1235
     * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
1236
     *                                       already exist, or a null should be returned instead
1237
     *
1238
     * @return null|Cell Cell that was found/created or null
1239
     */
1240 69
    public function getCellByColumnAndRow($columnIndex, $row, $createIfNotExists = true)
1241
    {
1242 69
        $columnLetter = Coordinate::stringFromColumnIndex($columnIndex);
1243 69
        $coordinate = $columnLetter . $row;
1244
1245 69
        if ($this->cellCollection->has($coordinate)) {
1246 68
            return $this->cellCollection->get($coordinate);
1247
        }
1248
1249
        // Create new cell object, if required
1250 40
        return $createIfNotExists ? $this->createNewCell($coordinate) : null;
1251
    }
1252
1253
    /**
1254
     * Create a new cell at the specified coordinate.
1255
     *
1256
     * @param string $pCoordinate Coordinate of the cell
1257
     *
1258
     * @return Cell Cell that was created
1259
     */
1260 265
    private function createNewCell($pCoordinate)
1261
    {
1262 265
        $cell = new Cell(null, DataType::TYPE_NULL, $this);
1263 265
        $this->cellCollection->add($pCoordinate, $cell);
1264 265
        $this->cellCollectionIsSorted = false;
1265
1266
        // Coordinates
1267 265
        $aCoordinates = Coordinate::coordinateFromString($pCoordinate);
1268 265
        if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($aCoordinates[0])) {
1269 177
            $this->cachedHighestColumn = $aCoordinates[0];
1270
        }
1271 265
        if ($aCoordinates[1] > $this->cachedHighestRow) {
1272 166
            $this->cachedHighestRow = $aCoordinates[1];
0 ignored issues
show
Documentation Bug introduced by
The property $cachedHighestRow was declared of type integer, but $aCoordinates[1] is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1273
        }
1274
1275
        // Cell needs appropriate xfIndex from dimensions records
1276
        //    but don't create dimension records if they don't already exist
1277 265
        $rowDimension = $this->getRowDimension($aCoordinates[1], false);
0 ignored issues
show
Bug introduced by
$aCoordinates[1] of type string is incompatible with the type integer expected by parameter $pRow of PhpOffice\PhpSpreadsheet...heet::getRowDimension(). ( Ignorable by Annotation )

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

1277
        $rowDimension = $this->getRowDimension(/** @scrutinizer ignore-type */ $aCoordinates[1], false);
Loading history...
1278 265
        $columnDimension = $this->getColumnDimension($aCoordinates[0], false);
1279
1280 265
        if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) {
1281
            // then there is a row dimension with explicit style, assign it to the cell
1282 2
            $cell->setXfIndex($rowDimension->getXfIndex());
1283 265
        } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) {
1284
            // then there is a column dimension, assign it to the cell
1285
            $cell->setXfIndex($columnDimension->getXfIndex());
1286
        }
1287
1288 265
        return $cell;
1289
    }
1290
1291
    /**
1292
     * Does the cell at a specific coordinate exist?
1293
     *
1294
     * @param string $pCoordinate Coordinate of the cell eg: 'A1'
1295
     *
1296
     * @throws Exception
1297
     *
1298
     * @return bool
1299
     */
1300 131
    public function cellExists($pCoordinate)
1301
    {
1302
        // Worksheet reference?
1303 131
        if (strpos($pCoordinate, '!') !== false) {
1304
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1305
1306
            return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1]));
1307
        }
1308
1309
        // Named range?
1310 131
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1311 131
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1312
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1313
            if ($namedRange !== null) {
1314
                $pCoordinate = $namedRange->getRange();
1315
                if ($this->getHashCode() != $namedRange->getWorksheet()->getHashCode()) {
1316
                    if (!$namedRange->getLocalOnly()) {
1317
                        return $namedRange->getWorksheet()->cellExists($pCoordinate);
1318
                    }
1319
1320
                    throw new Exception('Named range ' . $namedRange->getName() . ' is not accessible from within sheet ' . $this->getTitle());
1321
                }
1322
            } else {
1323
                return false;
1324
            }
1325
        }
1326
1327
        // Uppercase coordinate
1328 131
        $pCoordinate = strtoupper($pCoordinate);
1329
1330 131
        if (Coordinate::coordinateIsRange($pCoordinate)) {
1331
            throw new Exception('Cell coordinate can not be a range of cells.');
1332 131
        } elseif (strpos($pCoordinate, '$') !== false) {
1333
            throw new Exception('Cell coordinate must not be absolute.');
1334
        }
1335
1336
        // Cell exists?
1337 131
        return $this->cellCollection->has($pCoordinate);
1338
    }
1339
1340
    /**
1341
     * Cell at a specific coordinate by using numeric cell coordinates exists?
1342
     *
1343
     * @param int $columnIndex Numeric column coordinate of the cell
1344
     * @param int $row Numeric row coordinate of the cell
1345
     *
1346
     * @return bool
1347
     */
1348 13
    public function cellExistsByColumnAndRow($columnIndex, $row)
1349
    {
1350 13
        return $this->cellExists(Coordinate::stringFromColumnIndex($columnIndex) . $row);
1351
    }
1352
1353
    /**
1354
     * Get row dimension at a specific row.
1355
     *
1356
     * @param int $pRow Numeric index of the row
1357
     * @param bool $create
1358
     *
1359
     * @return RowDimension
1360
     */
1361 268
    public function getRowDimension($pRow, $create = true)
1362
    {
1363
        // Found
1364 268
        $found = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $found is dead and can be removed.
Loading history...
1365
1366
        // Get row dimension
1367 268
        if (!isset($this->rowDimensions[$pRow])) {
1368 268
            if (!$create) {
1369 249
                return null;
1370
            }
1371 132
            $this->rowDimensions[$pRow] = new RowDimension($pRow);
1372
1373 132
            $this->cachedHighestRow = max($this->cachedHighestRow, $pRow);
1374
        }
1375
1376 132
        return $this->rowDimensions[$pRow];
1377
    }
1378
1379
    /**
1380
     * Get column dimension at a specific column.
1381
     *
1382
     * @param string $pColumn String index of the column eg: 'A'
1383
     * @param bool $create
1384
     *
1385
     * @return ColumnDimension
1386
     */
1387 268
    public function getColumnDimension($pColumn, $create = true)
1388
    {
1389
        // Uppercase coordinate
1390 268
        $pColumn = strtoupper($pColumn);
1391
1392
        // Fetch dimensions
1393 268
        if (!isset($this->columnDimensions[$pColumn])) {
1394 268
            if (!$create) {
1395 255
                return null;
1396
            }
1397 67
            $this->columnDimensions[$pColumn] = new ColumnDimension($pColumn);
1398
1399 67
            if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($pColumn)) {
1400 43
                $this->cachedHighestColumn = $pColumn;
1401
            }
1402
        }
1403
1404 67
        return $this->columnDimensions[$pColumn];
1405
    }
1406
1407
    /**
1408
     * Get column dimension at a specific column by using numeric cell coordinates.
1409
     *
1410
     * @param int $columnIndex Numeric column coordinate of the cell
1411
     *
1412
     * @return ColumnDimension
1413
     */
1414 25
    public function getColumnDimensionByColumn($columnIndex)
1415
    {
1416 25
        return $this->getColumnDimension(Coordinate::stringFromColumnIndex($columnIndex));
1417
    }
1418
1419
    /**
1420
     * Get styles.
1421
     *
1422
     * @return Style[]
1423
     */
1424
    public function getStyles()
1425
    {
1426
        return $this->styles;
1427
    }
1428
1429
    /**
1430
     * Get style for cell.
1431
     *
1432
     * @param string $pCellCoordinate Cell coordinate (or range) to get style for, eg: 'A1'
1433
     *
1434
     * @throws Exception
1435
     *
1436
     * @return Style
1437
     */
1438 133
    public function getStyle($pCellCoordinate)
1439
    {
1440
        // set this sheet as active
1441 133
        $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
1442
1443
        // set cell coordinate as active
1444 133
        $this->setSelectedCells($pCellCoordinate);
1445
1446 133
        return $this->parent->getCellXfSupervisor();
1447
    }
1448
1449
    /**
1450
     * Get conditional styles for a cell.
1451
     *
1452
     * @param string $pCoordinate eg: 'A1'
1453
     *
1454
     * @return Conditional[]
1455
     */
1456 8
    public function getConditionalStyles($pCoordinate)
1457
    {
1458 8
        $pCoordinate = strtoupper($pCoordinate);
1459 8
        if (!isset($this->conditionalStylesCollection[$pCoordinate])) {
1460 2
            $this->conditionalStylesCollection[$pCoordinate] = [];
1461
        }
1462
1463 8
        return $this->conditionalStylesCollection[$pCoordinate];
1464
    }
1465
1466
    /**
1467
     * Do conditional styles exist for this cell?
1468
     *
1469
     * @param string $pCoordinate eg: 'A1'
1470
     *
1471
     * @return bool
1472
     */
1473 12
    public function conditionalStylesExists($pCoordinate)
1474
    {
1475 12
        return isset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
1476
    }
1477
1478
    /**
1479
     * Removes conditional styles for a cell.
1480
     *
1481
     * @param string $pCoordinate eg: 'A1'
1482
     *
1483
     * @return $this
1484
     */
1485 19
    public function removeConditionalStyles($pCoordinate)
1486
    {
1487 19
        unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
1488
1489 19
        return $this;
1490
    }
1491
1492
    /**
1493
     * Get collection of conditional styles.
1494
     *
1495
     * @return array
1496
     */
1497 118
    public function getConditionalStylesCollection()
1498
    {
1499 118
        return $this->conditionalStylesCollection;
1500
    }
1501
1502
    /**
1503
     * Set conditional styles.
1504
     *
1505
     * @param string $pCoordinate eg: 'A1'
1506
     * @param $pValue Conditional[]
1507
     *
1508
     * @return $this
1509
     */
1510 8
    public function setConditionalStyles($pCoordinate, $pValue)
1511
    {
1512 8
        $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue;
1513
1514 8
        return $this;
1515
    }
1516
1517
    /**
1518
     * Get style for cell by using numeric cell coordinates.
1519
     *
1520
     * @param int $columnIndex1 Numeric column coordinate of the cell
1521
     * @param int $row1 Numeric row coordinate of the cell
1522
     * @param null|int $columnIndex2 Numeric column coordinate of the range cell
1523
     * @param null|int $row2 Numeric row coordinate of the range cell
1524
     *
1525
     * @return Style
1526
     */
1527
    public function getStyleByColumnAndRow($columnIndex1, $row1, $columnIndex2 = null, $row2 = null)
1528
    {
1529
        if ($columnIndex2 !== null && $row2 !== null) {
1530
            $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
1531
1532
            return $this->getStyle($cellRange);
1533
        }
1534
1535
        return $this->getStyle(Coordinate::stringFromColumnIndex($columnIndex1) . $row1);
1536
    }
1537
1538
    /**
1539
     * Duplicate cell style to a range of cells.
1540
     *
1541
     * Please note that this will overwrite existing cell styles for cells in range!
1542
     *
1543
     * @param Style $pCellStyle Cell style to duplicate
1544
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1545
     *
1546
     * @throws Exception
1547
     *
1548
     * @return $this
1549
     */
1550 2
    public function duplicateStyle(Style $pCellStyle, $pRange)
1551
    {
1552
        // Add the style to the workbook if necessary
1553 2
        $workbook = $this->parent;
1554 2
        if ($existingStyle = $this->parent->getCellXfByHashCode($pCellStyle->getHashCode())) {
1555
            // there is already such cell Xf in our collection
1556 1
            $xfIndex = $existingStyle->getIndex();
1557
        } else {
1558
            // we don't have such a cell Xf, need to add
1559 2
            $workbook->addCellXf($pCellStyle);
1560 2
            $xfIndex = $pCellStyle->getIndex();
1561
        }
1562
1563
        // Calculate range outer borders
1564 2
        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange . ':' . $pRange);
1565
1566
        // Make sure we can loop upwards on rows and columns
1567 2
        if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
1568
            $tmp = $rangeStart;
1569
            $rangeStart = $rangeEnd;
1570
            $rangeEnd = $tmp;
1571
        }
1572
1573
        // Loop through cells and apply styles
1574 2
        for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
1575 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1576 2
                $this->getCell(Coordinate::stringFromColumnIndex($col) . $row)->setXfIndex($xfIndex);
1577
            }
1578
        }
1579
1580 2
        return $this;
1581
    }
1582
1583
    /**
1584
     * Duplicate conditional style to a range of cells.
1585
     *
1586
     * Please note that this will overwrite existing cell styles for cells in range!
1587
     *
1588
     * @param Conditional[] $pCellStyle Cell style to duplicate
1589
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1590
     *
1591
     * @throws Exception
1592
     *
1593
     * @return $this
1594
     */
1595 2
    public function duplicateConditionalStyle(array $pCellStyle, $pRange = '')
1596
    {
1597 2
        foreach ($pCellStyle as $cellStyle) {
1598 2
            if (!($cellStyle instanceof Conditional)) {
1599
                throw new Exception('Style is not a conditional style');
1600
            }
1601
        }
1602
1603
        // Calculate range outer borders
1604 2
        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange . ':' . $pRange);
1605
1606
        // Make sure we can loop upwards on rows and columns
1607 2
        if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
1608
            $tmp = $rangeStart;
1609
            $rangeStart = $rangeEnd;
1610
            $rangeEnd = $tmp;
1611
        }
1612
1613
        // Loop through cells and apply styles
1614 2
        for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
1615 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1616 2
                $this->setConditionalStyles(Coordinate::stringFromColumnIndex($col) . $row, $pCellStyle);
1617
            }
1618
        }
1619
1620 2
        return $this;
1621
    }
1622
1623
    /**
1624
     * Set break on a cell.
1625
     *
1626
     * @param string $pCoordinate Cell coordinate (e.g. A1)
1627
     * @param int $pBreak Break type (type of Worksheet::BREAK_*)
1628
     *
1629
     * @throws Exception
1630
     *
1631
     * @return $this
1632
     */
1633 2
    public function setBreak($pCoordinate, $pBreak)
1634
    {
1635
        // Uppercase coordinate
1636 2
        $pCoordinate = strtoupper($pCoordinate);
1637
1638 2
        if ($pCoordinate != '') {
1639 2
            if ($pBreak == self::BREAK_NONE) {
1640
                if (isset($this->breaks[$pCoordinate])) {
1641
                    unset($this->breaks[$pCoordinate]);
1642
                }
1643
            } else {
1644 2
                $this->breaks[$pCoordinate] = $pBreak;
1645
            }
1646
        } else {
1647
            throw new Exception('No cell coordinate specified.');
1648
        }
1649
1650 2
        return $this;
1651
    }
1652
1653
    /**
1654
     * Set break on a cell by using numeric cell coordinates.
1655
     *
1656
     * @param int $columnIndex Numeric column coordinate of the cell
1657
     * @param int $row Numeric row coordinate of the cell
1658
     * @param int $break Break type (type of Worksheet::BREAK_*)
1659
     *
1660
     * @return $this
1661
     */
1662
    public function setBreakByColumnAndRow($columnIndex, $row, $break)
1663
    {
1664
        return $this->setBreak(Coordinate::stringFromColumnIndex($columnIndex) . $row, $break);
1665
    }
1666
1667
    /**
1668
     * Get breaks.
1669
     *
1670
     * @return array[]
1671
     */
1672 134
    public function getBreaks()
1673
    {
1674 134
        return $this->breaks;
1675
    }
1676
1677
    /**
1678
     * Set merge on a cell range.
1679
     *
1680
     * @param string $pRange Cell range (e.g. A1:E1)
1681
     *
1682
     * @throws Exception
1683
     *
1684
     * @return $this
1685
     */
1686 44
    public function mergeCells($pRange)
1687
    {
1688
        // Uppercase coordinate
1689 44
        $pRange = strtoupper($pRange);
1690
1691 44
        if (strpos($pRange, ':') !== false) {
1692 44
            $this->mergeCells[$pRange] = $pRange;
1693
1694
            // make sure cells are created
1695
1696
            // get the cells in the range
1697 44
            $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
1698
1699
            // create upper left cell if it does not already exist
1700 44
            $upperLeft = $aReferences[0];
1701 44
            if (!$this->cellExists($upperLeft)) {
1702 14
                $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL);
1703
            }
1704
1705
            // Blank out the rest of the cells in the range (if they exist)
1706 44
            $count = count($aReferences);
1707 44
            for ($i = 1; $i < $count; ++$i) {
1708 44
                if ($this->cellExists($aReferences[$i])) {
1709 18
                    $this->getCell($aReferences[$i])->setValueExplicit(null, DataType::TYPE_NULL);
1710
                }
1711
            }
1712
        } else {
1713
            throw new Exception('Merge must be set on a range of cells.');
1714
        }
1715
1716 44
        return $this;
1717
    }
1718
1719
    /**
1720
     * Set merge on a cell range by using numeric cell coordinates.
1721
     *
1722
     * @param int $columnIndex1 Numeric column coordinate of the first cell
1723
     * @param int $row1 Numeric row coordinate of the first cell
1724
     * @param int $columnIndex2 Numeric column coordinate of the last cell
1725
     * @param int $row2 Numeric row coordinate of the last cell
1726
     *
1727
     * @throws Exception
1728
     *
1729
     * @return $this
1730
     */
1731
    public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
1732
    {
1733
        $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
1734
1735
        return $this->mergeCells($cellRange);
1736
    }
1737
1738
    /**
1739
     * Remove merge on a cell range.
1740
     *
1741
     * @param string $pRange Cell range (e.g. A1:E1)
1742
     *
1743
     * @throws Exception
1744
     *
1745
     * @return $this
1746
     */
1747 11
    public function unmergeCells($pRange)
1748
    {
1749
        // Uppercase coordinate
1750 11
        $pRange = strtoupper($pRange);
1751
1752 11
        if (strpos($pRange, ':') !== false) {
1753 11
            if (isset($this->mergeCells[$pRange])) {
1754 11
                unset($this->mergeCells[$pRange]);
1755
            } else {
1756 11
                throw new Exception('Cell range ' . $pRange . ' not known as merged.');
1757
            }
1758
        } else {
1759
            throw new Exception('Merge can only be removed from a range of cells.');
1760
        }
1761
1762 11
        return $this;
1763
    }
1764
1765
    /**
1766
     * Remove merge on a cell range by using numeric cell coordinates.
1767
     *
1768
     * @param int $columnIndex1 Numeric column coordinate of the first cell
1769
     * @param int $row1 Numeric row coordinate of the first cell
1770
     * @param int $columnIndex2 Numeric column coordinate of the last cell
1771
     * @param int $row2 Numeric row coordinate of the last cell
1772
     *
1773
     * @throws Exception
1774
     *
1775
     * @return $this
1776
     */
1777
    public function unmergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
1778
    {
1779
        $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
1780
1781
        return $this->unmergeCells($cellRange);
1782
    }
1783
1784
    /**
1785
     * Get merge cells array.
1786
     *
1787
     * @return array[]
1788
     */
1789 149
    public function getMergeCells()
1790
    {
1791 149
        return $this->mergeCells;
1792
    }
1793
1794
    /**
1795
     * Set merge cells array for the entire sheet. Use instead mergeCells() to merge
1796
     * a single cell range.
1797
     *
1798
     * @param array $pValue
1799
     *
1800
     * @return $this
1801
     */
1802 26
    public function setMergeCells(array $pValue)
1803
    {
1804 26
        $this->mergeCells = $pValue;
1805
1806 26
        return $this;
1807
    }
1808
1809
    /**
1810
     * Set protection on a cell range.
1811
     *
1812
     * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
1813
     * @param string $pPassword Password to unlock the protection
1814
     * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
1815
     *
1816
     * @return $this
1817
     */
1818 11
    public function protectCells($pRange, $pPassword, $pAlreadyHashed = false)
1819
    {
1820
        // Uppercase coordinate
1821 11
        $pRange = strtoupper($pRange);
1822
1823 11
        if (!$pAlreadyHashed) {
1824 11
            $pPassword = Shared\PasswordHasher::hashPassword($pPassword);
1825
        }
1826 11
        $this->protectedCells[$pRange] = $pPassword;
1827
1828 11
        return $this;
1829
    }
1830
1831
    /**
1832
     * Set protection on a cell range by using numeric cell coordinates.
1833
     *
1834
     * @param int $columnIndex1 Numeric column coordinate of the first cell
1835
     * @param int $row1 Numeric row coordinate of the first cell
1836
     * @param int $columnIndex2 Numeric column coordinate of the last cell
1837
     * @param int $row2 Numeric row coordinate of the last cell
1838
     * @param string $password Password to unlock the protection
1839
     * @param bool $alreadyHashed If the password has already been hashed, set this to true
1840
     *
1841
     * @return $this
1842
     */
1843
    public function protectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2, $password, $alreadyHashed = false)
1844
    {
1845
        $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
1846
1847
        return $this->protectCells($cellRange, $password, $alreadyHashed);
1848
    }
1849
1850
    /**
1851
     * Remove protection on a cell range.
1852
     *
1853
     * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
1854
     *
1855
     * @throws Exception
1856
     *
1857
     * @return $this
1858
     */
1859 11
    public function unprotectCells($pRange)
1860
    {
1861
        // Uppercase coordinate
1862 11
        $pRange = strtoupper($pRange);
1863
1864 11
        if (isset($this->protectedCells[$pRange])) {
1865 11
            unset($this->protectedCells[$pRange]);
1866
        } else {
1867
            throw new Exception('Cell range ' . $pRange . ' not known as protected.');
1868
        }
1869
1870 11
        return $this;
1871
    }
1872
1873
    /**
1874
     * Remove protection on a cell range by using numeric cell coordinates.
1875
     *
1876
     * @param int $columnIndex1 Numeric column coordinate of the first cell
1877
     * @param int $row1 Numeric row coordinate of the first cell
1878
     * @param int $columnIndex2 Numeric column coordinate of the last cell
1879
     * @param int $row2 Numeric row coordinate of the last cell
1880
     *
1881
     * @throws Exception
1882
     *
1883
     * @return $this
1884
     */
1885
    public function unprotectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
1886
    {
1887
        $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
1888
1889
        return $this->unprotectCells($cellRange);
1890
    }
1891
1892
    /**
1893
     * Get protected cells.
1894
     *
1895
     * @return array[]
1896
     */
1897 133
    public function getProtectedCells()
1898
    {
1899 133
        return $this->protectedCells;
1900
    }
1901
1902
    /**
1903
     * Get Autofilter.
1904
     *
1905
     * @return AutoFilter
1906
     */
1907 135
    public function getAutoFilter()
1908
    {
1909 135
        return $this->autoFilter;
1910
    }
1911
1912
    /**
1913
     * Set AutoFilter.
1914
     *
1915
     * @param AutoFilter|string $pValue
1916
     *            A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility
1917
     *
1918
     * @throws Exception
1919
     *
1920
     * @return $this
1921
     */
1922 4
    public function setAutoFilter($pValue)
1923
    {
1924 4
        if (is_string($pValue)) {
1925 4
            $this->autoFilter->setRange($pValue);
1926
        } elseif (is_object($pValue) && ($pValue instanceof AutoFilter)) {
1927
            $this->autoFilter = $pValue;
1928
        }
1929
1930 4
        return $this;
1931
    }
1932
1933
    /**
1934
     * Set Autofilter Range by using numeric cell coordinates.
1935
     *
1936
     * @param int $columnIndex1 Numeric column coordinate of the first cell
1937
     * @param int $row1 Numeric row coordinate of the first cell
1938
     * @param int $columnIndex2 Numeric column coordinate of the second cell
1939
     * @param int $row2 Numeric row coordinate of the second cell
1940
     *
1941
     * @throws Exception
1942
     *
1943
     * @return $this
1944
     */
1945
    public function setAutoFilterByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
1946
    {
1947
        return $this->setAutoFilter(
1948
            Coordinate::stringFromColumnIndex($columnIndex1) . $row1
1949
            . ':' .
1950
            Coordinate::stringFromColumnIndex($columnIndex2) . $row2
1951
        );
1952
    }
1953
1954
    /**
1955
     * Remove autofilter.
1956
     *
1957
     * @return $this
1958
     */
1959
    public function removeAutoFilter()
1960
    {
1961
        $this->autoFilter->setRange(null);
1962
1963
        return $this;
1964
    }
1965
1966
    /**
1967
     * Get Freeze Pane.
1968
     *
1969
     * @return string
1970
     */
1971 133
    public function getFreezePane()
1972
    {
1973 133
        return $this->freezePane;
1974
    }
1975
1976
    /**
1977
     * Freeze Pane.
1978
     *
1979
     * Examples:
1980
     *
1981
     *     - A2 will freeze the rows above cell A2 (i.e row 1)
1982
     *     - B1 will freeze the columns to the left of cell B1 (i.e column A)
1983
     *     - B2 will freeze the rows above and to the left of cell B2 (i.e row 1 and column A)
1984
     *
1985
     * @param null|string $cell Position of the split
1986
     * @param null|string $topLeftCell default position of the right bottom pane
1987
     *
1988
     * @throws Exception
1989
     *
1990
     * @return $this
1991
     */
1992 11
    public function freezePane($cell, $topLeftCell = null)
1993
    {
1994 11
        if (is_string($cell) && Coordinate::coordinateIsRange($cell)) {
1995
            throw new Exception('Freeze pane can not be set on a range of cells.');
1996
        }
1997
1998 11
        if ($cell !== null && $topLeftCell === null) {
1999 7
            $coordinate = Coordinate::coordinateFromString($cell);
2000 7
            $topLeftCell = $coordinate[0] . $coordinate[1];
2001
        }
2002
2003 11
        $this->freezePane = $cell;
2004 11
        $this->topLeftCell = $topLeftCell;
2005
2006 11
        return $this;
2007
    }
2008
2009
    /**
2010
     * Freeze Pane by using numeric cell coordinates.
2011
     *
2012
     * @param int $columnIndex Numeric column coordinate of the cell
2013
     * @param int $row Numeric row coordinate of the cell
2014
     *
2015
     * @return $this
2016
     */
2017
    public function freezePaneByColumnAndRow($columnIndex, $row)
2018
    {
2019
        return $this->freezePane(Coordinate::stringFromColumnIndex($columnIndex) . $row);
2020
    }
2021
2022
    /**
2023
     * Unfreeze Pane.
2024
     *
2025
     * @return $this
2026
     */
2027
    public function unfreezePane()
2028
    {
2029
        return $this->freezePane(null);
2030
    }
2031
2032
    /**
2033
     * Get the default position of the right bottom pane.
2034
     *
2035
     * @return int
2036
     */
2037 10
    public function getTopLeftCell()
2038
    {
2039 10
        return $this->topLeftCell;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->topLeftCell also could return the type string which is incompatible with the documented return type integer.
Loading history...
2040
    }
2041
2042
    /**
2043
     * Insert a new row, updating all possible related data.
2044
     *
2045
     * @param int $pBefore Insert before this one
2046
     * @param int $pNumRows Number of rows to insert
2047
     *
2048
     * @throws Exception
2049
     *
2050
     * @return $this
2051
     */
2052 12
    public function insertNewRowBefore($pBefore, $pNumRows = 1)
2053
    {
2054 12
        if ($pBefore >= 1) {
2055 12
            $objReferenceHelper = ReferenceHelper::getInstance();
2056 12
            $objReferenceHelper->insertNewBefore('A' . $pBefore, 0, $pNumRows, $this);
2057
        } else {
2058
            throw new Exception('Rows can only be inserted before at least row 1.');
2059
        }
2060
2061 12
        return $this;
2062
    }
2063
2064
    /**
2065
     * Insert a new column, updating all possible related data.
2066
     *
2067
     * @param string $pBefore Insert before this one, eg: 'A'
2068
     * @param int $pNumCols Number of columns to insert
2069
     *
2070
     * @throws Exception
2071
     *
2072
     * @return $this
2073
     */
2074 11
    public function insertNewColumnBefore($pBefore, $pNumCols = 1)
2075
    {
2076 11
        if (!is_numeric($pBefore)) {
2077 11
            $objReferenceHelper = ReferenceHelper::getInstance();
2078 11
            $objReferenceHelper->insertNewBefore($pBefore . '1', $pNumCols, 0, $this);
2079
        } else {
2080
            throw new Exception('Column references should not be numeric.');
2081
        }
2082
2083 11
        return $this;
2084
    }
2085
2086
    /**
2087
     * Insert a new column, updating all possible related data.
2088
     *
2089
     * @param int $beforeColumnIndex Insert before this one (numeric column coordinate of the cell)
2090
     * @param int $pNumCols Number of columns to insert
2091
     *
2092
     * @throws Exception
2093
     *
2094
     * @return $this
2095
     */
2096
    public function insertNewColumnBeforeByIndex($beforeColumnIndex, $pNumCols = 1)
2097
    {
2098
        if ($beforeColumnIndex >= 1) {
2099
            return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $pNumCols);
2100
        }
2101
2102
        throw new Exception('Columns can only be inserted before at least column A (1).');
2103
    }
2104
2105
    /**
2106
     * Delete a row, updating all possible related data.
2107
     *
2108
     * @param int $pRow Remove starting with this one
2109
     * @param int $pNumRows Number of rows to remove
2110
     *
2111
     * @throws Exception
2112
     *
2113
     * @return $this
2114
     */
2115 22
    public function removeRow($pRow, $pNumRows = 1)
2116
    {
2117 22
        if ($pRow < 1) {
2118
            throw new Exception('Rows to be deleted should at least start from row 1.');
2119
        }
2120
2121 22
        $highestRow = $this->getHighestDataRow();
2122 22
        $removedRowsCounter = 0;
2123
2124 22
        for ($r = 0; $r < $pNumRows; ++$r) {
2125 22
            if ($pRow + $r <= $highestRow) {
2126 21
                $this->getCellCollection()->removeRow($pRow + $r);
2127 21
                ++$removedRowsCounter;
2128
            }
2129
        }
2130
2131 22
        $objReferenceHelper = ReferenceHelper::getInstance();
2132 22
        $objReferenceHelper->insertNewBefore('A' . ($pRow + $pNumRows), 0, -$pNumRows, $this);
2133 22
        for ($r = 0; $r < $removedRowsCounter; ++$r) {
2134 21
            $this->getCellCollection()->removeRow($highestRow);
2135 21
            --$highestRow;
2136
        }
2137
2138 22
        return $this;
2139
    }
2140
2141
    /**
2142
     * Remove a column, updating all possible related data.
2143
     *
2144
     * @param string $pColumn Remove starting with this one, eg: 'A'
2145
     * @param int $pNumCols Number of columns to remove
2146
     *
2147
     * @throws Exception
2148
     *
2149
     * @return $this
2150
     */
2151 16
    public function removeColumn($pColumn, $pNumCols = 1)
2152
    {
2153 16
        if (is_numeric($pColumn)) {
2154
            throw new Exception('Column references should not be numeric.');
2155
        }
2156
2157 16
        $highestColumn = $this->getHighestDataColumn();
2158 16
        $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
2159 16
        $pColumnIndex = Coordinate::columnIndexFromString($pColumn);
2160
2161 16
        if ($pColumnIndex > $highestColumnIndex) {
2162 1
            return $this;
2163
        }
2164
2165 15
        $pColumn = Coordinate::stringFromColumnIndex($pColumnIndex + $pNumCols);
2166 15
        $objReferenceHelper = ReferenceHelper::getInstance();
2167 15
        $objReferenceHelper->insertNewBefore($pColumn . '1', -$pNumCols, 0, $this);
2168
2169 15
        $maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1;
2170
2171 15
        for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $pNumCols); $c < $n; ++$c) {
2172 15
            $this->getCellCollection()->removeColumn($highestColumn);
2173 15
            $highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1);
2174
        }
2175
2176 15
        $this->garbageCollect();
2177
2178 15
        return $this;
2179
    }
2180
2181
    /**
2182
     * Remove a column, updating all possible related data.
2183
     *
2184
     * @param int $columnIndex Remove starting with this one (numeric column coordinate of the cell)
2185
     * @param int $numColumns Number of columns to remove
2186
     *
2187
     * @throws Exception
2188
     *
2189
     * @return $this
2190
     */
2191
    public function removeColumnByIndex($columnIndex, $numColumns = 1)
2192
    {
2193
        if ($columnIndex >= 1) {
2194
            return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns);
2195
        }
2196
2197
        throw new Exception('Columns to be deleted should at least start from column A (1)');
2198
    }
2199
2200
    /**
2201
     * Show gridlines?
2202
     *
2203
     * @return bool
2204
     */
2205 129
    public function getShowGridlines()
2206
    {
2207 129
        return $this->showGridlines;
2208
    }
2209
2210
    /**
2211
     * Set show gridlines.
2212
     *
2213
     * @param bool $pValue Show gridlines (true/false)
2214
     *
2215
     * @return $this
2216
     */
2217 85
    public function setShowGridlines($pValue)
2218
    {
2219 85
        $this->showGridlines = $pValue;
2220
2221 85
        return $this;
2222
    }
2223
2224
    /**
2225
     * Print gridlines?
2226
     *
2227
     * @return bool
2228
     */
2229 118
    public function getPrintGridlines()
2230
    {
2231 118
        return $this->printGridlines;
2232
    }
2233
2234
    /**
2235
     * Set print gridlines.
2236
     *
2237
     * @param bool $pValue Print gridlines (true/false)
2238
     *
2239
     * @return $this
2240
     */
2241 29
    public function setPrintGridlines($pValue)
2242
    {
2243 29
        $this->printGridlines = $pValue;
2244
2245 29
        return $this;
2246
    }
2247
2248
    /**
2249
     * Show row and column headers?
2250
     *
2251
     * @return bool
2252
     */
2253 118
    public function getShowRowColHeaders()
2254
    {
2255 118
        return $this->showRowColHeaders;
2256
    }
2257
2258
    /**
2259
     * Set show row and column headers.
2260
     *
2261
     * @param bool $pValue Show row and column headers (true/false)
2262
     *
2263
     * @return $this
2264
     */
2265 83
    public function setShowRowColHeaders($pValue)
2266
    {
2267 83
        $this->showRowColHeaders = $pValue;
2268
2269 83
        return $this;
2270
    }
2271
2272
    /**
2273
     * Show summary below? (Row/Column outlining).
2274
     *
2275
     * @return bool
2276
     */
2277 118
    public function getShowSummaryBelow()
2278
    {
2279 118
        return $this->showSummaryBelow;
2280
    }
2281
2282
    /**
2283
     * Set show summary below.
2284
     *
2285
     * @param bool $pValue Show summary below (true/false)
2286
     *
2287
     * @return $this
2288
     */
2289 82
    public function setShowSummaryBelow($pValue)
2290
    {
2291 82
        $this->showSummaryBelow = $pValue;
2292
2293 82
        return $this;
2294
    }
2295
2296
    /**
2297
     * Show summary right? (Row/Column outlining).
2298
     *
2299
     * @return bool
2300
     */
2301 118
    public function getShowSummaryRight()
2302
    {
2303 118
        return $this->showSummaryRight;
2304
    }
2305
2306
    /**
2307
     * Set show summary right.
2308
     *
2309
     * @param bool $pValue Show summary right (true/false)
2310
     *
2311
     * @return $this
2312
     */
2313 82
    public function setShowSummaryRight($pValue)
2314
    {
2315 82
        $this->showSummaryRight = $pValue;
2316
2317 82
        return $this;
2318
    }
2319
2320
    /**
2321
     * Get comments.
2322
     *
2323
     * @return Comment[]
2324
     */
2325 136
    public function getComments()
2326
    {
2327 136
        return $this->comments;
2328
    }
2329
2330
    /**
2331
     * Set comments array for the entire sheet.
2332
     *
2333
     * @param Comment[] $pValue
2334
     *
2335
     * @return $this
2336
     */
2337 26
    public function setComments(array $pValue)
2338
    {
2339 26
        $this->comments = $pValue;
2340
2341 26
        return $this;
2342
    }
2343
2344
    /**
2345
     * Get comment for cell.
2346
     *
2347
     * @param string $pCellCoordinate Cell coordinate to get comment for, eg: 'A1'
2348
     *
2349
     * @throws Exception
2350
     *
2351
     * @return Comment
2352
     */
2353 33
    public function getComment($pCellCoordinate)
2354
    {
2355
        // Uppercase coordinate
2356 33
        $pCellCoordinate = strtoupper($pCellCoordinate);
2357
2358 33
        if (Coordinate::coordinateIsRange($pCellCoordinate)) {
2359
            throw new Exception('Cell coordinate string can not be a range of cells.');
2360 33
        } elseif (strpos($pCellCoordinate, '$') !== false) {
2361
            throw new Exception('Cell coordinate string must not be absolute.');
2362 33
        } elseif ($pCellCoordinate == '') {
2363
            throw new Exception('Cell coordinate can not be zero-length string.');
2364
        }
2365
2366
        // Check if we already have a comment for this cell.
2367 33
        if (isset($this->comments[$pCellCoordinate])) {
2368 19
            return $this->comments[$pCellCoordinate];
2369
        }
2370
2371
        // If not, create a new comment.
2372 33
        $newComment = new Comment();
2373 33
        $this->comments[$pCellCoordinate] = $newComment;
2374
2375 33
        return $newComment;
2376
    }
2377
2378
    /**
2379
     * Get comment for cell by using numeric cell coordinates.
2380
     *
2381
     * @param int $columnIndex Numeric column coordinate of the cell
2382
     * @param int $row Numeric row coordinate of the cell
2383
     *
2384
     * @return Comment
2385
     */
2386 3
    public function getCommentByColumnAndRow($columnIndex, $row)
2387
    {
2388 3
        return $this->getComment(Coordinate::stringFromColumnIndex($columnIndex) . $row);
2389
    }
2390
2391
    /**
2392
     * Get active cell.
2393
     *
2394
     * @return string Example: 'A1'
2395
     */
2396 166
    public function getActiveCell()
2397
    {
2398 166
        return $this->activeCell;
2399
    }
2400
2401
    /**
2402
     * Get selected cells.
2403
     *
2404
     * @return string
2405
     */
2406 162
    public function getSelectedCells()
2407
    {
2408 162
        return $this->selectedCells;
2409
    }
2410
2411
    /**
2412
     * Selected cell.
2413
     *
2414
     * @param string $pCoordinate Cell (i.e. A1)
2415
     *
2416
     * @return $this
2417
     */
2418 2
    public function setSelectedCell($pCoordinate)
2419
    {
2420 2
        return $this->setSelectedCells($pCoordinate);
2421
    }
2422
2423
    /**
2424
     * Select a range of cells.
2425
     *
2426
     * @param string $pCoordinate Cell range, examples: 'A1', 'B2:G5', 'A:C', '3:6'
2427
     *
2428
     * @return $this
2429
     */
2430 210
    public function setSelectedCells($pCoordinate)
2431
    {
2432
        // Uppercase coordinate
2433 210
        $pCoordinate = strtoupper($pCoordinate);
2434
2435
        // Convert 'A' to 'A:A'
2436 210
        $pCoordinate = preg_replace('/^([A-Z]+)$/', '${1}:${1}', $pCoordinate);
2437
2438
        // Convert '1' to '1:1'
2439 210
        $pCoordinate = preg_replace('/^(\d+)$/', '${1}:${1}', $pCoordinate);
2440
2441
        // Convert 'A:C' to 'A1:C1048576'
2442 210
        $pCoordinate = preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $pCoordinate);
2443
2444
        // Convert '1:3' to 'A1:XFD3'
2445 210
        $pCoordinate = preg_replace('/^(\d+):(\d+)$/', 'A${1}:XFD${2}', $pCoordinate);
2446
2447 210
        if (Coordinate::coordinateIsRange($pCoordinate)) {
2448 61
            [$first] = Coordinate::splitRange($pCoordinate);
2449 61
            $this->activeCell = $first[0];
2450
        } else {
2451 195
            $this->activeCell = $pCoordinate;
2452
        }
2453 210
        $this->selectedCells = $pCoordinate;
2454
2455 210
        return $this;
2456
    }
2457
2458
    /**
2459
     * Selected cell by using numeric cell coordinates.
2460
     *
2461
     * @param int $columnIndex Numeric column coordinate of the cell
2462
     * @param int $row Numeric row coordinate of the cell
2463
     *
2464
     * @throws Exception
2465
     *
2466
     * @return $this
2467
     */
2468
    public function setSelectedCellByColumnAndRow($columnIndex, $row)
2469
    {
2470
        return $this->setSelectedCells(Coordinate::stringFromColumnIndex($columnIndex) . $row);
2471
    }
2472
2473
    /**
2474
     * Get right-to-left.
2475
     *
2476
     * @return bool
2477
     */
2478 118
    public function getRightToLeft()
2479
    {
2480 118
        return $this->rightToLeft;
2481
    }
2482
2483
    /**
2484
     * Set right-to-left.
2485
     *
2486
     * @param bool $value Right-to-left true/false
2487
     *
2488
     * @return $this
2489
     */
2490 31
    public function setRightToLeft($value)
2491
    {
2492 31
        $this->rightToLeft = $value;
2493
2494 31
        return $this;
2495
    }
2496
2497
    /**
2498
     * Fill worksheet from values in array.
2499
     *
2500
     * @param array $source Source array
2501
     * @param mixed $nullValue Value in source array that stands for blank cell
2502
     * @param string $startCell Insert array starting from this cell address as the top left coordinate
2503
     * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array
2504
     *
2505
     * @throws Exception
2506
     *
2507
     * @return $this
2508
     */
2509 70
    public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false)
2510
    {
2511
        //    Convert a 1-D array to 2-D (for ease of looping)
2512 70
        if (!is_array(end($source))) {
2513 3
            $source = [$source];
2514
        }
2515
2516
        // start coordinate
2517 70
        [$startColumn, $startRow] = Coordinate::coordinateFromString($startCell);
2518
2519
        // Loop through $source
2520 70
        foreach ($source as $rowData) {
2521 70
            $currentColumn = $startColumn;
2522 70
            foreach ($rowData as $cellValue) {
2523 70
                if ($strictNullComparison) {
2524 3
                    if ($cellValue !== $nullValue) {
2525
                        // Set cell value
2526 3
                        $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2527
                    }
2528
                } else {
2529 67
                    if ($cellValue != $nullValue) {
2530
                        // Set cell value
2531 64
                        $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2532
                    }
2533
                }
2534 70
                ++$currentColumn;
2535
            }
2536 70
            ++$startRow;
2537
        }
2538
2539 70
        return $this;
2540
    }
2541
2542
    /**
2543
     * Create array from a range of cells.
2544
     *
2545
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
2546
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2547
     * @param bool $calculateFormulas Should formulas be calculated?
2548
     * @param bool $formatData Should formatting be applied to cell values?
2549
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2550
     *                               True - Return rows and columns indexed by their actual row and column IDs
2551
     *
2552
     * @return array
2553
     */
2554 40
    public function rangeToArray($pRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2555
    {
2556
        // Returnvalue
2557 40
        $returnValue = [];
2558
        //    Identify the range that we need to extract from the worksheet
2559 40
        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange);
2560 40
        $minCol = Coordinate::stringFromColumnIndex($rangeStart[0]);
2561 40
        $minRow = $rangeStart[1];
2562 40
        $maxCol = Coordinate::stringFromColumnIndex($rangeEnd[0]);
2563 40
        $maxRow = $rangeEnd[1];
2564
2565 40
        ++$maxCol;
2566
        // Loop through rows
2567 40
        $r = -1;
2568 40
        for ($row = $minRow; $row <= $maxRow; ++$row) {
2569 40
            $rRef = $returnCellRef ? $row : ++$r;
2570 40
            $c = -1;
2571
            // Loop through columns in the current row
2572 40
            for ($col = $minCol; $col != $maxCol; ++$col) {
2573 40
                $cRef = $returnCellRef ? $col : ++$c;
2574
                //    Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
2575
                //        so we test and retrieve directly against cellCollection
2576 40
                if ($this->cellCollection->has($col . $row)) {
2577
                    // Cell exists
2578 40
                    $cell = $this->cellCollection->get($col . $row);
2579 40
                    if ($cell->getValue() !== null) {
2580 40
                        if ($cell->getValue() instanceof RichText) {
2581 2
                            $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
2582
                        } else {
2583 40
                            if ($calculateFormulas) {
2584 39
                                $returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
2585
                            } else {
2586 2
                                $returnValue[$rRef][$cRef] = $cell->getValue();
2587
                            }
2588
                        }
2589
2590 40
                        if ($formatData) {
2591 39
                            $style = $this->parent->getCellXfByIndex($cell->getXfIndex());
2592 39
                            $returnValue[$rRef][$cRef] = NumberFormat::toFormattedString(
2593 39
                                $returnValue[$rRef][$cRef],
2594 40
                                ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL
2595
                            );
2596
                        }
2597
                    } else {
2598
                        // Cell holds a NULL
2599 40
                        $returnValue[$rRef][$cRef] = $nullValue;
2600
                    }
2601
                } else {
2602
                    // Cell doesn't exist
2603 13
                    $returnValue[$rRef][$cRef] = $nullValue;
2604
                }
2605
            }
2606
        }
2607
2608
        // Return
2609 40
        return $returnValue;
2610
    }
2611
2612
    /**
2613
     * Create array from a range of cells.
2614
     *
2615
     * @param string $pNamedRange Name of the Named Range
2616
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2617
     * @param bool $calculateFormulas Should formulas be calculated?
2618
     * @param bool $formatData Should formatting be applied to cell values?
2619
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2620
     *                                True - Return rows and columns indexed by their actual row and column IDs
2621
     *
2622
     * @throws Exception
2623
     *
2624
     * @return array
2625
     */
2626
    public function namedRangeToArray($pNamedRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2627
    {
2628
        $namedRange = NamedRange::resolveRange($pNamedRange, $this);
2629
        if ($namedRange !== null) {
2630
            $pWorkSheet = $namedRange->getWorksheet();
2631
            $pCellRange = $namedRange->getRange();
2632
2633
            return $pWorkSheet->rangeToArray($pCellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2634
        }
2635
2636
        throw new Exception('Named Range ' . $pNamedRange . ' does not exist.');
2637
    }
2638
2639
    /**
2640
     * Create array from worksheet.
2641
     *
2642
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2643
     * @param bool $calculateFormulas Should formulas be calculated?
2644
     * @param bool $formatData Should formatting be applied to cell values?
2645
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2646
     *                               True - Return rows and columns indexed by their actual row and column IDs
2647
     *
2648
     * @return array
2649
     */
2650 27
    public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2651
    {
2652
        // Garbage collect...
2653 27
        $this->garbageCollect();
2654
2655
        //    Identify the range that we need to extract from the worksheet
2656 27
        $maxCol = $this->getHighestColumn();
2657 27
        $maxRow = $this->getHighestRow();
2658
2659
        // Return
2660 27
        return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2661
    }
2662
2663
    /**
2664
     * Get row iterator.
2665
     *
2666
     * @param int $startRow The row number at which to start iterating
2667
     * @param int $endRow The row number at which to stop iterating
2668
     *
2669
     * @return RowIterator
2670
     */
2671 9
    public function getRowIterator($startRow = 1, $endRow = null)
2672
    {
2673 9
        return new RowIterator($this, $startRow, $endRow);
2674
    }
2675
2676
    /**
2677
     * Get column iterator.
2678
     *
2679
     * @param string $startColumn The column address at which to start iterating
2680
     * @param string $endColumn The column address at which to stop iterating
2681
     *
2682
     * @return ColumnIterator
2683
     */
2684
    public function getColumnIterator($startColumn = 'A', $endColumn = null)
2685
    {
2686
        return new ColumnIterator($this, $startColumn, $endColumn);
2687
    }
2688
2689
    /**
2690
     * Run PhpSpreadsheet garbage collector.
2691
     *
2692
     * @return $this
2693
     */
2694 162
    public function garbageCollect()
2695
    {
2696
        // Flush cache
2697 162
        $this->cellCollection->get('A1');
2698
2699
        // Lookup highest column and highest row if cells are cleaned
2700 162
        $colRow = $this->cellCollection->getHighestRowAndColumn();
2701 162
        $highestRow = $colRow['row'];
2702 162
        $highestColumn = Coordinate::columnIndexFromString($colRow['column']);
2703
2704
        // Loop through column dimensions
2705 162
        foreach ($this->columnDimensions as $dimension) {
2706 36
            $highestColumn = max($highestColumn, Coordinate::columnIndexFromString($dimension->getColumnIndex()));
2707
        }
2708
2709
        // Loop through row dimensions
2710 162
        foreach ($this->rowDimensions as $dimension) {
2711 49
            $highestRow = max($highestRow, $dimension->getRowIndex());
2712
        }
2713
2714
        // Cache values
2715 162
        if ($highestColumn < 1) {
2716
            $this->cachedHighestColumn = 'A';
2717
        } else {
2718 162
            $this->cachedHighestColumn = Coordinate::stringFromColumnIndex($highestColumn);
2719
        }
2720 162
        $this->cachedHighestRow = $highestRow;
2721
2722
        // Return
2723 162
        return $this;
2724
    }
2725
2726
    /**
2727
     * Get hash code.
2728
     *
2729
     * @return string Hash code
2730
     */
2731 215
    public function getHashCode()
2732
    {
2733 215
        if ($this->dirty) {
2734 215
            $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__);
2735 215
            $this->dirty = false;
2736
        }
2737
2738 215
        return $this->hash;
2739
    }
2740
2741
    /**
2742
     * Extract worksheet title from range.
2743
     *
2744
     * Example: extractSheetTitle("testSheet!A1") ==> 'A1'
2745
     * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1'];
2746
     *
2747
     * @param string $pRange Range to extract title from
2748
     * @param bool $returnRange Return range? (see example)
2749
     *
2750
     * @return mixed
2751
     */
2752 113
    public static function extractSheetTitle($pRange, $returnRange = false)
2753
    {
2754
        // Sheet title included?
2755 113
        if (($sep = strrpos($pRange, '!')) === false) {
2756 52
            return $returnRange ? ['', $pRange] : '';
2757
        }
2758
2759 86
        if ($returnRange) {
2760 86
            return [substr($pRange, 0, $sep), substr($pRange, $sep + 1)];
2761
        }
2762
2763 7
        return substr($pRange, $sep + 1);
2764
    }
2765
2766
    /**
2767
     * Get hyperlink.
2768
     *
2769
     * @param string $pCellCoordinate Cell coordinate to get hyperlink for, eg: 'A1'
2770
     *
2771
     * @return Hyperlink
2772
     */
2773 23
    public function getHyperlink($pCellCoordinate)
2774
    {
2775
        // return hyperlink if we already have one
2776 23
        if (isset($this->hyperlinkCollection[$pCellCoordinate])) {
2777 14
            return $this->hyperlinkCollection[$pCellCoordinate];
2778
        }
2779
2780
        // else create hyperlink
2781 23
        $this->hyperlinkCollection[$pCellCoordinate] = new Hyperlink();
2782
2783 23
        return $this->hyperlinkCollection[$pCellCoordinate];
2784
    }
2785
2786
    /**
2787
     * Set hyperlink.
2788
     *
2789
     * @param string $pCellCoordinate Cell coordinate to insert hyperlink, eg: 'A1'
2790
     * @param null|Hyperlink $pHyperlink
2791
     *
2792
     * @return $this
2793
     */
2794 12
    public function setHyperlink($pCellCoordinate, Hyperlink $pHyperlink = null)
2795
    {
2796 12
        if ($pHyperlink === null) {
2797 12
            unset($this->hyperlinkCollection[$pCellCoordinate]);
2798
        } else {
2799 12
            $this->hyperlinkCollection[$pCellCoordinate] = $pHyperlink;
2800
        }
2801
2802 12
        return $this;
2803
    }
2804
2805
    /**
2806
     * Hyperlink at a specific coordinate exists?
2807
     *
2808
     * @param string $pCoordinate eg: 'A1'
2809
     *
2810
     * @return bool
2811
     */
2812 13
    public function hyperlinkExists($pCoordinate)
2813
    {
2814 13
        return isset($this->hyperlinkCollection[$pCoordinate]);
2815
    }
2816
2817
    /**
2818
     * Get collection of hyperlinks.
2819
     *
2820
     * @return Hyperlink[]
2821
     */
2822 133
    public function getHyperlinkCollection()
2823
    {
2824 133
        return $this->hyperlinkCollection;
2825
    }
2826
2827
    /**
2828
     * Get data validation.
2829
     *
2830
     * @param string $pCellCoordinate Cell coordinate to get data validation for, eg: 'A1'
2831
     *
2832
     * @return DataValidation
2833
     */
2834 5
    public function getDataValidation($pCellCoordinate)
2835
    {
2836
        // return data validation if we already have one
2837 5
        if (isset($this->dataValidationCollection[$pCellCoordinate])) {
2838 2
            return $this->dataValidationCollection[$pCellCoordinate];
2839
        }
2840
2841
        // else create data validation
2842 5
        $this->dataValidationCollection[$pCellCoordinate] = new DataValidation();
2843
2844 5
        return $this->dataValidationCollection[$pCellCoordinate];
2845
    }
2846
2847
    /**
2848
     * Set data validation.
2849
     *
2850
     * @param string $pCellCoordinate Cell coordinate to insert data validation, eg: 'A1'
2851
     * @param null|DataValidation $pDataValidation
2852
     *
2853
     * @return $this
2854
     */
2855
    public function setDataValidation($pCellCoordinate, DataValidation $pDataValidation = null)
2856
    {
2857
        if ($pDataValidation === null) {
2858
            unset($this->dataValidationCollection[$pCellCoordinate]);
2859
        } else {
2860
            $this->dataValidationCollection[$pCellCoordinate] = $pDataValidation;
2861
        }
2862
2863
        return $this;
2864
    }
2865
2866
    /**
2867
     * Data validation at a specific coordinate exists?
2868
     *
2869
     * @param string $pCoordinate eg: 'A1'
2870
     *
2871
     * @return bool
2872
     */
2873 4
    public function dataValidationExists($pCoordinate)
2874
    {
2875 4
        return isset($this->dataValidationCollection[$pCoordinate]);
2876
    }
2877
2878
    /**
2879
     * Get collection of data validations.
2880
     *
2881
     * @return DataValidation[]
2882
     */
2883 133
    public function getDataValidationCollection()
2884
    {
2885 133
        return $this->dataValidationCollection;
2886
    }
2887
2888
    /**
2889
     * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet.
2890
     *
2891
     * @param string $range
2892
     *
2893
     * @return string Adjusted range value
2894
     */
2895 1
    public function shrinkRangeToFit($range)
2896
    {
2897 1
        $maxCol = $this->getHighestColumn();
2898 1
        $maxRow = $this->getHighestRow();
2899 1
        $maxCol = Coordinate::columnIndexFromString($maxCol);
2900
2901 1
        $rangeBlocks = explode(' ', $range);
2902 1
        foreach ($rangeBlocks as &$rangeSet) {
2903 1
            $rangeBoundaries = Coordinate::getRangeBoundaries($rangeSet);
2904
2905 1
            if (Coordinate::columnIndexFromString($rangeBoundaries[0][0]) > $maxCol) {
2906
                $rangeBoundaries[0][0] = Coordinate::stringFromColumnIndex($maxCol);
2907
            }
2908 1
            if ($rangeBoundaries[0][1] > $maxRow) {
2909
                $rangeBoundaries[0][1] = $maxRow;
2910
            }
2911 1
            if (Coordinate::columnIndexFromString($rangeBoundaries[1][0]) > $maxCol) {
2912
                $rangeBoundaries[1][0] = Coordinate::stringFromColumnIndex($maxCol);
2913
            }
2914 1
            if ($rangeBoundaries[1][1] > $maxRow) {
2915
                $rangeBoundaries[1][1] = $maxRow;
2916
            }
2917 1
            $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1];
2918
        }
2919 1
        unset($rangeSet);
2920
2921 1
        return implode(' ', $rangeBlocks);
2922
    }
2923
2924
    /**
2925
     * Get tab color.
2926
     *
2927
     * @return Color
2928
     */
2929 11
    public function getTabColor()
2930
    {
2931 11
        if ($this->tabColor === null) {
2932 11
            $this->tabColor = new Color();
2933
        }
2934
2935 11
        return $this->tabColor;
2936
    }
2937
2938
    /**
2939
     * Reset tab color.
2940
     *
2941
     * @return $this
2942
     */
2943
    public function resetTabColor()
2944
    {
2945
        $this->tabColor = null;
2946
        unset($this->tabColor);
2947
2948
        return $this;
2949
    }
2950
2951
    /**
2952
     * Tab color set?
2953
     *
2954
     * @return bool
2955
     */
2956 127
    public function isTabColorSet()
2957
    {
2958 127
        return $this->tabColor !== null;
2959
    }
2960
2961
    /**
2962
     * Copy worksheet (!= clone!).
2963
     *
2964
     * @return static
2965
     */
2966
    public function copy()
2967
    {
2968
        return clone $this;
2969
    }
2970
2971
    /**
2972
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
2973
     */
2974 2
    public function __clone()
2975
    {
2976 2
        foreach ($this as $key => $val) {
2977 2
            if ($key == 'parent') {
2978 2
                continue;
2979
            }
2980
2981 2
            if (is_object($val) || (is_array($val))) {
2982 2
                if ($key == 'cellCollection') {
2983 2
                    $newCollection = $this->cellCollection->cloneCellCollection($this);
2984 2
                    $this->cellCollection = $newCollection;
2985 2
                } elseif ($key == 'drawingCollection') {
2986 2
                    $currentCollection = $this->drawingCollection;
2987 2
                    $this->drawingCollection = new ArrayObject();
0 ignored issues
show
Documentation Bug introduced by
It seems like new ArrayObject() of type ArrayObject is incompatible with the declared type PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing[] of property $drawingCollection.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2988 2
                    foreach ($currentCollection as $item) {
2989 1
                        if (is_object($item)) {
2990 1
                            $newDrawing = clone $item;
2991 1
                            $newDrawing->setWorksheet($this);
2992
                        }
2993
                    }
2994 2
                } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) {
2995 2
                    $newAutoFilter = clone $this->autoFilter;
2996 2
                    $this->autoFilter = $newAutoFilter;
2997 2
                    $this->autoFilter->setParent($this);
2998
                } else {
2999 2
                    $this->{$key} = unserialize(serialize($val));
3000
                }
3001
            }
3002
        }
3003 2
    }
3004
3005
    /**
3006
     * Define the code name of the sheet.
3007
     *
3008
     * @param string $pValue Same rule as Title minus space not allowed (but, like Excel, change
3009
     *                       silently space to underscore)
3010
     * @param bool $validate False to skip validation of new title. WARNING: This should only be set
3011
     *                       at parse time (by Readers), where titles can be assumed to be valid.
3012
     *
3013
     * @throws Exception
3014
     *
3015
     * @return $this
3016
     */
3017 316
    public function setCodeName($pValue, $validate = true)
3018
    {
3019
        // Is this a 'rename' or not?
3020 316
        if ($this->getCodeName() == $pValue) {
3021
            return $this;
3022
        }
3023
3024 316
        if ($validate) {
3025 316
            $pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same
3026
3027
            // Syntax check
3028
            // throw an exception if not valid
3029 316
            self::checkSheetCodeName($pValue);
3030
3031
            // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
3032
3033 316
            if ($this->getParent()) {
3034
                // Is there already such sheet name?
3035 299
                if ($this->getParent()->sheetCodeNameExists($pValue)) {
3036
                    // Use name, but append with lowest possible integer
3037
3038 47
                    if (Shared\StringHelper::countCharacters($pValue) > 29) {
3039
                        $pValue = Shared\StringHelper::substring($pValue, 0, 29);
3040
                    }
3041 47
                    $i = 1;
3042 47
                    while ($this->getParent()->sheetCodeNameExists($pValue . '_' . $i)) {
3043 14
                        ++$i;
3044 14
                        if ($i == 10) {
3045
                            if (Shared\StringHelper::countCharacters($pValue) > 28) {
3046
                                $pValue = Shared\StringHelper::substring($pValue, 0, 28);
3047
                            }
3048 14
                        } elseif ($i == 100) {
3049
                            if (Shared\StringHelper::countCharacters($pValue) > 27) {
3050
                                $pValue = Shared\StringHelper::substring($pValue, 0, 27);
3051
                            }
3052
                        }
3053
                    }
3054
3055 47
                    $pValue .= '_' . $i; // ok, we have a valid name
3056
                }
3057
            }
3058
        }
3059
3060 316
        $this->codeName = $pValue;
3061
3062 316
        return $this;
3063
    }
3064
3065
    /**
3066
     * Return the code name of the sheet.
3067
     *
3068
     * @return null|string
3069
     */
3070 316
    public function getCodeName()
3071
    {
3072 316
        return $this->codeName;
3073
    }
3074
3075
    /**
3076
     * Sheet has a code name ?
3077
     *
3078
     * @return bool
3079
     */
3080 1
    public function hasCodeName()
3081
    {
3082 1
        return $this->codeName !== null;
3083
    }
3084
}
3085