Passed
Push — master ( 75dfcb...5ec0e3 )
by Adrien
27:56
created

Worksheet::setPrintGridlines()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
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 266
    public function __construct(Spreadsheet $parent = null, $pTitle = 'Worksheet')
348
    {
349
        // Set parent and title
350 266
        $this->parent = $parent;
351 266
        $this->setTitle($pTitle, false);
352
        // setTitle can change $pTitle
353 266
        $this->setCodeName($this->getTitle());
354 266
        $this->setSheetState(self::SHEETSTATE_VISIBLE);
355
356 266
        $this->cellCollection = CellsFactory::getInstance($this);
357
        // Set page setup
358 266
        $this->pageSetup = new PageSetup();
359
        // Set page margins
360 266
        $this->pageMargins = new PageMargins();
361
        // Set page header/footer
362 266
        $this->headerFooter = new HeaderFooter();
363
        // Set sheet view
364 266
        $this->sheetView = new SheetView();
365
        // Drawing collection
366 266
        $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 266
        $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 266
        $this->protection = new Protection();
371
        // Default row dimension
372 266
        $this->defaultRowDimension = new RowDimension(null);
373
        // Default column dimension
374 266
        $this->defaultColumnDimension = new ColumnDimension(null);
375 266
        $this->autoFilter = new AutoFilter(null, $this);
376 266
    }
377
378
    /**
379
     * Disconnect all cells from this Worksheet object,
380
     * typically so that the worksheet object can be unset.
381
     */
382 27
    public function disconnectCells()
383
    {
384 27
        if ($this->cellCollection !== null) {
385 4
            $this->cellCollection->unsetWorksheetCells();
386 4
            $this->cellCollection = null;
387
        }
388
        //    detach ourself from the workbook, so that it can then delete this worksheet successfully
389 27
        $this->parent = null;
390 27
    }
391
392
    /**
393
     * Code to execute when this worksheet is unset().
394
     */
395 27
    public function __destruct()
396
    {
397 27
        Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title);
398
399 27
        $this->disconnectCells();
400 27
    }
401
402
    /**
403
     * Return the cell collection.
404
     *
405
     * @return Cells
406
     */
407 216
    public function getCellCollection()
