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

Worksheet::removeRow()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.0073

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 7
nop 2
dl 0
loc 24
ccs 14
cts 15
cp 0.9333
crap 5.0073
rs 9.4888
c 0
b 0
f 0
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