408
    {
409 216
        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 266
    private static function checkSheetCodeName($pValue)
432
    {
433 266
        $CharCount = Shared\StringHelper::countCharacters($pValue);
434 266
        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 266
        if ((str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) ||
439 266
            (Shared\StringHelper::substring($pValue, -1, 1) == '\'') ||
440 266
            (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 266
        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 266
        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 266
    private static function checkSheetTitle($pValue)
462
    {
463
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ]
464 266
        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 266
        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 266
        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 126
    public function getCoordinates($sorted = true)
484
    {
485 126
        if ($this->cellCollection == null) {
486
            return [];
487
        }
488
489 126
        if ($sorted) {
490 101
            return $this->cellCollection->getSortedCoordinates();
491
        }
492
493 120
        return $this->cellCollection->getCoordinates();
494
    }
495
496
    /**
497
     * Get collection of row dimensions.
498
     *
499
     * @return RowDimension[]
500
     */
501 113
    public function getRowDimensions()
502
    {
503 113
        return $this->rowDimensions;
504
    }
505
506
    /**
507
     * Get default row dimension.
508
     *
509
     * @return RowDimension
510
     */
511 118
    public function getDefaultRowDimension()
512
    {
513 118
        return $this->defaultRowDimension;
514
    }
515
516
    /**
517
     * Get collection of column dimensions.
518
     *
519
     * @return ColumnDimension[]
520
     */
521 113
    public function getColumnDimensions()
522
    {
523 113
        return $this->columnDimensions;
524
    }
525
526
    /**
527
     * Get default column dimension.
528
     *
529
     * @return ColumnDimension
530
     */
531 93
    public function getDefaultColumnDimension()
532
    {
533 93
        return $this->defaultColumnDimension;
534
    }
535
536
    /**
537
     * Get collection of drawings.
538
     *
539
     * @return BaseDrawing[]
540
     */
541 111
    public function getDrawingCollection()
542
    {
543 111
        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 Worksheet
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 Worksheet
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 97
    public function calculateWorksheetDimension()
691
    {
692
        // Return
693 97
        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 Worksheet;
711
     */
712 61
    public function calculateColumnWidths()
713
    {
714
        // initialize $autoSizes array
715 61
        $autoSizes = [];
716 61
        foreach ($this->getColumnDimensions() as $colDimension) {
717 27
            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 61
        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 13
                                $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 61
        return $this;
783
    }
784
785
    /**
786
     * Get parent.
787
     *
788
     * @return Spreadsheet
789
     */
790 266
    public function getParent()
791
    {
792 266
        return $this->parent;
793
    }
794
795
    /**
796
     * Re-bind parent.
797
     *
798
     * @param Spreadsheet $parent
799
     *
800
     * @return Worksheet
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 266
    public function getTitle()
825
    {
826 266
        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 Worksheet
842
     */
843 266
    public function setTitle($pValue, $updateFormulaCellReferences = true, $validate = true)
844
    {
845
        // Is this a 'rename' or not?
846 266
        if ($this->getTitle() == $pValue) {
847 42
            return $this;
848
        }
849
850
        // Old title
851 266
        $oldTitle = $this->getTitle();
852
853 266
        if ($validate) {
854
            // Syntax check
855 266
            self::checkSheetTitle($pValue);
856
857 266
            if ($this->parent) {
858
                // Is there already such sheet name?
859 249
                if ($this->parent->sheetNameExists($pValue)) {
860
                    // Use name, but append with lowest possible integer
861
862 6
                    if (Shared\StringHelper::countCharacters($pValue) > 29) {
863
                        $pValue = Shared\StringHelper::substring($pValue, 0, 29);
864
                    }
865 6
                    $i = 1;
866 6
                    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 6
                    $pValue .= " $i";
880
                }
881
            }
882
        }
883
884
        // Set title
885 266
        $this->title = $pValue;
886 266
        $this->dirty = true;
887
888 266
        if ($this->parent && $this->parent->getCalculationEngine()) {
889
            // New title
890 249
            $newTitle = $this->getTitle();
891 249
            $this->parent->getCalculationEngine()
892 249
                ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
893 249
            if ($updateFormulaCellReferences) {
894 48
                ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
895
            }
896
        }
897
898 266
        return $this;
899
    }
900
901
    /**
902
     * Get sheet state.
903
     *
904
     * @return string Sheet state (visible, hidden, veryHidden)
905
     */
906 92
    public function getSheetState()
907
    {
908 92
        return $this->sheetState;
909
    }
910
911
    /**
912
     * Set sheet state.
913
     *
914
     * @param string $value Sheet state (visible, hidden, veryHidden)
915
     *
916
     * @return Worksheet
917
     */
918 266
    public function setSheetState($value)
919
    {
920 266
        $this->sheetState = $value;
921
922 266
        return $this;
923
    }
924
925
    /**
926
     * Get page setup.
927
     *
928
     * @return PageSetup
929
     */
930 128
    public function getPageSetup()
931
    {
932 128
        return $this->pageSetup;
933
    }
934
935
    /**
936
     * Set page setup.
937
     *
938
     * @param PageSetup $pValue
939
     *
940
     * @return Worksheet
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 126
    public function getPageMargins()
955
    {
956 126
        return $this->pageMargins;
957
    }
958
959
    /**
960
     * Set page margins.
961
     *
962
     * @param PageMargins $pValue
963
     *
964
     * @return Worksheet
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 100
    public function getHeaderFooter()
979
    {
980 100
        return $this->headerFooter;
981
    }
982
983
    /**
984
     * Set page header/footer.
985
     *
986
     * @param HeaderFooter $pValue
987
     *
988
     * @return Worksheet
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 107
    public function getSheetView()
1003
    {
1004 107
        return $this->sheetView;
1005
    }
1006
1007
    /**
1008
     * Set sheet view.
1009
     *
1010
     * @param SheetView $pValue
1011
     *
1012
     * @return Worksheet
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 102
    public function getProtection()
1027
    {
1028 102
        return $this->protection;
1029
    }
1030
1031
    /**
1032
     * Set Protection.
1033
     *
1034
     * @param Protection $pValue
1035
     *
1036
     * @return Worksheet
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 134
    public function getHighestColumn($row = null)
1055
    {
1056 134
        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 134
            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 134
    public function getHighestRow($column = null)
1085
    {
1086 134
        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 134
            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 16
    public function getHighestDataRow($column = null)
1102
    {
1103 16
        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 Worksheet
1123
     */
1124 78
    public function setCellValue($pCoordinate, $pValue)
1125
    {
1126 78
        $this->getCell($pCoordinate)->setValue($pValue);
1127
1128 78
        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 Worksheet
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 Worksheet
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 Worksheet
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 214
    public function getCell($pCoordinate, $createIfNotExists = true)
1193
    {
1194
        // Uppercase coordinate
1195 214
        $pCoordinateUpper = strtoupper($pCoordinate);
1196
1197
        // Check cell collection
1198 214
        if ($this->cellCollection->has($pCoordinateUpper)) {
1199 186
            return $this->cellCollection->get($pCoordinateUpper);
1200
        }
1201
1202
        // Worksheet reference?
1203 208
        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 208
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1211 208
            (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 208
        if (Coordinate::coordinateIsRange($pCoordinate)) {
1221
            throw new Exception('Cell coordinate can not be a range of cells.');
1222 208
        } elseif (strpos($pCoordinate, '$') !== false) {
1223
            throw new Exception('Cell coordinate must not be absolute.');
1224
        }
1225
1226
        // Create new cell object, if required
1227 208
        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 62
    public function getCellByColumnAndRow($columnIndex, $row, $createIfNotExists = true)
1241
    {
1242 62
        $columnLetter = Coordinate::stringFromColumnIndex($columnIndex);
1243 62
        $coordinate = $columnLetter . $row;
1244
1245 62
        if ($this->cellCollection->has($coordinate)) {
1246 61
            return $this->cellCollection->get($coordinate);
1247
        }
1248
1249
        // Create new cell object, if required
1250 34
        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 216
    private function createNewCell($pCoordinate)
1261
    {
1262 216
        $cell = new Cell(null, DataType::TYPE_NULL, $this);
1263 216
        $this->cellCollection->add($pCoordinate, $cell);
1264 216
        $this->cellCollectionIsSorted = false;
1265
1266
        // Coordinates
1267 216
        $aCoordinates = Coordinate::coordinateFromString($pCoordinate);
1268 216
        if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($aCoordinates[0])) {
1269 155
            $this->cachedHighestColumn = $aCoordinates[0];
1270
        }
1271 216
        if ($aCoordinates[1] > $this->cachedHighestRow) {
1272 141
            $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 216
        $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 216
        $columnDimension = $this->getColumnDimension($aCoordinates[0], false);
1279
1280 216
        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 216
        } 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 216
        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 115
    public function cellExists($pCoordinate)
1301
    {
1302
        // Worksheet reference?
1303 115
        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 115
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1311 115
            (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 115
        $pCoordinate = strtoupper($pCoordinate);
1329
1330 115
        if (Coordinate::coordinateIsRange($pCoordinate)) {
1331
            throw new Exception('Cell coordinate can not be a range of cells.');
1332 115
        } elseif (strpos($pCoordinate, '$') !== false) {
1333
            throw new Exception('Cell coordinate must not be absolute.');
1334
        }
1335
1336
        // Cell exists?
1337 115
        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 219
    public function getRowDimension($pRow, $create = true)
1362
    {
1363
        // Found
1364 219
        $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 219
        if (!isset($this->rowDimensions[$pRow])) {
1368 219
            if (!$create) {
1369 201
                return null;
1370
            }
1371 108
            $this->rowDimensions[$pRow] = new RowDimension($pRow);
1372
1373 108
            $this->cachedHighestRow = max($this->cachedHighestRow, $pRow);
1374
        }
1375
1376 108
        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 218
    public function getColumnDimension($pColumn, $create = true)
1388
    {
1389
        // Uppercase coordinate
1390 218
        $pColumn = strtoupper($pColumn);
1391
1392
        // Fetch dimensions
1393 218
        if (!isset($this->columnDimensions[$pColumn])) {
1394 218
            if (!$create) {
1395 209
                return null;
1396
            }
1397 53
            $this->columnDimensions[$pColumn] = new ColumnDimension($pColumn);
1398
1399 53
            if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($pColumn)) {
1400 31
                $this->cachedHighestColumn = $pColumn;
1401
            }
1402
        }
1403
1404 53
        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 18
    public function getColumnDimensionByColumn($columnIndex)
1415
    {
1416 18
        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 118
    public function getStyle($pCellCoordinate)
1439
    {
1440
        // set this sheet as active
1441 118
        $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
1442
1443
        // set cell coordinate as active
1444 118
        $this->setSelectedCells($pCellCoordinate);
1445
1446 118
        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 4
    public function getConditionalStyles($pCoordinate)
1457
    {
1458 4
        $pCoordinate = strtoupper($pCoordinate);
1459 4
        if (!isset($this->conditionalStylesCollection[$pCoordinate])) {
1460 2
            $this->conditionalStylesCollection[$pCoordinate] = [];
1461
        }
1462
1463 4
        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 Worksheet
1484
     */
1485 14
    public function removeConditionalStyles($pCoordinate)
1486
    {
1487 14
        unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
1488
1489 14
        return $this;
1490
    }
1491
1492
    /**
1493
     * Get collection of conditional styles.
1494
     *
1495
     * @return array
1496
     */
1497 92
    public function getConditionalStylesCollection()
1498
    {
1499 92
        return $this->conditionalStylesCollection;
1500
    }
1501
1502
    /**
1503
     * Set conditional styles.
1504
     *
1505
     * @param string $pCoordinate eg: 'A1'
1506
     * @param $pValue Conditional[]
1507
     *
1508
     * @return Worksheet
1509
     */
1510 4
    public function setConditionalStyles($pCoordinate, $pValue)
1511
    {
1512 4
        $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue;
1513
1514 4
        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 Worksheet
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 Worksheet
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 Worksheet
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 Worksheet
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 101
    public function getBreaks()
1673
    {
1674 101
        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 Worksheet
1685
     */
1686 38
    public function mergeCells($pRange)
1687
    {
1688
        // Uppercase coordinate
1689 38
        $pRange = strtoupper($pRange);
1690
1691 38
        if (strpos($pRange, ':') !== false) {
1692 38
            $this->mergeCells[$pRange] = $pRange;
1693
1694
            // make sure cells are created
1695
1696
            // get the cells in the range
1697 38
            $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
1698
1699
            // create upper left cell if it does not already exist
1700 38
            $upperLeft = $aReferences[0];
1701 38
            if (!$this->cellExists($upperLeft)) {
1702 13
                $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 38
            $count = count($aReferences);
1707 38
            for ($i = 1; $i < $count; ++$i) {
1708 38
                if ($this->cellExists($aReferences[$i])) {
1709 15
                    $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 38
        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 Worksheet
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 Worksheet
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 Worksheet
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 116
    public function getMergeCells()
1790
    {
1791 116
        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 Worksheet
1801
     */
1802 19
    public function setMergeCells(array $pValue)
1803
    {
1804 19
        $this->mergeCells = $pValue;
1805
1806 19
        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 Worksheet
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 Worksheet
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 Worksheet
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 Worksheet
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 100
    public function getProtectedCells()
1898
    {
1899 100
        return $this->protectedCells;
1900
    }
1901
1902
    /**
1903
     * Get Autofilter.
1904
     *
1905
     * @return AutoFilter
1906
     */
1907 102
    public function getAutoFilter()
1908
    {
1909 102
        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 Worksheet
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 Worksheet
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 Worksheet
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 100
    public function getFreezePane()
1972
    {
1973 100
        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 Worksheet
1991
     */
1992 8
    public function freezePane($cell, $topLeftCell = null)
1993
    {
1994 8
        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 8
        if ($cell !== null && $topLeftCell === null) {
1999 5
            $coordinate = Coordinate::coordinateFromString($cell);
2000 5
            $topLeftCell = $coordinate[0] . $coordinate[1];
2001
        }
2002
2003 8
        $this->freezePane = $cell;
2004 8
        $this->topLeftCell = $topLeftCell;
2005
2006 8
        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 Worksheet
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 Worksheet
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 7
    public function getTopLeftCell()
2038
    {
2039 7
        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 Worksheet
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 Worksheet
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 Worksheet
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 Worksheet
2114
     */
2115 15
    public function removeRow($pRow, $pNumRows = 1)
2116
    {
2117 15
        if ($pRow >= 1) {
2118 15
            for ($r = 0; $r < $pNumRows; ++$r) {
2119 15
                $this->getCellCollection()->removeRow($pRow + $r);
2120
            }
2121
2122 15
            $highestRow = $this->getHighestDataRow();
2123 15
            $objReferenceHelper = ReferenceHelper::getInstance();
2124 15
            $objReferenceHelper->insertNewBefore('A' . ($pRow + $pNumRows), 0, -$pNumRows, $this);
2125 15
            for ($r = 0; $r < $pNumRows; ++$r) {
2126 15
                $this->getCellCollection()->removeRow($highestRow);
2127 15
                --$highestRow;
2128
            }
2129
        } else {
2130
            throw new Exception('Rows to be deleted should at least start from row 1.');
2131
        }
2132
2133 15
        return $this;
2134
    }
2135
2136
    /**
2137
     * Remove a column, updating all possible related data.
2138
     *
2139
     * @param string $pColumn Remove starting with this one, eg: 'A'
2140
     * @param int $pNumCols Number of columns to remove
2141
     *
2142
     * @throws Exception
2143
     *
2144
     * @return Worksheet
2145
     */
2146 16
    public function removeColumn($pColumn, $pNumCols = 1)
2147
    {
2148 16
        if (is_numeric($pColumn)) {
2149
            throw new Exception('Column references should not be numeric.');
2150
        }
2151
2152 16
        $highestColumn = $this->getHighestDataColumn();
2153 16
        $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
2154 16
        $pColumnIndex = Coordinate::columnIndexFromString($pColumn);
2155
2156 16
        if ($pColumnIndex > $highestColumnIndex) {
2157 1
            return $this;
2158
        }
2159
2160 15
        $pColumn = Coordinate::stringFromColumnIndex($pColumnIndex + $pNumCols);
2161 15
        $objReferenceHelper = ReferenceHelper::getInstance();
2162 15
        $objReferenceHelper->insertNewBefore($pColumn . '1', -$pNumCols, 0, $this);
2163
2164 15
        $maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1;
2165
2166 15
        for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $pNumCols); $c < $n; ++$c) {
2167 15
            $this->getCellCollection()->removeColumn($highestColumn);
2168 15
            $highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1);
2169
        }
2170
2171 15
        $this->garbageCollect();
2172
2173 15
        return $this;
2174
    }
2175
2176
    /**
2177
     * Remove a column, updating all possible related data.
2178
     *
2179
     * @param int $columnIndex Remove starting with this one (numeric column coordinate of the cell)
2180
     * @param int $numColumns Number of columns to remove
2181
     *
2182
     * @throws Exception
2183
     *
2184
     * @return Worksheet
2185
     */
2186
    public function removeColumnByIndex($columnIndex, $numColumns = 1)
2187
    {
2188
        if ($columnIndex >= 1) {
2189
            return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns);
2190
        }
2191
2192
        throw new Exception('Columns to be deleted should at least start from column A (1)');
2193
    }
2194
2195
    /**
2196
     * Show gridlines?
2197
     *
2198
     * @return bool
2199
     */
2200 103
    public function getShowGridlines()
2201
    {
2202 103
        return $this->showGridlines;
2203
    }
2204
2205
    /**
2206
     * Set show gridlines.
2207
     *
2208
     * @param bool $pValue Show gridlines (true/false)
2209
     *
2210
     * @return Worksheet
2211
     */
2212 59
    public function setShowGridlines($pValue)
2213
    {
2214 59
        $this->showGridlines = $pValue;
2215
2216 59
        return $this;
2217
    }
2218
2219
    /**
2220
     * Print gridlines?
2221
     *
2222
     * @return bool
2223
     */
2224 92
    public function getPrintGridlines()
2225
    {
2226 92
        return $this->printGridlines;
2227
    }
2228
2229
    /**
2230
     * Set print gridlines.
2231
     *
2232
     * @param bool $pValue Print gridlines (true/false)
2233
     *
2234
     * @return Worksheet
2235
     */
2236 22
    public function setPrintGridlines($pValue)
2237
    {
2238 22
        $this->printGridlines = $pValue;
2239
2240 22
        return $this;
2241
    }
2242
2243
    /**
2244
     * Show row and column headers?
2245
     *
2246
     * @return bool
2247
     */
2248 92
    public function getShowRowColHeaders()
2249
    {
2250 92
        return $this->showRowColHeaders;
2251
    }
2252
2253
    /**
2254
     * Set show row and column headers.
2255
     *
2256
     * @param bool $pValue Show row and column headers (true/false)
2257
     *
2258
     * @return Worksheet
2259
     */
2260 57
    public function setShowRowColHeaders($pValue)
2261
    {
2262 57
        $this->showRowColHeaders = $pValue;
2263
2264 57
        return $this;
2265
    }
2266
2267
    /**
2268
     * Show summary below? (Row/Column outlining).
2269
     *
2270
     * @return bool
2271
     */
2272 92
    public function getShowSummaryBelow()
2273
    {
2274 92
        return $this->showSummaryBelow;
2275
    }
2276
2277
    /**
2278
     * Set show summary below.
2279
     *
2280
     * @param bool $pValue Show summary below (true/false)
2281
     *
2282
     * @return Worksheet
2283
     */
2284 56
    public function setShowSummaryBelow($pValue)
2285
    {
2286 56
        $this->showSummaryBelow = $pValue;
2287
2288 56
        return $this;
2289
    }
2290
2291
    /**
2292
     * Show summary right? (Row/Column outlining).
2293
     *
2294
     * @return bool
2295
     */
2296 92
    public function getShowSummaryRight()
2297
    {
2298 92
        return $this->showSummaryRight;
2299
    }
2300
2301
    /**
2302
     * Set show summary right.
2303
     *
2304
     * @param bool $pValue Show summary right (true/false)
2305
     *
2306
     * @return Worksheet
2307
     */
2308 56
    public function setShowSummaryRight($pValue)
2309
    {
2310 56
        $this->showSummaryRight = $pValue;
2311
2312 56
        return $this;
2313
    }
2314
2315
    /**
2316
     * Get comments.
2317
     *
2318
     * @return Comment[]
2319
     */
2320 109
    public function getComments()
2321
    {
2322 109
        return $this->comments;
2323
    }
2324
2325
    /**
2326
     * Set comments array for the entire sheet.
2327
     *
2328
     * @param Comment[] $pValue
2329
     *
2330
     * @return Worksheet
2331
     */
2332 19
    public function setComments(array $pValue)
2333
    {
2334 19
        $this->comments = $pValue;
2335
2336 19
        return $this;
2337
    }
2338
2339
    /**
2340
     * Get comment for cell.
2341
     *
2342
     * @param string $pCellCoordinate Cell coordinate to get comment for, eg: 'A1'
2343
     *
2344
     * @throws Exception
2345
     *
2346
     * @return Comment
2347
     */
2348 29
    public function getComment($pCellCoordinate)
2349
    {
2350
        // Uppercase coordinate
2351 29
        $pCellCoordinate = strtoupper($pCellCoordinate);
2352
2353 29
        if (Coordinate::coordinateIsRange($pCellCoordinate)) {
2354
            throw new Exception('Cell coordinate string can not be a range of cells.');
2355 29
        } elseif (strpos($pCellCoordinate, '$') !== false) {
2356
            throw new Exception('Cell coordinate string must not be absolute.');
2357 29
        } elseif ($pCellCoordinate == '') {
2358
            throw new Exception('Cell coordinate can not be zero-length string.');
2359
        }
2360
2361
        // Check if we already have a comment for this cell.
2362 29
        if (isset($this->comments[$pCellCoordinate])) {
2363 19
            return $this->comments[$pCellCoordinate];
2364
        }
2365
2366
        // If not, create a new comment.
2367 29
        $newComment = new Comment();
2368 29
        $this->comments[$pCellCoordinate] = $newComment;
2369
2370 29
        return $newComment;
2371
    }
2372
2373
    /**
2374
     * Get comment for cell by using numeric cell coordinates.
2375
     *
2376
     * @param int $columnIndex Numeric column coordinate of the cell
2377
     * @param int $row Numeric row coordinate of the cell
2378
     *
2379
     * @return Comment
2380
     */
2381 3
    public function getCommentByColumnAndRow($columnIndex, $row)
2382
    {
2383 3
        return $this->getComment(Coordinate::stringFromColumnIndex($columnIndex) . $row);
2384
    }
2385
2386
    /**
2387
     * Get active cell.
2388
     *
2389
     * @return string Example: 'A1'
2390
     */
2391 141
    public function getActiveCell()
2392
    {
2393 141
        return $this->activeCell;
2394
    }
2395
2396
    /**
2397
     * Get selected cells.
2398
     *
2399
     * @return string
2400
     */
2401 127
    public function getSelectedCells()
2402
    {
2403 127
        return $this->selectedCells;
2404
    }
2405
2406
    /**
2407
     * Selected cell.
2408
     *
2409
     * @param string $pCoordinate Cell (i.e. A1)
2410
     *
2411
     * @return Worksheet
2412
     */
2413
    public function setSelectedCell($pCoordinate)
2414
    {
2415
        return $this->setSelectedCells($pCoordinate);
2416
    }
2417
2418
    /**
2419
     * Select a range of cells.
2420
     *
2421
     * @param string $pCoordinate Cell range, examples: 'A1', 'B2:G5', 'A:C', '3:6'
2422
     *
2423
     * @return Worksheet
2424
     */
2425 166
    public function setSelectedCells($pCoordinate)
2426
    {
2427
        // Uppercase coordinate
2428 166
        $pCoordinate = strtoupper($pCoordinate);
2429
2430
        // Convert 'A' to 'A:A'
2431 166
        $pCoordinate = preg_replace('/^([A-Z]+)$/', '${1}:${1}', $pCoordinate);
2432
2433
        // Convert '1' to '1:1'
2434 166
        $pCoordinate = preg_replace('/^(\d+)$/', '${1}:${1}', $pCoordinate);
2435
2436
        // Convert 'A:C' to 'A1:C1048576'
2437 166
        $pCoordinate = preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $pCoordinate);
2438
2439
        // Convert '1:3' to 'A1:XFD3'
2440 166
        $pCoordinate = preg_replace('/^(\d+):(\d+)$/', 'A${1}:XFD${2}', $pCoordinate);
2441
2442 166
        if (Coordinate::coordinateIsRange($pCoordinate)) {
2443 52
            [$first] = Coordinate::splitRange($pCoordinate);
2444 52
            $this->activeCell = $first[0];
2445
        } else {
2446 153
            $this->activeCell = $pCoordinate;
2447
        }
2448 166
        $this->selectedCells = $pCoordinate;
2449
2450 166
        return $this;
2451
    }
2452
2453
    /**
2454
     * Selected cell by using numeric cell coordinates.
2455
     *
2456
     * @param int $columnIndex Numeric column coordinate of the cell
2457
     * @param int $row Numeric row coordinate of the cell
2458
     *
2459
     * @throws Exception
2460
     *
2461
     * @return Worksheet
2462
     */
2463
    public function setSelectedCellByColumnAndRow($columnIndex, $row)
2464
    {
2465
        return $this->setSelectedCells(Coordinate::stringFromColumnIndex($columnIndex) . $row);
2466
    }
2467
2468
    /**
2469
     * Get right-to-left.
2470
     *
2471
     * @return bool
2472
     */
2473 92
    public function getRightToLeft()
2474
    {
2475 92
        return $this->rightToLeft;
2476
    }
2477
2478
    /**
2479
     * Set right-to-left.
2480
     *
2481
     * @param bool $value Right-to-left true/false
2482
     *
2483
     * @return Worksheet
2484
     */
2485 24
    public function setRightToLeft($value)
2486
    {
2487 24
        $this->rightToLeft = $value;
2488
2489 24
        return $this;
2490
    }
2491
2492
    /**
2493
     * Fill worksheet from values in array.
2494
     *
2495
     * @param array $source Source array
2496
     * @param mixed $nullValue Value in source array that stands for blank cell
2497
     * @param string $startCell Insert array starting from this cell address as the top left coordinate
2498
     * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array
2499
     *
2500
     * @throws Exception
2501
     *
2502
     * @return Worksheet
2503
     */
2504 63
    public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false)
2505
    {
2506
        //    Convert a 1-D array to 2-D (for ease of looping)
2507 63
        if (!is_array(end($source))) {
2508 3
            $source = [$source];
2509
        }
2510
2511
        // start coordinate
2512 63
        [$startColumn, $startRow] = Coordinate::coordinateFromString($startCell);
2513
2514
        // Loop through $source
2515 63
        foreach ($source as $rowData) {
2516 63
            $currentColumn = $startColumn;
2517 63
            foreach ($rowData as $cellValue) {
2518 63
                if ($strictNullComparison) {
2519 3
                    if ($cellValue !== $nullValue) {
2520
                        // Set cell value
2521 3
                        $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2522
                    }
2523
                } else {
2524 60
                    if ($cellValue != $nullValue) {
2525
                        // Set cell value
2526 57
                        $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2527
                    }
2528
                }
2529 63
                ++$currentColumn;
2530
            }
2531 63
            ++$startRow;
2532
        }
2533
2534 63
        return $this;
2535
    }
2536
2537
    /**
2538
     * Create array from a range of cells.
2539
     *
2540
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
2541
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2542
     * @param bool $calculateFormulas Should formulas be calculated?
2543
     * @param bool $formatData Should formatting be applied to cell values?
2544
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2545
     *                               True - Return rows and columns indexed by their actual row and column IDs
2546
     *
2547
     * @return array
2548
     */
2549 33
    public function rangeToArray($pRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2550
    {
2551
        // Returnvalue
2552 33
        $returnValue = [];
2553
        //    Identify the range that we need to extract from the worksheet
2554 33
        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange);
2555 33
        $minCol = Coordinate::stringFromColumnIndex($rangeStart[0]);
2556 33
        $minRow = $rangeStart[1];
2557 33
        $maxCol = Coordinate::stringFromColumnIndex($rangeEnd[0]);
2558 33
        $maxRow = $rangeEnd[1];
2559
2560 33
        ++$maxCol;
2561
        // Loop through rows
2562 33
        $r = -1;
2563 33
        for ($row = $minRow; $row <= $maxRow; ++$row) {
2564 33
            $rRef = $returnCellRef ? $row : ++$r;
2565 33
            $c = -1;
2566
            // Loop through columns in the current row
2567 33
            for ($col = $minCol; $col != $maxCol; ++$col) {
2568 33
                $cRef = $returnCellRef ? $col : ++$c;
2569
                //    Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
2570
                //        so we test and retrieve directly against cellCollection
2571 33
                if ($this->cellCollection->has($col . $row)) {
2572
                    // Cell exists
2573 33
                    $cell = $this->cellCollection->get($col . $row);
2574 33
                    if ($cell->getValue() !== null) {
2575 33
                        if ($cell->getValue() instanceof RichText) {
2576 2
                            $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
2577
                        } else {
2578 33
                            if ($calculateFormulas) {
2579 32
                                $returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
2580
                            } else {
2581 2
                                $returnValue[$rRef][$cRef] = $cell->getValue();
2582
                            }
2583
                        }
2584
2585 33
                        if ($formatData) {
2586 32
                            $style = $this->parent->getCellXfByIndex($cell->getXfIndex());
2587 32
                            $returnValue[$rRef][$cRef] = NumberFormat::toFormattedString(
2588 32
                                $returnValue[$rRef][$cRef],
2589 33
                                ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL
2590
                            );
2591
                        }
2592
                    } else {
2593
                        // Cell holds a NULL
2594 33
                        $returnValue[$rRef][$cRef] = $nullValue;
2595
                    }
2596
                } else {
2597
                    // Cell doesn't exist
2598 13
                    $returnValue[$rRef][$cRef] = $nullValue;
2599
                }
2600
            }
2601
        }
2602
2603
        // Return
2604 33
        return $returnValue;
2605
    }
2606
2607
    /**
2608
     * Create array from a range of cells.
2609
     *
2610
     * @param string $pNamedRange Name of the Named Range
2611
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2612
     * @param bool $calculateFormulas Should formulas be calculated?
2613
     * @param bool $formatData Should formatting be applied to cell values?
2614
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2615
     *                                True - Return rows and columns indexed by their actual row and column IDs
2616
     *
2617
     * @throws Exception
2618
     *
2619
     * @return array
2620
     */
2621
    public function namedRangeToArray($pNamedRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2622
    {
2623
        $namedRange = NamedRange::resolveRange($pNamedRange, $this);
2624
        if ($namedRange !== null) {
2625
            $pWorkSheet = $namedRange->getWorksheet();
2626
            $pCellRange = $namedRange->getRange();
2627
2628
            return $pWorkSheet->rangeToArray($pCellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2629
        }
2630
2631
        throw new Exception('Named Range ' . $pNamedRange . ' does not exist.');
2632
    }
2633
2634
    /**
2635
     * Create array from worksheet.
2636
     *
2637
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2638
     * @param bool $calculateFormulas Should formulas be calculated?
2639
     * @param bool $formatData Should formatting be applied to cell values?
2640
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2641
     *                               True - Return rows and columns indexed by their actual row and column IDs
2642
     *
2643
     * @return array
2644
     */
2645 20
    public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2646
    {
2647
        // Garbage collect...
2648 20
        $this->garbageCollect();
2649
2650
        //    Identify the range that we need to extract from the worksheet
2651 20
        $maxCol = $this->getHighestColumn();
2652 20
        $maxRow = $this->getHighestRow();
2653
2654
        // Return
2655 20
        return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2656
    }
2657
2658
    /**
2659
     * Get row iterator.
2660
     *
2661
     * @param int $startRow The row number at which to start iterating
2662
     * @param int $endRow The row number at which to stop iterating
2663
     *
2664
     * @return RowIterator
2665
     */
2666 9
    public function getRowIterator($startRow = 1, $endRow = null)
2667
    {
2668 9
        return new RowIterator($this, $startRow, $endRow);
2669
    }
2670
2671
    /**
2672
     * Get column iterator.
2673
     *
2674
     * @param string $startColumn The column address at which to start iterating
2675
     * @param string $endColumn The column address at which to stop iterating
2676
     *
2677
     * @return ColumnIterator
2678
     */
2679
    public function getColumnIterator($startColumn = 'A', $endColumn = null)
2680
    {
2681
        return new ColumnIterator($this, $startColumn, $endColumn);
2682
    }
2683
2684
    /**
2685
     * Run PhpSpreadsheet garbage collector.
2686
     *
2687
     * @return Worksheet
2688
     */
2689 129
    public function garbageCollect()
2690
    {
2691
        // Flush cache
2692 129
        $this->cellCollection->get('A1');
2693
2694
        // Lookup highest column and highest row if cells are cleaned
2695 129
        $colRow = $this->cellCollection->getHighestRowAndColumn();
2696 129
        $highestRow = $colRow['row'];
2697 129
        $highestColumn = Coordinate::columnIndexFromString($colRow['column']);
2698
2699
        // Loop through column dimensions
2700 129
        foreach ($this->columnDimensions as $dimension) {
2701 35
            $highestColumn = max($highestColumn, Coordinate::columnIndexFromString($dimension->getColumnIndex()));
2702
        }
2703
2704
        // Loop through row dimensions
2705 129
        foreach ($this->rowDimensions as $dimension) {
2706 49
            $highestRow = max($highestRow, $dimension->getRowIndex());
2707
        }
2708
2709
        // Cache values
2710 129
        if ($highestColumn < 1) {
2711
            $this->cachedHighestColumn = 'A';
2712
        } else {
2713 129
            $this->cachedHighestColumn = Coordinate::stringFromColumnIndex($highestColumn);
2714
        }
2715 129
        $this->cachedHighestRow = $highestRow;
2716
2717
        // Return
2718 129
        return $this;
2719
    }
2720
2721
    /**
2722
     * Get hash code.
2723
     *
2724
     * @return string Hash code
2725
     */
2726 179
    public function getHashCode()
2727
    {
2728 179
        if ($this->dirty) {
2729 179
            $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__);
2730 179
            $this->dirty = false;
2731
        }
2732
2733 179
        return $this->hash;
2734
    }
2735
2736
    /**
2737
     * Extract worksheet title from range.
2738
     *
2739
     * Example: extractSheetTitle("testSheet!A1") ==> 'A1'
2740
     * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1'];
2741
     *
2742
     * @param string $pRange Range to extract title from
2743
     * @param bool $returnRange Return range? (see example)
2744
     *
2745
     * @return mixed
2746
     */
2747 107
    public static function extractSheetTitle($pRange, $returnRange = false)
2748
    {
2749
        // Sheet title included?
2750 107
        if (($sep = strrpos($pRange, '!')) === false) {
2751 48
            return $returnRange ? ['', $pRange] : '';
2752
        }
2753
2754 81
        if ($returnRange) {
2755 81
            return [substr($pRange, 0, $sep), substr($pRange, $sep + 1)];
2756
        }
2757
2758 7
        return substr($pRange, $sep + 1);
2759
    }
2760
2761
    /**
2762
     * Get hyperlink.
2763
     *
2764
     * @param string $pCellCoordinate Cell coordinate to get hyperlink for, eg: 'A1'
2765
     *
2766
     * @return Hyperlink
2767
     */
2768 20
    public function getHyperlink($pCellCoordinate)
2769
    {
2770
        // return hyperlink if we already have one
2771 20
        if (isset($this->hyperlinkCollection[$pCellCoordinate])) {
2772 14
            return $this->hyperlinkCollection[$pCellCoordinate];
2773
        }
2774
2775
        // else create hyperlink
2776 20
        $this->hyperlinkCollection[$pCellCoordinate] = new Hyperlink();
2777
2778 20
        return $this->hyperlinkCollection[$pCellCoordinate];
2779
    }
2780
2781
    /**
2782
     * Set hyperlink.
2783
     *
2784
     * @param string $pCellCoordinate Cell coordinate to insert hyperlink, eg: 'A1'
2785
     * @param null|Hyperlink $pHyperlink
2786
     *
2787
     * @return Worksheet
2788
     */
2789 12
    public function setHyperlink($pCellCoordinate, Hyperlink $pHyperlink = null)
2790
    {
2791 12
        if ($pHyperlink === null) {
2792 12
            unset($this->hyperlinkCollection[$pCellCoordinate]);
2793
        } else {
2794 12
            $this->hyperlinkCollection[$pCellCoordinate] = $pHyperlink;
2795
        }
2796
2797 12
        return $this;
2798
    }
2799
2800
    /**
2801
     * Hyperlink at a specific coordinate exists?
2802
     *
2803
     * @param string $pCoordinate eg: 'A1'
2804
     *
2805
     * @return bool
2806
     */
2807 13
    public function hyperlinkExists($pCoordinate)
2808
    {
2809 13
        return isset($this->hyperlinkCollection[$pCoordinate]);
2810
    }
2811
2812
    /**
2813
     * Get collection of hyperlinks.
2814
     *
2815
     * @return Hyperlink[]
2816
     */
2817 100
    public function getHyperlinkCollection()
2818
    {
2819 100
        return $this->hyperlinkCollection;
2820
    }
2821
2822
    /**
2823
     * Get data validation.
2824
     *
2825
     * @param string $pCellCoordinate Cell coordinate to get data validation for, eg: 'A1'
2826
     *
2827
     * @return DataValidation
2828
     */
2829 5
    public function getDataValidation($pCellCoordinate)
2830
    {
2831
        // return data validation if we already have one
2832 5
        if (isset($this->dataValidationCollection[$pCellCoordinate])) {
2833 2
            return $this->dataValidationCollection[$pCellCoordinate];
2834
        }
2835
2836
        // else create data validation
2837 5
        $this->dataValidationCollection[$pCellCoordinate] = new DataValidation();
2838
2839 5
        return $this->dataValidationCollection[$pCellCoordinate];
2840
    }
2841
2842
    /**
2843
     * Set data validation.
2844
     *
2845
     * @param string $pCellCoordinate Cell coordinate to insert data validation, eg: 'A1'
2846
     * @param null|DataValidation $pDataValidation
2847
     *
2848
     * @return Worksheet
2849
     */
2850
    public function setDataValidation($pCellCoordinate, DataValidation $pDataValidation = null)
2851
    {
2852
        if ($pDataValidation === null) {
2853
            unset($this->dataValidationCollection[$pCellCoordinate]);
2854
        } else {
2855
            $this->dataValidationCollection[$pCellCoordinate] = $pDataValidation;
2856
        }
2857
2858
        return $this;
2859
    }
2860
2861
    /**
2862
     * Data validation at a specific coordinate exists?
2863
     *
2864
     * @param string $pCoordinate eg: 'A1'
2865
     *
2866
     * @return bool
2867
     */
2868 4
    public function dataValidationExists($pCoordinate)
2869
    {
2870 4
        return isset($this->dataValidationCollection[$pCoordinate]);
2871
    }
2872
2873
    /**
2874
     * Get collection of data validations.
2875
     *
2876
     * @return DataValidation[]
2877
     */
2878 100
    public function getDataValidationCollection()
2879
    {
2880 100
        return $this->dataValidationCollection;
2881
    }
2882
2883
    /**
2884
     * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet.
2885
     *
2886
     * @param string $range
2887
     *
2888
     * @return string Adjusted range value
2889
     */
2890 1
    public function shrinkRangeToFit($range)
2891
    {
2892 1
        $maxCol = $this->getHighestColumn();
2893 1
        $maxRow = $this->getHighestRow();
2894 1
        $maxCol = Coordinate::columnIndexFromString($maxCol);
2895
2896 1
        $rangeBlocks = explode(' ', $range);
2897 1
        foreach ($rangeBlocks as &$rangeSet) {
2898 1
            $rangeBoundaries = Coordinate::getRangeBoundaries($rangeSet);
2899
2900 1
            if (Coordinate::columnIndexFromString($rangeBoundaries[0][0]) > $maxCol) {
2901
                $rangeBoundaries[0][0] = Coordinate::stringFromColumnIndex($maxCol);
2902
            }
2903 1
            if ($rangeBoundaries[0][1] > $maxRow) {
2904
                $rangeBoundaries[0][1] = $maxRow;
2905
            }
2906 1
            if (Coordinate::columnIndexFromString($rangeBoundaries[1][0]) > $maxCol) {
2907
                $rangeBoundaries[1][0] = Coordinate::stringFromColumnIndex($maxCol);
2908
            }
2909 1
            if ($rangeBoundaries[1][1] > $maxRow) {
2910
                $rangeBoundaries[1][1] = $maxRow;
2911
            }
2912 1
            $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1];
2913
        }
2914 1
        unset($rangeSet);
2915
2916 1
        return implode(' ', $rangeBlocks);
2917
    }
2918
2919
    /**
2920
     * Get tab color.
2921
     *
2922
     * @return Color
2923
     */
2924 11
    public function getTabColor()
2925
    {
2926 11
        if ($this->tabColor === null) {
2927 11
            $this->tabColor = new Color();
2928
        }
2929
2930 11
        return $this->tabColor;
2931
    }
2932
2933
    /**
2934
     * Reset tab color.
2935
     *
2936
     * @return Worksheet
2937
     */
2938
    public function resetTabColor()
2939
    {
2940
        $this->tabColor = null;
2941
        unset($this->tabColor);
2942
2943
        return $this;
2944
    }
2945
2946
    /**
2947
     * Tab color set?
2948
     *
2949
     * @return bool
2950
     */
2951 101
    public function isTabColorSet()
2952
    {
2953 101
        return $this->tabColor !== null;
2954
    }
2955
2956
    /**
2957
     * Copy worksheet (!= clone!).
2958
     *
2959
     * @return Worksheet
2960
     */
2961
    public function copy()
2962
    {
2963
        return clone $this;
2964
    }
2965
2966
    /**
2967
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
2968
     */
2969 2
    public function __clone()
2970
    {
2971 2
        foreach ($this as $key => $val) {
2972 2
            if ($key == 'parent') {
2973 2
                continue;
2974
            }
2975
2976 2
            if (is_object($val) || (is_array($val))) {
2977 2
                if ($key == 'cellCollection') {
2978 2
                    $newCollection = $this->cellCollection->cloneCellCollection($this);
2979 2
                    $this->cellCollection = $newCollection;
2980 2
                } elseif ($key == 'drawingCollection') {
2981 2
                    $currentCollection = $this->drawingCollection;
2982 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...
2983 2
                    foreach ($currentCollection as $item) {
2984 1
                        if (is_object($item)) {
2985 1
                            $newDrawing = clone $item;
2986 1
                            $newDrawing->setWorksheet($this);
2987
                        }
2988
                    }
2989 2
                } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) {
2990 2
                    $newAutoFilter = clone $this->autoFilter;
2991 2
                    $this->autoFilter = $newAutoFilter;
2992 2
                    $this->autoFilter->setParent($this);
2993
                } else {
2994 2
                    $this->{$key} = unserialize(serialize($val));
2995
                }
2996
            }
2997
        }
2998 2
    }
2999
3000
    /**
3001
     * Define the code name of the sheet.
3002
     *
3003
     * @param string $pValue Same rule as Title minus space not allowed (but, like Excel, change
3004
     *                       silently space to underscore)
3005
     * @param bool $validate False to skip validation of new title. WARNING: This should only be set
3006
     *                       at parse time (by Readers), where titles can be assumed to be valid.
3007
     *
3008
     * @throws Exception
3009
     *
3010
     * @return Worksheet
3011
     */
3012 266
    public function setCodeName($pValue, $validate = true)
3013
    {
3014
        // Is this a 'rename' or not?
3015 266
        if ($this->getCodeName() == $pValue) {
3016
            return $this;
3017
        }
3018
3019 266
        if ($validate) {
3020 266
            $pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same
3021
3022
            // Syntax check
3023
            // throw an exception if not valid
3024 266
            self::checkSheetCodeName($pValue);
3025
3026
            // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
3027
3028 266
            if ($this->getParent()) {
3029
                // Is there already such sheet name?
3030 249
                if ($this->getParent()->sheetCodeNameExists($pValue)) {
3031
                    // Use name, but append with lowest possible integer
3032
3033 41
                    if (Shared\StringHelper::countCharacters($pValue) > 29) {
3034
                        $pValue = Shared\StringHelper::substring($pValue, 0, 29);
3035
                    }
3036 41
                    $i = 1;
3037 41
                    while ($this->getParent()->sheetCodeNameExists($pValue . '_' . $i)) {
3038 12
                        ++$i;
3039 12
                        if ($i == 10) {
3040
                            if (Shared\StringHelper::countCharacters($pValue) > 28) {
3041
                                $pValue = Shared\StringHelper::substring($pValue, 0, 28);
3042
                            }
3043 12
                        } elseif ($i == 100) {
3044
                            if (Shared\StringHelper::countCharacters($pValue) > 27) {
3045
                                $pValue = Shared\StringHelper::substring($pValue, 0, 27);
3046
                            }
3047
                        }
3048
                    }
3049
3050 41
                    $pValue .= '_' . $i; // ok, we have a valid name
3051
                }
3052
            }
3053
        }
3054
3055 266
        $this->codeName = $pValue;
3056
3057 266
        return $this;
3058
    }
3059
3060
    /**
3061
     * Return the code name of the sheet.
3062
     *
3063
     * @return null|string
3064
     */
3065 266
    public function getCodeName()
3066
    {
3067 266
        return $this->codeName;
3068
    }
3069
3070
    /**
3071
     * Sheet has a code name ?
3072
     *
3073
     * @return bool
3074
     */
3075 1
    public function hasCodeName()
3076
    {
3077 1
        return $this->codeName !== null;
3078
    }
3079
}
3080