Completed
Push — develop ( 782b4e...557e80 )
by Adrien
43:38
created

Worksheet::setSheetState()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
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\DataType;
9
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
10
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
11
use PhpOffice\PhpSpreadsheet\Chart\Chart;
12
use PhpOffice\PhpSpreadsheet\Collection\Cells;
13
use PhpOffice\PhpSpreadsheet\Collection\CellsFactory;
14
use PhpOffice\PhpSpreadsheet\Comment;
15
use PhpOffice\PhpSpreadsheet\Exception;
16
use PhpOffice\PhpSpreadsheet\IComparable;
17
use PhpOffice\PhpSpreadsheet\NamedRange;
18
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
19
use PhpOffice\PhpSpreadsheet\RichText\RichText;
20
use PhpOffice\PhpSpreadsheet\Shared;
21
use PhpOffice\PhpSpreadsheet\Spreadsheet;
22
use PhpOffice\PhpSpreadsheet\Style\Color;
23
use PhpOffice\PhpSpreadsheet\Style\Conditional;
24
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
25
use PhpOffice\PhpSpreadsheet\Style\Style;
26
27
class Worksheet implements IComparable
28
{
29
    // Break types
30
    const BREAK_NONE = 0;
31
    const BREAK_ROW = 1;
32
    const BREAK_COLUMN = 2;
33
34
    // Sheet state
35
    const SHEETSTATE_VISIBLE = 'visible';
36
    const SHEETSTATE_HIDDEN = 'hidden';
37
    const SHEETSTATE_VERYHIDDEN = 'veryHidden';
38
39
    /**
40
     * Invalid characters in sheet title.
41
     *
42
     * @var array
43
     */
44
    private static $invalidCharacters = ['*', ':', '/', '\\', '?', '[', ']'];
45
46
    /**
47
     * Parent spreadsheet.
48
     *
49
     * @var Spreadsheet
50
     */
51
    private $parent;
52
53
    /**
54
     * Collection of cells.
55
     *
56
     * @var Cells
57
     */
58
    private $cellCollection;
59
60
    /**
61
     * Collection of row dimensions.
62
     *
63
     * @var RowDimension[]
64
     */
65
    private $rowDimensions = [];
66
67
    /**
68
     * Default row dimension.
69
     *
70
     * @var RowDimension
71
     */
72
    private $defaultRowDimension;
73
74
    /**
75
     * Collection of column dimensions.
76
     *
77
     * @var ColumnDimension[]
78
     */
79
    private $columnDimensions = [];
80
81
    /**
82
     * Default column dimension.
83
     *
84
     * @var ColumnDimension
85
     */
86
    private $defaultColumnDimension;
87
88
    /**
89
     * Collection of drawings.
90
     *
91
     * @var BaseDrawing[]
92
     */
93
    private $drawingCollection;
94
95
    /**
96
     * Collection of Chart objects.
97
     *
98
     * @var Chart[]
99
     */
100
    private $chartCollection = [];
101
102
    /**
103
     * Worksheet title.
104
     *
105
     * @var string
106
     */
107
    private $title;
108
109
    /**
110
     * Sheet state.
111
     *
112
     * @var string
113
     */
114
    private $sheetState;
115
116
    /**
117
     * Page setup.
118
     *
119
     * @var PageSetup
120
     */
121
    private $pageSetup;
122
123
    /**
124
     * Page margins.
125
     *
126
     * @var PageMargins
127
     */
128
    private $pageMargins;
129
130
    /**
131
     * Page header/footer.
132
     *
133
     * @var HeaderFooter
134
     */
135
    private $headerFooter;
136
137
    /**
138
     * Sheet view.
139
     *
140
     * @var SheetView
141
     */
142
    private $sheetView;
143
144
    /**
145
     * Protection.
146
     *
147
     * @var Protection
148
     */
149
    private $protection;
150
151
    /**
152
     * Collection of styles.
153
     *
154
     * @var Style[]
155
     */
156
    private $styles = [];
157
158
    /**
159
     * Conditional styles. Indexed by cell coordinate, e.g. 'A1'.
160
     *
161
     * @var array
162
     */
163
    private $conditionalStylesCollection = [];
164
165
    /**
166
     * Is the current cell collection sorted already?
167
     *
168
     * @var bool
169
     */
170
    private $cellCollectionIsSorted = false;
171
172
    /**
173
     * Collection of breaks.
174
     *
175
     * @var array
176
     */
177
    private $breaks = [];
178
179
    /**
180
     * Collection of merged cell ranges.
181
     *
182
     * @var array
183
     */
184
    private $mergeCells = [];
185
186
    /**
187
     * Collection of protected cell ranges.
188
     *
189
     * @var array
190
     */
191
    private $protectedCells = [];
192
193
    /**
194
     * Autofilter Range and selection.
195
     *
196
     * @var AutoFilter
197
     */
198
    private $autoFilter;
199
200
    /**
201
     * Freeze pane.
202
     *
203
     * @var string
204
     */
205
    private $freezePane = '';
206
207
    /**
208
     * Show gridlines?
209
     *
210
     * @var bool
211
     */
212
    private $showGridlines = true;
213
214
    /**
215
     * Print gridlines?
216
     *
217
     * @var bool
218
     */
219
    private $printGridlines = false;
220
221
    /**
222
     * Show row and column headers?
223
     *
224
     * @var bool
225
     */
226
    private $showRowColHeaders = true;
227
228
    /**
229
     * Show summary below? (Row/Column outline).
230
     *
231
     * @var bool
232
     */
233
    private $showSummaryBelow = true;
234
235
    /**
236
     * Show summary right? (Row/Column outline).
237
     *
238
     * @var bool
239
     */
240
    private $showSummaryRight = true;
241
242
    /**
243
     * Collection of comments.
244
     *
245
     * @var Comment[]
246
     */
247
    private $comments = [];
248
249
    /**
250
     * Active cell. (Only one!).
251
     *
252
     * @var string
253
     */
254
    private $activeCell = 'A1';
255
256
    /**
257
     * Selected cells.
258
     *
259
     * @var string
260
     */
261
    private $selectedCells = 'A1';
262
263
    /**
264
     * Cached highest column.
265
     *
266
     * @var string
267
     */
268
    private $cachedHighestColumn = 'A';
269
270
    /**
271
     * Cached highest row.
272
     *
273
     * @var int
274
     */
275
    private $cachedHighestRow = 1;
276
277
    /**
278
     * Right-to-left?
279
     *
280
     * @var bool
281
     */
282
    private $rightToLeft = false;
283
284
    /**
285
     * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'.
286
     *
287
     * @var array
288
     */
289
    private $hyperlinkCollection = [];
290
291
    /**
292
     * Data validation objects. Indexed by cell coordinate, e.g. 'A1'.
293
     *
294
     * @var array
295
     */
296
    private $dataValidationCollection = [];
297
298
    /**
299
     * Tab color.
300
     *
301
     * @var Color
302
     */
303
    private $tabColor;
304
305
    /**
306
     * Dirty flag.
307
     *
308
     * @var bool
309
     */
310
    private $dirty = true;
311
312
    /**
313
     * Hash.
314
     *
315
     * @var string
316
     */
317
    private $hash;
318
319
    /**
320
     * CodeName.
321
     *
322
     * @var string
323
     */
324
    private $codeName;
325
326
    /**
327
     * Create a new worksheet.
328
     *
329
     * @param Spreadsheet $parent
330
     * @param string $pTitle
331
     */
332 150
    public function __construct(Spreadsheet $parent = null, $pTitle = 'Worksheet')
333
    {
334
        // Set parent and title
335 150
        $this->parent = $parent;
336 150
        $this->setTitle($pTitle, false);
337
        // setTitle can change $pTitle
338 150
        $this->setCodeName($this->getTitle());
339 150
        $this->setSheetState(self::SHEETSTATE_VISIBLE);
340
341 150
        $this->cellCollection = CellsFactory::getInstance($this);
342
        // Set page setup
343 150
        $this->pageSetup = new PageSetup();
344
        // Set page margins
345 150
        $this->pageMargins = new PageMargins();
346
        // Set page header/footer
347 150
        $this->headerFooter = new HeaderFooter();
348
        // Set sheet view
349 150
        $this->sheetView = new SheetView();
350
        // Drawing collection
351 150
        $this->drawingCollection = new \ArrayObject();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \ArrayObject() of type object<ArrayObject> is incompatible with the declared type array<integer,object<Php...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...
352
        // Chart collection
353 150
        $this->chartCollection = new \ArrayObject();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \ArrayObject() of type object<ArrayObject> is incompatible with the declared type array<integer,object<Php...readsheet\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...
354
        // Protection
355 150
        $this->protection = new Protection();
356
        // Default row dimension
357 150
        $this->defaultRowDimension = new RowDimension(null);
358
        // Default column dimension
359 150
        $this->defaultColumnDimension = new ColumnDimension(null);
360 150
        $this->autoFilter = new AutoFilter(null, $this);
361 150
    }
362
363
    /**
364
     * Disconnect all cells from this Worksheet object,
365
     * typically so that the worksheet object can be unset.
366
     */
367 2
    public function disconnectCells()
368
    {
369 2
        if ($this->cellCollection !== null) {
370 2
            $this->cellCollection->unsetWorksheetCells();
371 2
            $this->cellCollection = null;
372
        }
373
        //    detach ourself from the workbook, so that it can then delete this worksheet successfully
374 2
        $this->parent = null;
375 2
    }
376
377
    /**
378
     * Code to execute when this worksheet is unset().
379
     */
380 2
    public function __destruct()
381
    {
382 2
        Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title);
383
384 2
        $this->disconnectCells();
385 2
    }
386
387
    /**
388
     * Return the cell collection.
389
     *
390
     * @return Cells
391
     */
392 114
    public function getCellCollection()
393
    {
394 114
        return $this->cellCollection;
395
    }
396
397
    /**
398
     * Get array of invalid characters for sheet title.
399
     *
400
     * @return array
401
     */
402
    public static function getInvalidCharacters()
403
    {
404
        return self::$invalidCharacters;
405
    }
406
407
    /**
408
     * Check sheet code name for valid Excel syntax.
409
     *
410
     * @param string $pValue The string to check
411
     *
412
     * @throws Exception
413
     *
414
     * @return string The valid string
415
     */
416 150
    private static function checkSheetCodeName($pValue)
417
    {
418 150
        $CharCount = Shared\StringHelper::countCharacters($pValue);
419 150
        if ($CharCount == 0) {
420
            throw new Exception('Sheet code name cannot be empty.');
421
        }
422
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ] and  first and last characters cannot be a "'"
423 150
        if ((str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) ||
424 150
            (Shared\StringHelper::substring($pValue, -1, 1) == '\'') ||
425 150
            (Shared\StringHelper::substring($pValue, 0, 1) == '\'')) {
426 1
            throw new Exception('Invalid character found in sheet code name');
427
        }
428
429
        // Maximum 31 characters allowed for sheet title
430 150
        if ($CharCount > 31) {
431 1
            throw new Exception('Maximum 31 characters allowed in sheet code name.');
432
        }
433
434 150
        return $pValue;
435
    }
436
437
    /**
438
     * Check sheet title for valid Excel syntax.
439
     *
440
     * @param string $pValue The string to check
441
     *
442
     * @throws Exception
443
     *
444
     * @return string The valid string
445
     */
446 150
    private static function checkSheetTitle($pValue)
447
    {
448
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ]
449 150
        if (str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) {
450 1
            throw new Exception('Invalid character found in sheet title');
451
        }
452
453
        // Maximum 31 characters allowed for sheet title
454 150
        if (Shared\StringHelper::countCharacters($pValue) > 31) {
455 1
            throw new Exception('Maximum 31 characters allowed in sheet title.');
456
        }
457
458 150
        return $pValue;
459
    }
460
461
    /**
462
     * Get a sorted list of all cell coordinates currently held in the collection by row and column.
463
     *
464
     * @param bool $sorted Also sort the cell collection?
465
     *
466
     * @return string[]
467
     */
468 68
    public function getCoordinates($sorted = true)
469
    {
470 68
        if ($this->cellCollection == null) {
471
            return [];
472
        }
473
474 68
        if ($sorted) {
475 63
            return $this->cellCollection->getSortedCoordinates();
476
        }
477
478 67
        return $this->cellCollection->getCoordinates();
479
    }
480
481
    /**
482
     * Get collection of row dimensions.
483
     *
484
     * @return RowDimension[]
485
     */
486 62
    public function getRowDimensions()
487
    {
488 62
        return $this->rowDimensions;
489
    }
490
491
    /**
492
     * Get default row dimension.
493
     *
494
     * @return RowDimension
495
     */
496 77
    public function getDefaultRowDimension()
497
    {
498 77
        return $this->defaultRowDimension;
499
    }
500
501
    /**
502
     * Get collection of column dimensions.
503
     *
504
     * @return ColumnDimension[]
505
     */
506 62
    public function getColumnDimensions()
507
    {
508 62
        return $this->columnDimensions;
509
    }
510
511
    /**
512
     * Get default column dimension.
513
     *
514
     * @return ColumnDimension
515
     */
516 59
    public function getDefaultColumnDimension()
517
    {
518 59
        return $this->defaultColumnDimension;
519
    }
520
521
    /**
522
     * Get collection of drawings.
523
     *
524
     * @return BaseDrawing[]
525
     */
526 63
    public function getDrawingCollection()
527
    {
528 63
        return $this->drawingCollection;
529
    }
530
531
    /**
532
     * Get collection of charts.
533
     *
534
     * @return Chart[]
535
     */
536 14
    public function getChartCollection()
537
    {
538 14
        return $this->chartCollection;
539
    }
540
541
    /**
542
     * Add chart.
543
     *
544
     * @param Chart $pChart
545
     * @param null|int $iChartIndex Index where chart should go (0,1,..., or null for last)
546
     *
547
     * @return Chart
548
     */
549 15
    public function addChart(Chart $pChart, $iChartIndex = null)
550
    {
551 15
        $pChart->setWorksheet($this);
552 15
        if ($iChartIndex === null) {
553 15
            $this->chartCollection[] = $pChart;
554
        } else {
555
            // Insert the chart at the requested index
556
            array_splice($this->chartCollection, $iChartIndex, 0, [$pChart]);
557
        }
558
559 15
        return $pChart;
560
    }
561
562
    /**
563
     * Return the count of charts on this worksheet.
564
     *
565
     * @return int The number of charts
566
     */
567 14
    public function getChartCount()
568
    {
569 14
        return count($this->chartCollection);
570
    }
571
572
    /**
573
     * Get a chart by its index position.
574
     *
575
     * @param string $index Chart index position
576
     *
577
     * @throws Exception
578
     *
579
     * @return Chart|false
580
     */
581 13
    public function getChartByIndex($index)
582
    {
583 13
        $chartCount = count($this->chartCollection);
584 13
        if ($chartCount == 0) {
585
            return false;
586
        }
587 13
        if ($index === null) {
588
            $index = --$chartCount;
589
        }
590 13
        if (!isset($this->chartCollection[$index])) {
591
            return false;
592
        }
593
594 13
        return $this->chartCollection[$index];
595
    }
596
597
    /**
598
     * Return an array of the names of charts on this worksheet.
599
     *
600
     * @throws Exception
601
     *
602
     * @return string[] The names of charts
603
     */
604 2
    public function getChartNames()
605
    {
606 2
        $chartNames = [];
607 2
        foreach ($this->chartCollection as $chart) {
608 2
            $chartNames[] = $chart->getName();
609
        }
610
611 2
        return $chartNames;
612
    }
613
614
    /**
615
     * Get a chart by name.
616
     *
617
     * @param string $chartName Chart name
618
     *
619
     * @throws Exception
620
     *
621
     * @return Chart|false
622
     */
623 2
    public function getChartByName($chartName)
624
    {
625 2
        $chartCount = count($this->chartCollection);
626 2
        if ($chartCount == 0) {
627
            return false;
628
        }
629 2
        foreach ($this->chartCollection as $index => $chart) {
630 2
            if ($chart->getName() == $chartName) {
631 2
                return $this->chartCollection[$index];
632
            }
633
        }
634
635
        return false;
636
    }
637
638
    /**
639
     * Refresh column dimensions.
640
     *
641
     * @return Worksheet
642
     */
643 15
    public function refreshColumnDimensions()
644
    {
645 15
        $currentColumnDimensions = $this->getColumnDimensions();
646 15
        $newColumnDimensions = [];
647
648 15
        foreach ($currentColumnDimensions as $objColumnDimension) {
649 15
            $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension;
650
        }
651
652 15
        $this->columnDimensions = $newColumnDimensions;
653
654 15
        return $this;
655
    }
656
657
    /**
658
     * Refresh row dimensions.
659
     *
660
     * @return Worksheet
661
     */
662 2
    public function refreshRowDimensions()
663
    {
664 2
        $currentRowDimensions = $this->getRowDimensions();
665 2
        $newRowDimensions = [];
666
667 2
        foreach ($currentRowDimensions as $objRowDimension) {
668 2
            $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension;
669
        }
670
671 2
        $this->rowDimensions = $newRowDimensions;
672
673 2
        return $this;
674
    }
675
676
    /**
677
     * Calculate worksheet dimension.
678
     *
679
     * @return string String containing the dimension of this worksheet
680
     */
681 61
    public function calculateWorksheetDimension()
682
    {
683
        // Return
684 61
        return 'A1' . ':' . $this->getHighestColumn() . $this->getHighestRow();
685
    }
686
687
    /**
688
     * Calculate worksheet data dimension.
689
     *
690
     * @return string String containing the dimension of this worksheet that actually contain data
691
     */
692
    public function calculateWorksheetDataDimension()
693
    {
694
        // Return
695
        return 'A1' . ':' . $this->getHighestDataColumn() . $this->getHighestDataRow();
696
    }
697
698
    /**
699
     * Calculate widths for auto-size columns.
700
     *
701
     * @return Worksheet;
0 ignored issues
show
Documentation introduced by
The doc-type Worksheet; could not be parsed: Expected "|" or "end of type", but got ";" at position 9. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
702
     */
703 47
    public function calculateColumnWidths()
704
    {
705
        // initialize $autoSizes array
706 47
        $autoSizes = [];
707 47
        foreach ($this->getColumnDimensions() as $colDimension) {
708 26
            if ($colDimension->getAutoSize()) {
709 26
                $autoSizes[$colDimension->getColumnIndex()] = -1;
710
            }
711
        }
712
713
        // There is only something to do if there are some auto-size columns
714 47
        if (!empty($autoSizes)) {
715
            // build list of cells references that participate in a merge
716 14
            $isMergeCell = [];
717 14
            foreach ($this->getMergeCells() as $cells) {
718 11
                foreach (Cell::extractAllCellReferencesInRange($cells) as $cellReference) {
0 ignored issues
show
Documentation introduced by
$cells is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
719 11
                    $isMergeCell[$cellReference] = true;
720
                }
721
            }
722
723
            // loop through all cells in the worksheet
724 14
            foreach ($this->getCoordinates(false) as $coordinate) {
725 14
                $cell = $this->getCell($coordinate, false);
726 14
                if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
727
                    //Determine if cell is in merge range
728 14
                    $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
729
730
                    //By default merged cells should be ignored
731 14
                    $isMergedButProceed = false;
732
733
                    //The only exception is if it's a merge range value cell of a 'vertical' randge (1 column wide)
734 14
                    if ($isMerged && $cell->isMergeRangeValueCell()) {
735
                        $range = $cell->getMergeRange();
736
                        $rangeBoundaries = Cell::rangeDimension($range);
0 ignored issues
show
Documentation introduced by
$range is of type array|false, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
737
                        if ($rangeBoundaries[0] == 1) {
738
                            $isMergedButProceed = true;
739
                        }
740
                    }
741
742
                    // Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range
743 14
                    if (!$isMerged || $isMergedButProceed) {
744
                        // Calculated value
745
                        // To formatted string
746 14
                        $cellValue = NumberFormat::toFormattedString(
747 14
                            $cell->getCalculatedValue(),
748 14
                            $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
749
                        );
750
751 14
                        $autoSizes[$this->cellCollection->getCurrentColumn()] = max(
752 14
                            (float) $autoSizes[$this->cellCollection->getCurrentColumn()],
753 14
                            (float) Shared\Font::calculateColumnWidth(
754 14
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
755 14
                                $cellValue,
756 14
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
757 14
                                $this->getParent()->getDefaultStyle()->getFont()
758
                            )
759
                        );
760
                    }
761
                }
762
            }
763
764
            // adjust column widths
765 14
            foreach ($autoSizes as $columnIndex => $width) {
766 14
                if ($width == -1) {
767
                    $width = $this->getDefaultColumnDimension()->getWidth();
768
                }
769 14
                $this->getColumnDimension($columnIndex)->setWidth($width);
770
            }
771
        }
772
773 47
        return $this;
774
    }
775
776
    /**
777
     * Get parent.
778
     *
779
     * @return Spreadsheet
780
     */
781 150
    public function getParent()
782
    {
783 150
        return $this->parent;
784
    }
785
786
    /**
787
     * Re-bind parent.
788
     *
789
     * @param Spreadsheet $parent
790
     *
791
     * @return Worksheet
792
     */
793 1
    public function rebindParent(Spreadsheet $parent)
794
    {
795 1
        if ($this->parent !== null) {
796 1
            $namedRanges = $this->parent->getNamedRanges();
797 1
            foreach ($namedRanges as $namedRange) {
798
                $parent->addNamedRange($namedRange);
799
            }
800
801 1
            $this->parent->removeSheetByIndex(
802 1
                $this->parent->getIndex($this)
803
            );
804
        }
805 1
        $this->parent = $parent;
806
807 1
        return $this;
808
    }
809
810
    /**
811
     * Get title.
812
     *
813
     * @return string
814
     */
815 150
    public function getTitle()
816
    {
817 150
        return $this->title;
818
    }
819
820
    /**
821
     * Set title.
822
     *
823
     * @param string $pValue String containing the dimension of this worksheet
824
     * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should
825
     *            be updated to reflect the new sheet name.
826
     *          This should be left as the default true, unless you are
827
     *          certain that no formula cells on any worksheet contain
828
     *          references to this worksheet
829
     * @param bool $validate False to skip validation of new title. WARNING: This should only be set
830
     *                       at parse time (by Readers), where titles can be assumed to be valid.
831
     *
832
     * @return Worksheet
833
     */
834 150
    public function setTitle($pValue, $updateFormulaCellReferences = true, $validate = true)
835
    {
836
        // Is this a 'rename' or not?
837 150
        if ($this->getTitle() == $pValue) {
838 7
            return $this;
839
        }
840
841
        // Old title
842 150
        $oldTitle = $this->getTitle();
843
844 150
        if ($validate) {
845
            // Syntax check
846 150
            self::checkSheetTitle($pValue);
847
848 150
            if ($this->parent) {
849
                // Is there already such sheet name?
850 134
                if ($this->parent->sheetNameExists($pValue)) {
851
                    // Use name, but append with lowest possible integer
852
853 5
                    if (Shared\StringHelper::countCharacters($pValue) > 29) {
854
                        $pValue = Shared\StringHelper::substring($pValue, 0, 29);
855
                    }
856 5
                    $i = 1;
857 5 View Code Duplication
                    while ($this->parent->sheetNameExists($pValue . ' ' . $i)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
858 2
                        ++$i;
859 2
                        if ($i == 10) {
860
                            if (Shared\StringHelper::countCharacters($pValue) > 28) {
861
                                $pValue = Shared\StringHelper::substring($pValue, 0, 28);
862
                            }
863 2
                        } elseif ($i == 100) {
864
                            if (Shared\StringHelper::countCharacters($pValue) > 27) {
865
                                $pValue = Shared\StringHelper::substring($pValue, 0, 27);
866
                            }
867
                        }
868
                    }
869
870 5
                    $pValue .= " $i";
871
                }
872
            }
873
        }
874
875
        // Set title
876 150
        $this->title = $pValue;
877 150
        $this->dirty = true;
878
879 150
        if ($this->parent && $this->parent->getCalculationEngine()) {
880
            // New title
881 134
            $newTitle = $this->getTitle();
882 134
            $this->parent->getCalculationEngine()
883 134
                ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
884 134
            if ($updateFormulaCellReferences) {
885 31
                ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
886
            }
887
        }
888
889 150
        return $this;
890
    }
891
892
    /**
893
     * Get sheet state.
894
     *
895
     * @return string Sheet state (visible, hidden, veryHidden)
896
     */
897 58
    public function getSheetState()
898
    {
899 58
        return $this->sheetState;
900
    }
901
902
    /**
903
     * Set sheet state.
904
     *
905
     * @param string $value Sheet state (visible, hidden, veryHidden)
906
     *
907
     * @return Worksheet
908
     */
909 150
    public function setSheetState($value)
910
    {
911 150
        $this->sheetState = $value;
912
913 150
        return $this;
914
    }
915
916
    /**
917
     * Get page setup.
918
     *
919
     * @return PageSetup
920
     */
921 78
    public function getPageSetup()
922
    {
923 78
        return $this->pageSetup;
924
    }
925
926
    /**
927
     * Set page setup.
928
     *
929
     * @param PageSetup $pValue
930
     *
931
     * @return Worksheet
932
     */
933
    public function setPageSetup(PageSetup $pValue)
934
    {
935
        $this->pageSetup = $pValue;
936
937
        return $this;
938
    }
939
940
    /**
941
     * Get page margins.
942
     *
943
     * @return PageMargins
944
     */
945 78
    public function getPageMargins()
946
    {
947 78
        return $this->pageMargins;
948
    }
949
950
    /**
951
     * Set page margins.
952
     *
953
     * @param PageMargins $pValue
954
     *
955
     * @return Worksheet
956
     */
957
    public function setPageMargins(PageMargins $pValue)
958
    {
959
        $this->pageMargins = $pValue;
960
961
        return $this;
962
    }
963
964
    /**
965
     * Get page header/footer.
966
     *
967
     * @return HeaderFooter
968
     */
969 64
    public function getHeaderFooter()
970
    {
971 64
        return $this->headerFooter;
972
    }
973
974
    /**
975
     * Set page header/footer.
976
     *
977
     * @param HeaderFooter $pValue
978
     *
979
     * @return Worksheet
980
     */
981
    public function setHeaderFooter(HeaderFooter $pValue)
982
    {
983
        $this->headerFooter = $pValue;
984
985
        return $this;
986
    }
987
988
    /**
989
     * Get sheet view.
990
     *
991
     * @return SheetView
992
     */
993 72
    public function getSheetView()
994
    {
995 72
        return $this->sheetView;
996
    }
997
998
    /**
999
     * Set sheet view.
1000
     *
1001
     * @param SheetView $pValue
1002
     *
1003
     * @return Worksheet
1004
     */
1005
    public function setSheetView(SheetView $pValue)
1006
    {
1007
        $this->sheetView = $pValue;
1008
1009
        return $this;
1010
    }
1011
1012
    /**
1013
     * Get Protection.
1014
     *
1015
     * @return Protection
1016
     */
1017 65
    public function getProtection()
1018
    {
1019 65
        return $this->protection;
1020
    }
1021
1022
    /**
1023
     * Set Protection.
1024
     *
1025
     * @param Protection $pValue
1026
     *
1027
     * @return Worksheet
1028
     */
1029
    public function setProtection(Protection $pValue)
1030
    {
1031
        $this->protection = $pValue;
1032
        $this->dirty = true;
1033
1034
        return $this;
1035
    }
1036
1037
    /**
1038
     * Get highest worksheet column.
1039
     *
1040
     * @param string $row Return the data highest column for the specified row,
1041
     *                                     or the highest column of any row if no row number is passed
1042
     *
1043
     * @return string Highest column name
1044
     */
1045 78
    public function getHighestColumn($row = null)
1046
    {
1047 78
        if ($row == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $row of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1048 78
            return $this->cachedHighestColumn;
1049
        }
1050
1051
        return $this->getHighestDataColumn($row);
1052
    }
1053
1054
    /**
1055
     * Get highest worksheet column that contains data.
1056
     *
1057
     * @param string $row Return the highest data column for the specified row,
1058
     *                                     or the highest data column of any row if no row number is passed
1059
     *
1060
     * @return string Highest column name that contains data
1061
     */
1062 14
    public function getHighestDataColumn($row = null)
1063
    {
1064 14
        return $this->cellCollection->getHighestColumn($row);
1065
    }
1066
1067
    /**
1068
     * Get highest worksheet row.
1069
     *
1070
     * @param string $column Return the highest data row for the specified column,
1071
     *                                     or the highest row of any column if no column letter is passed
1072
     *
1073
     * @return int Highest row number
1074
     */
1075 78
    public function getHighestRow($column = null)
1076
    {
1077 78
        if ($column == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $column of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1078 78
            return $this->cachedHighestRow;
1079
        }
1080
1081
        return $this->getHighestDataRow($column);
1082
    }
1083
1084
    /**
1085
     * Get highest worksheet row that contains data.
1086
     *
1087
     * @param string $column Return the highest data row for the specified column,
1088
     *                                     or the highest data row of any column if no column letter is passed
1089
     *
1090
     * @return string Highest row number that contains data
1091
     */
1092 16
    public function getHighestDataRow($column = null)
1093
    {
1094 16
        return $this->cellCollection->getHighestRow($column);
1095
    }
1096
1097
    /**
1098
     * Get highest worksheet column and highest row that have cell records.
1099
     *
1100
     * @return array Highest column name and highest row number
1101
     */
1102
    public function getHighestRowAndColumn()
1103
    {
1104
        return $this->cellCollection->getHighestRowAndColumn();
1105
    }
1106
1107
    /**
1108
     * Set a cell value.
1109
     *
1110
     * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
1111
     * @param mixed $pValue Value of the cell
1112
     *
1113
     * @return Worksheet
1114
     */
1115 55
    public function setCellValue($pCoordinate, $pValue)
1116
    {
1117 55
        $this->getCell($pCoordinate)->setValue($pValue);
1118
1119 55
        return $this;
1120
    }
1121
1122
    /**
1123
     * Set a cell value by using numeric cell coordinates.
1124
     *
1125
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1126
     * @param int $pRow Numeric row coordinate of the cell
1127
     * @param mixed $pValue Value of the cell
1128
     *
1129
     * @return Worksheet
1130
     */
1131
    public function setCellValueByColumnAndRow($pColumn, $pRow, $pValue)
1132
    {
1133
        $this->getCellByColumnAndRow($pColumn, $pRow)->setValue($pValue);
1134
1135
        return $this;
1136
    }
1137
1138
    /**
1139
     * Set a cell value.
1140
     *
1141
     * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
1142
     * @param mixed $pValue Value of the cell
1143
     * @param string $pDataType Explicit data type, see DataType::TYPE_*
1144
     *
1145
     * @return Worksheet
1146
     */
1147 1
    public function setCellValueExplicit($pCoordinate, $pValue, $pDataType)
1148
    {
1149
        // Set value
1150 1
        $this->getCell($pCoordinate)->setValueExplicit($pValue, $pDataType);
1151
1152 1
        return $this;
1153
    }
1154
1155
    /**
1156
     * Set a cell value by using numeric cell coordinates.
1157
     *
1158
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1159
     * @param int $pRow Numeric row coordinate of the cell
1160
     * @param mixed $pValue Value of the cell
1161
     * @param string $pDataType Explicit data type, see DataType::TYPE_*
1162
     *
1163
     * @return Worksheet
1164
     */
1165
    public function setCellValueExplicitByColumnAndRow($pColumn, $pRow, $pValue, $pDataType)
1166
    {
1167
        $this->getCellByColumnAndRow($pColumn, $pRow)->setValueExplicit($pValue, $pDataType);
1168
1169
        return $this;
1170
    }
1171
1172
    /**
1173
     * Get cell at a specific coordinate.
1174
     *
1175
     * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
1176
     * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
1177
     *                                       already exist, or a null should be returned instead
1178
     *
1179
     * @throws Exception
1180
     *
1181
     * @return null|Cell Cell that was found/created or null
1182
     */
1183 113
    public function getCell($pCoordinate, $createIfNotExists = true)
1184
    {
1185
        // Check cell collection
1186 113
        if ($this->cellCollection->has(strtoupper($pCoordinate))) {
1187 103
            return $this->cellCollection->get($pCoordinate);
1188
        }
1189
1190
        // Worksheet reference?
1191 111 View Code Duplication
        if (strpos($pCoordinate, '!') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1192 1
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1193
1194 1
            return $this->parent->getSheetByName($worksheetReference[0])->getCell(strtoupper($worksheetReference[1]), $createIfNotExists);
1195
        }
1196
1197
        // Named range?
1198 111
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1199 111
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1200
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1201
            if ($namedRange !== null) {
1202
                $pCoordinate = $namedRange->getRange();
1203
1204
                return $namedRange->getWorksheet()->getCell($pCoordinate, $createIfNotExists);
1205
            }
1206
        }
1207
1208
        // Uppercase coordinate
1209 111
        $pCoordinate = strtoupper($pCoordinate);
1210
1211 111
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
1212
            throw new Exception('Cell coordinate can not be a range of cells.');
1213 111
        } elseif (strpos($pCoordinate, '$') !== false) {
1214
            throw new Exception('Cell coordinate must not be absolute.');
1215
        }
1216
1217
        // Create new cell object, if required
1218 111
        return $createIfNotExists ? $this->createNewCell($pCoordinate) : null;
1219
    }
1220
1221
    /**
1222
     * Get cell at a specific coordinate by using numeric cell coordinates.
1223
     *
1224
     * @param string $pColumn Numeric column coordinate of the cell
1225
     * @param string $pRow Numeric row coordinate of the cell
1226
     * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
1227
     *                                       already exist, or a null should be returned instead
1228
     *
1229
     * @return null|Cell Cell that was found/created or null
1230
     */
1231 47
    public function getCellByColumnAndRow($pColumn, $pRow, $createIfNotExists = true)
1232
    {
1233 47
        $columnLetter = Cell::stringFromColumnIndex($pColumn);
1234 47
        $coordinate = $columnLetter . $pRow;
1235
1236 47
        if ($this->cellCollection->has($coordinate)) {
1237 46
            return $this->cellCollection->get($coordinate);
1238
        }
1239
1240
        // Create new cell object, if required
1241 25
        return $createIfNotExists ? $this->createNewCell($coordinate) : null;
1242
    }
1243
1244
    /**
1245
     * Create a new cell at the specified coordinate.
1246
     *
1247
     * @param string $pCoordinate Coordinate of the cell
1248
     *
1249
     * @return Cell Cell that was created
1250
     */
1251 114
    private function createNewCell($pCoordinate)
1252
    {
1253 114
        $cell = new Cell(null, DataType::TYPE_NULL, $this);
1254 114
        $this->cellCollection->add($pCoordinate, $cell);
1255 114
        $this->cellCollectionIsSorted = false;
1256
1257
        // Coordinates
1258 114
        $aCoordinates = Cell::coordinateFromString($pCoordinate);
1259 114
        if (Cell::columnIndexFromString($this->cachedHighestColumn) < Cell::columnIndexFromString($aCoordinates[0])) {
1260 100
            $this->cachedHighestColumn = $aCoordinates[0];
1261
        }
1262 114
        $this->cachedHighestRow = max($this->cachedHighestRow, $aCoordinates[1]);
0 ignored issues
show
Documentation Bug introduced by
It seems like max($this->cachedHighestRow, $aCoordinates[1]) can also be of type string. However, the property $cachedHighestRow is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1263
1264
        // Cell needs appropriate xfIndex from dimensions records
1265
        //    but don't create dimension records if they don't already exist
1266 114
        $rowDimension = $this->getRowDimension($aCoordinates[1], false);
1267 114
        $columnDimension = $this->getColumnDimension($aCoordinates[0], false);
1268
1269 114
        if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) {
1270
            // then there is a row dimension with explicit style, assign it to the cell
1271 2
            $cell->setXfIndex($rowDimension->getXfIndex());
1272 114
        } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) {
1273
            // then there is a column dimension, assign it to the cell
1274
            $cell->setXfIndex($columnDimension->getXfIndex());
1275
        }
1276
1277 114
        return $cell;
1278
    }
1279
1280
    /**
1281
     * Does the cell at a specific coordinate exist?
1282
     *
1283
     * @param string $pCoordinate Coordinate of the cell eg: 'A1'
1284
     *
1285
     * @throws Exception
1286
     *
1287
     * @return bool
1288
     */
1289 71
    public function cellExists($pCoordinate)
1290
    {
1291
        // Worksheet reference?
1292 71 View Code Duplication
        if (strpos($pCoordinate, '!') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1293
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1294
1295
            return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1]));
1296
        }
1297
1298
        // Named range?
1299 71
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1300 71
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1301
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1302
            if ($namedRange !== null) {
1303
                $pCoordinate = $namedRange->getRange();
1304
                if ($this->getHashCode() != $namedRange->getWorksheet()->getHashCode()) {
1305
                    if (!$namedRange->getLocalOnly()) {
1306
                        return $namedRange->getWorksheet()->cellExists($pCoordinate);
1307
                    }
1308
1309
                    throw new Exception('Named range ' . $namedRange->getName() . ' is not accessible from within sheet ' . $this->getTitle());
1310
                }
1311
            } else {
1312
                return false;
1313
            }
1314
        }
1315
1316
        // Uppercase coordinate
1317 71
        $pCoordinate = strtoupper($pCoordinate);
1318
1319 71
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
1320
            throw new Exception('Cell coordinate can not be a range of cells.');
1321 71
        } elseif (strpos($pCoordinate, '$') !== false) {
1322
            throw new Exception('Cell coordinate must not be absolute.');
1323
        }
1324
1325
        // Cell exists?
1326 71
        return $this->cellCollection->has($pCoordinate);
1327
    }
1328
1329
    /**
1330
     * Cell at a specific coordinate by using numeric cell coordinates exists?
1331
     *
1332
     * @param string $pColumn Numeric column coordinate of the cell (A = 0)
1333
     * @param string $pRow Numeric row coordinate of the cell
1334
     *
1335
     * @return bool
1336
     */
1337 6
    public function cellExistsByColumnAndRow($pColumn, $pRow)
1338
    {
1339 6
        return $this->cellExists(Cell::stringFromColumnIndex($pColumn) . $pRow);
1340
    }
1341
1342
    /**
1343
     * Get row dimension at a specific row.
1344
     *
1345
     * @param int $pRow Numeric index of the row
1346
     * @param mixed $create
1347
     *
1348
     * @return RowDimension
1349
     */
1350 114
    public function getRowDimension($pRow, $create = true)
1351
    {
1352
        // Found
1353 114
        $found = null;
0 ignored issues
show
Unused Code introduced by
$found is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1354
1355
        // Get row dimension
1356 114
        if (!isset($this->rowDimensions[$pRow])) {
1357 114
            if (!$create) {
1358 100
                return null;
1359
            }
1360 73
            $this->rowDimensions[$pRow] = new RowDimension($pRow);
1361
1362 73
            $this->cachedHighestRow = max($this->cachedHighestRow, $pRow);
1363
        }
1364
1365 73
        return $this->rowDimensions[$pRow];
1366
    }
1367
1368
    /**
1369
     * Get column dimension at a specific column.
1370
     *
1371
     * @param string $pColumn String index of the column eg: 'A'
1372
     * @param mixed $create
1373
     *
1374
     * @return ColumnDimension
1375
     */
1376 114
    public function getColumnDimension($pColumn, $create = true)
1377
    {
1378
        // Uppercase coordinate
1379 114
        $pColumn = strtoupper($pColumn);
1380
1381
        // Fetch dimensions
1382 114
        if (!isset($this->columnDimensions[$pColumn])) {
1383 114
            if (!$create) {
1384 110
                return null;
1385
            }
1386 41
            $this->columnDimensions[$pColumn] = new ColumnDimension($pColumn);
1387
1388 41
            if (Cell::columnIndexFromString($this->cachedHighestColumn) < Cell::columnIndexFromString($pColumn)) {
1389 23
                $this->cachedHighestColumn = $pColumn;
1390
            }
1391
        }
1392
1393 41
        return $this->columnDimensions[$pColumn];
1394
    }
1395
1396
    /**
1397
     * Get column dimension at a specific column by using numeric cell coordinates.
1398
     *
1399
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1400
     *
1401
     * @return ColumnDimension
1402
     */
1403 12
    public function getColumnDimensionByColumn($pColumn)
1404
    {
1405 12
        return $this->getColumnDimension(Cell::stringFromColumnIndex($pColumn));
1406
    }
1407
1408
    /**
1409
     * Get styles.
1410
     *
1411
     * @return Style[]
1412
     */
1413
    public function getStyles()
1414
    {
1415
        return $this->styles;
1416
    }
1417
1418
    /**
1419
     * Get style for cell.
1420
     *
1421
     * @param string $pCellCoordinate Cell coordinate (or range) to get style for, eg: 'A1'
1422
     *
1423
     * @throws Exception
1424
     *
1425
     * @return Style
1426
     */
1427 46
    public function getStyle($pCellCoordinate)
1428
    {
1429
        // set this sheet as active
1430 46
        $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
1431
1432
        // set cell coordinate as active
1433 46
        $this->setSelectedCells(strtoupper($pCellCoordinate));
1434
1435 46
        return $this->parent->getCellXfSupervisor();
1436
    }
1437
1438
    /**
1439
     * Get conditional styles for a cell.
1440
     *
1441
     * @param string $pCoordinate eg: 'A1'
1442
     *
1443
     * @return Conditional[]
1444
     */
1445 2
    public function getConditionalStyles($pCoordinate)
1446
    {
1447 2
        $pCoordinate = strtoupper($pCoordinate);
1448 2
        if (!isset($this->conditionalStylesCollection[$pCoordinate])) {
1449 2
            $this->conditionalStylesCollection[$pCoordinate] = [];
1450
        }
1451
1452 2
        return $this->conditionalStylesCollection[$pCoordinate];
1453
    }
1454
1455
    /**
1456
     * Do conditional styles exist for this cell?
1457
     *
1458
     * @param string $pCoordinate eg: 'A1'
1459
     *
1460
     * @return bool
1461
     */
1462 13
    public function conditionalStylesExists($pCoordinate)
1463
    {
1464 13
        if (isset($this->conditionalStylesCollection[strtoupper($pCoordinate)])) {
1465
            return true;
1466
        }
1467
1468 13
        return false;
1469
    }
1470
1471
    /**
1472
     * Removes conditional styles for a cell.
1473
     *
1474
     * @param string $pCoordinate eg: 'A1'
1475
     *
1476
     * @return Worksheet
1477
     */
1478 14
    public function removeConditionalStyles($pCoordinate)
1479
    {
1480 14
        unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
1481
1482 14
        return $this;
1483
    }
1484
1485
    /**
1486
     * Get collection of conditional styles.
1487
     *
1488
     * @return array
1489
     */
1490 58
    public function getConditionalStylesCollection()
1491
    {
1492 58
        return $this->conditionalStylesCollection;
1493
    }
1494
1495
    /**
1496
     * Set conditional styles.
1497
     *
1498
     * @param string $pCoordinate eg: 'A1'
1499
     * @param $pValue Conditional[]
1500
     *
1501
     * @return Worksheet
1502
     */
1503 2
    public function setConditionalStyles($pCoordinate, $pValue)
1504
    {
1505 2
        $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue;
1506
1507 2
        return $this;
1508
    }
1509
1510
    /**
1511
     * Get style for cell by using numeric cell coordinates.
1512
     *
1513
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1514
     * @param int $pRow Numeric row coordinate of the cell
1515
     * @param int pColumn2 Numeric column coordinate of the range cell (A = 0)
1516
     * @param int pRow2 Numeric row coordinate of the range cell
1517
     * @param null|mixed $pColumn2
1518
     * @param null|mixed $pRow2
1519
     *
1520
     * @return Style
1521
     */
1522
    public function getStyleByColumnAndRow($pColumn, $pRow, $pColumn2 = null, $pRow2 = null)
1523
    {
1524
        if ($pColumn2 !== null && $pRow2 !== null) {
1525
            $cellRange = Cell::stringFromColumnIndex($pColumn) . $pRow . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1526
1527
            return $this->getStyle($cellRange);
1528
        }
1529
1530
        return $this->getStyle(Cell::stringFromColumnIndex($pColumn) . $pRow);
1531
    }
1532
1533
    /**
1534
     * Duplicate cell style to a range of cells.
1535
     *
1536
     * Please note that this will overwrite existing cell styles for cells in range!
1537
     *
1538
     * @param Style $pCellStyle Cell style to duplicate
1539
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1540
     *
1541
     * @throws Exception
1542
     *
1543
     * @return Worksheet
1544
     */
1545 2
    public function duplicateStyle(Style $pCellStyle, $pRange)
1546
    {
1547
        // Add the style to the workbook if necessary
1548 2
        $workbook = $this->parent;
1549 2
        if ($existingStyle = $this->parent->getCellXfByHashCode($pCellStyle->getHashCode())) {
1550
            // there is already such cell Xf in our collection
1551 1
            $xfIndex = $existingStyle->getIndex();
1552
        } else {
1553
            // we don't have such a cell Xf, need to add
1554 2
            $workbook->addCellXf($pCellStyle);
1555 2
            $xfIndex = $pCellStyle->getIndex();
1556
        }
1557
1558
        // Calculate range outer borders
1559 2
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange . ':' . $pRange);
1560
1561
        // Make sure we can loop upwards on rows and columns
1562 2 View Code Duplication
        if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1563
            $tmp = $rangeStart;
1564
            $rangeStart = $rangeEnd;
1565
            $rangeEnd = $tmp;
1566
        }
1567
1568
        // Loop through cells and apply styles
1569 2 View Code Duplication
        for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1570 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1571 2
                $this->getCell(Cell::stringFromColumnIndex($col - 1) . $row)->setXfIndex($xfIndex);
1572
            }
1573
        }
1574
1575 2
        return $this;
1576
    }
1577
1578
    /**
1579
     * Duplicate conditional style to a range of cells.
1580
     *
1581
     * Please note that this will overwrite existing cell styles for cells in range!
1582
     *
1583
     * @param Conditional[] $pCellStyle Cell style to duplicate
1584
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1585
     *
1586
     * @throws Exception
1587
     *
1588
     * @return Worksheet
1589
     */
1590 2
    public function duplicateConditionalStyle(array $pCellStyle, $pRange = '')
1591
    {
1592 2
        foreach ($pCellStyle as $cellStyle) {
1593 2
            if (!($cellStyle instanceof Conditional)) {
1594 2
                throw new Exception('Style is not a conditional style');
1595
            }
1596
        }
1597
1598
        // Calculate range outer borders
1599 2
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange . ':' . $pRange);
1600
1601
        // Make sure we can loop upwards on rows and columns
1602 2 View Code Duplication
        if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1603
            $tmp = $rangeStart;
1604
            $rangeStart = $rangeEnd;
1605
            $rangeEnd = $tmp;
1606
        }
1607
1608
        // Loop through cells and apply styles
1609 2 View Code Duplication
        for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1610 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1611 2
                $this->setConditionalStyles(Cell::stringFromColumnIndex($col - 1) . $row, $pCellStyle);
1612
            }
1613
        }
1614
1615 2
        return $this;
1616
    }
1617
1618
    /**
1619
     * Set break on a cell.
1620
     *
1621
     * @param string $pCoordinate Cell coordinate (e.g. A1)
1622
     * @param int $pBreak Break type (type of Worksheet::BREAK_*)
1623
     *
1624
     * @throws Exception
1625
     *
1626
     * @return Worksheet
1627
     */
1628 1
    public function setBreak($pCoordinate, $pBreak)
1629
    {
1630
        // Uppercase coordinate
1631 1
        $pCoordinate = strtoupper($pCoordinate);
1632
1633 1
        if ($pCoordinate != '') {
1634 1
            if ($pBreak == self::BREAK_NONE) {
1635
                if (isset($this->breaks[$pCoordinate])) {
1636
                    unset($this->breaks[$pCoordinate]);
1637
                }
1638
            } else {
1639 1
                $this->breaks[$pCoordinate] = $pBreak;
1640
            }
1641
        } else {
1642
            throw new Exception('No cell coordinate specified.');
1643
        }
1644
1645 1
        return $this;
1646
    }
1647
1648
    /**
1649
     * Set break on a cell by using numeric cell coordinates.
1650
     *
1651
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1652
     * @param int $pRow Numeric row coordinate of the cell
1653
     * @param int $pBreak Break type (type of Worksheet::BREAK_*)
1654
     *
1655
     * @return Worksheet
1656
     */
1657
    public function setBreakByColumnAndRow($pColumn, $pRow, $pBreak)
1658
    {
1659
        return $this->setBreak(Cell::stringFromColumnIndex($pColumn) . $pRow, $pBreak);
1660
    }
1661
1662
    /**
1663
     * Get breaks.
1664
     *
1665
     * @return array[]
1666
     */
1667 62
    public function getBreaks()
1668
    {
1669 62
        return $this->breaks;
1670
    }
1671
1672
    /**
1673
     * Set merge on a cell range.
1674
     *
1675
     * @param string $pRange Cell range (e.g. A1:E1)
1676
     *
1677
     * @throws Exception
1678
     *
1679
     * @return Worksheet
1680
     */
1681 28
    public function mergeCells($pRange)
1682
    {
1683
        // Uppercase coordinate
1684 28
        $pRange = strtoupper($pRange);
1685
1686 28
        if (strpos($pRange, ':') !== false) {
1687 28
            $this->mergeCells[$pRange] = $pRange;
1688
1689
            // make sure cells are created
1690
1691
            // get the cells in the range
1692 28
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
1693
1694
            // create upper left cell if it does not already exist
1695 28
            $upperLeft = $aReferences[0];
1696 28
            if (!$this->cellExists($upperLeft)) {
1697 14
                $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL);
1698
            }
1699
1700
            // Blank out the rest of the cells in the range (if they exist)
1701 28
            $count = count($aReferences);
1702 28
            for ($i = 1; $i < $count; ++$i) {
1703 28
                if ($this->cellExists($aReferences[$i])) {
1704 14
                    $this->getCell($aReferences[$i])->setValueExplicit(null, DataType::TYPE_NULL);
1705
                }
1706
            }
1707
        } else {
1708
            throw new Exception('Merge must be set on a range of cells.');
1709
        }
1710
1711 28
        return $this;
1712
    }
1713
1714
    /**
1715
     * Set merge on a cell range by using numeric cell coordinates.
1716
     *
1717
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1718
     * @param int $pRow1 Numeric row coordinate of the first cell
1719
     * @param int $pColumn2 Numeric column coordinate of the last cell (A = 0)
1720
     * @param int $pRow2 Numeric row coordinate of the last cell
1721
     *
1722
     * @throws Exception
1723
     *
1724
     * @return Worksheet
1725
     */
1726
    public function mergeCellsByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2)
1727
    {
1728
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1729
1730
        return $this->mergeCells($cellRange);
1731
    }
1732
1733
    /**
1734
     * Remove merge on a cell range.
1735
     *
1736
     * @param string $pRange Cell range (e.g. A1:E1)
1737
     *
1738
     * @throws Exception
1739
     *
1740
     * @return Worksheet
1741
     */
1742 12
    public function unmergeCells($pRange)
1743
    {
1744
        // Uppercase coordinate
1745 12
        $pRange = strtoupper($pRange);
1746
1747 12
        if (strpos($pRange, ':') !== false) {
1748 12
            if (isset($this->mergeCells[$pRange])) {
1749 12
                unset($this->mergeCells[$pRange]);
1750
            } else {
1751 12
                throw new Exception('Cell range ' . $pRange . ' not known as merged.');
1752
            }
1753
        } else {
1754
            throw new Exception('Merge can only be removed from a range of cells.');
1755
        }
1756
1757 12
        return $this;
1758
    }
1759
1760
    /**
1761
     * Remove merge on a cell range by using numeric cell coordinates.
1762
     *
1763
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1764
     * @param int $pRow1 Numeric row coordinate of the first cell
1765
     * @param int $pColumn2 Numeric column coordinate of the last cell (A = 0)
1766
     * @param int $pRow2 Numeric row coordinate of the last cell
1767
     *
1768
     * @throws Exception
1769
     *
1770
     * @return Worksheet
1771
     */
1772
    public function unmergeCellsByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2)
1773
    {
1774
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1775
1776
        return $this->unmergeCells($cellRange);
1777
    }
1778
1779
    /**
1780
     * Get merge cells array.
1781
     *
1782
     * @return array[]
1783
     */
1784 62
    public function getMergeCells()
1785
    {
1786 62
        return $this->mergeCells;
1787
    }
1788
1789
    /**
1790
     * Set merge cells array for the entire sheet. Use instead mergeCells() to merge
1791
     * a single cell range.
1792
     *
1793
     * @param array
1794
     * @param mixed $pValue
1795
     */
1796 15
    public function setMergeCells(array $pValue)
1797
    {
1798 15
        $this->mergeCells = $pValue;
1799
1800 15
        return $this;
1801
    }
1802
1803
    /**
1804
     * Set protection on a cell range.
1805
     *
1806
     * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
1807
     * @param string $pPassword Password to unlock the protection
1808
     * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
1809
     *
1810
     * @throws Exception
1811
     *
1812
     * @return Worksheet
1813
     */
1814 12
    public function protectCells($pRange, $pPassword, $pAlreadyHashed = false)
1815
    {
1816
        // Uppercase coordinate
1817 12
        $pRange = strtoupper($pRange);
1818
1819 12
        if (!$pAlreadyHashed) {
1820 12
            $pPassword = Shared\PasswordHasher::hashPassword($pPassword);
1821
        }
1822 12
        $this->protectedCells[$pRange] = $pPassword;
1823
1824 12
        return $this;
1825
    }
1826
1827
    /**
1828
     * Set protection on a cell range by using numeric cell coordinates.
1829
     *
1830
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1831
     * @param int $pRow1 Numeric row coordinate of the first cell
1832
     * @param int $pColumn2 Numeric column coordinate of the last cell (A = 0)
1833
     * @param int $pRow2 Numeric row coordinate of the last cell
1834
     * @param string $pPassword Password to unlock the protection
1835
     * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
1836
     *
1837
     * @throws Exception
1838
     *
1839
     * @return Worksheet
1840
     */
1841
    public function protectCellsByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2, $pPassword, $pAlreadyHashed = false)
1842
    {
1843
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1844
1845
        return $this->protectCells($cellRange, $pPassword, $pAlreadyHashed);
1846
    }
1847
1848
    /**
1849
     * Remove protection on a cell range.
1850
     *
1851
     * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
1852
     *
1853
     * @throws Exception
1854
     *
1855
     * @return Worksheet
1856
     */
1857 12
    public function unprotectCells($pRange)
1858
    {
1859
        // Uppercase coordinate
1860 12
        $pRange = strtoupper($pRange);
1861
1862 12
        if (isset($this->protectedCells[$pRange])) {
1863 12
            unset($this->protectedCells[$pRange]);
1864
        } else {
1865
            throw new Exception('Cell range ' . $pRange . ' not known as protected.');
1866
        }
1867
1868 12
        return $this;
1869
    }
1870
1871
    /**
1872
     * Remove protection on a cell range by using numeric cell coordinates.
1873
     *
1874
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1875
     * @param int $pRow1 Numeric row coordinate of the first cell
1876
     * @param int $pColumn2 Numeric column coordinate of the last cell (A = 0)
1877
     * @param int $pRow2 Numeric row coordinate of the last cell
1878
     *
1879
     * @throws Exception
1880
     *
1881
     * @return Worksheet
1882
     */
1883
    public function unprotectCellsByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2)
1884
    {
1885
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1886
1887
        return $this->unprotectCells($cellRange);
1888
    }
1889
1890
    /**
1891
     * Get protected cells.
1892
     *
1893
     * @return array[]
1894
     */
1895 62
    public function getProtectedCells()
1896
    {
1897 62
        return $this->protectedCells;
1898
    }
1899
1900
    /**
1901
     * Get Autofilter.
1902
     *
1903
     * @return AutoFilter
1904
     */
1905 63
    public function getAutoFilter()
1906
    {
1907 63
        return $this->autoFilter;
1908
    }
1909
1910
    /**
1911
     * Set AutoFilter.
1912
     *
1913
     * @param string|Worksheet\AutoFilter $pValue
1914
     *            A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility
1915
     *
1916
     * @throws Exception
1917
     *
1918
     * @return Worksheet
1919
     */
1920 4
    public function setAutoFilter($pValue)
1921
    {
1922 4
        $pValue = strtoupper($pValue);
1923 4
        if (is_string($pValue)) {
1924 4
            $this->autoFilter->setRange($pValue);
1925
        } elseif (is_object($pValue) && ($pValue instanceof AutoFilter)) {
1926
            $this->autoFilter = $pValue;
1927
        }
1928
1929 4
        return $this;
1930
    }
1931
1932
    /**
1933
     * Set Autofilter Range by using numeric cell coordinates.
1934
     *
1935
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1936
     * @param int $pRow1 Numeric row coordinate of the first cell
1937
     * @param int $pColumn2 Numeric column coordinate of the second cell (A = 0)
1938
     * @param int $pRow2 Numeric row coordinate of the second cell
1939
     *
1940
     * @throws Exception
1941
     *
1942
     * @return Worksheet
1943
     */
1944
    public function setAutoFilterByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2)
1945
    {
1946
        return $this->setAutoFilter(
1947
            Cell::stringFromColumnIndex($pColumn1) . $pRow1
1948
            . ':' .
1949
            Cell::stringFromColumnIndex($pColumn2) . $pRow2
1950
        );
1951
    }
1952
1953
    /**
1954
     * Remove autofilter.
1955
     *
1956
     * @return Worksheet
1957
     */
1958
    public function removeAutoFilter()
1959
    {
1960
        $this->autoFilter->setRange(null);
1961
1962
        return $this;
1963
    }
1964
1965
    /**
1966
     * Get Freeze Pane.
1967
     *
1968
     * @return string
1969
     */
1970 62
    public function getFreezePane()
1971
    {
1972 62
        return $this->freezePane;
1973
    }
1974
1975
    /**
1976
     * Freeze Pane.
1977
     *
1978
     * @param string $pCell Cell (i.e. A2)
1979
     *                                    Examples:
1980
     *                                        A2 will freeze the rows above cell A2 (i.e row 1)
1981
     *                                        B1 will freeze the columns to the left of cell B1 (i.e column A)
1982
     *                                        B2 will freeze the rows above and to the left of cell A2
1983
     *                                            (i.e row 1 and column A)
1984
     *
1985
     * @throws Exception
1986
     *
1987
     * @return Worksheet
1988
     */
1989 4
    public function freezePane($pCell)
1990
    {
1991
        // Uppercase coordinate
1992 4
        $pCell = strtoupper($pCell);
1993 4
        if (strpos($pCell, ':') === false && strpos($pCell, ',') === false) {
1994 4
            $this->freezePane = $pCell;
1995
        } else {
1996
            throw new Exception('Freeze pane can not be set on a range of cells.');
1997
        }
1998
1999 4
        return $this;
2000
    }
2001
2002
    /**
2003
     * Freeze Pane by using numeric cell coordinates.
2004
     *
2005
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
2006
     * @param int $pRow Numeric row coordinate of the cell
2007
     *
2008
     * @throws Exception
2009
     *
2010
     * @return Worksheet
2011
     */
2012
    public function freezePaneByColumnAndRow($pColumn, $pRow)
2013
    {
2014
        return $this->freezePane(Cell::stringFromColumnIndex($pColumn) . $pRow);
2015
    }
2016
2017
    /**
2018
     * Unfreeze Pane.
2019
     *
2020
     * @return Worksheet
2021
     */
2022
    public function unfreezePane()
2023
    {
2024
        return $this->freezePane('');
2025
    }
2026
2027
    /**
2028
     * Insert a new row, updating all possible related data.
2029
     *
2030
     * @param int $pBefore Insert before this one
2031
     * @param int $pNumRows Number of rows to insert
2032
     *
2033
     * @throws Exception
2034
     *
2035
     * @return Worksheet
2036
     */
2037 13 View Code Duplication
    public function insertNewRowBefore($pBefore, $pNumRows = 1)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2038
    {
2039 13
        if ($pBefore >= 1) {
2040 13
            $objReferenceHelper = ReferenceHelper::getInstance();
2041 13
            $objReferenceHelper->insertNewBefore('A' . $pBefore, 0, $pNumRows, $this);
2042
        } else {
2043
            throw new Exception('Rows can only be inserted before at least row 1.');
2044
        }
2045
2046 13
        return $this;
2047
    }
2048
2049
    /**
2050
     * Insert a new column, updating all possible related data.
2051
     *
2052
     * @param int $pBefore Insert before this one, eg: 'A'
2053
     * @param int $pNumCols Number of columns to insert
2054
     *
2055
     * @throws Exception
2056
     *
2057
     * @return Worksheet
2058
     */
2059 12 View Code Duplication
    public function insertNewColumnBefore($pBefore, $pNumCols = 1)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2060
    {
2061 12
        if (!is_numeric($pBefore)) {
2062 12
            $objReferenceHelper = ReferenceHelper::getInstance();
2063 12
            $objReferenceHelper->insertNewBefore($pBefore . '1', $pNumCols, 0, $this);
2064
        } else {
2065
            throw new Exception('Column references should not be numeric.');
2066
        }
2067
2068 12
        return $this;
2069
    }
2070
2071
    /**
2072
     * Insert a new column, updating all possible related data.
2073
     *
2074
     * @param int $pBefore Insert before this one (numeric column coordinate of the cell, A = 0)
2075
     * @param int $pNumCols Number of columns to insert
2076
     *
2077
     * @throws Exception
2078
     *
2079
     * @return Worksheet
2080
     */
2081
    public function insertNewColumnBeforeByIndex($pBefore, $pNumCols = 1)
2082
    {
2083
        if ($pBefore >= 0) {
2084
            return $this->insertNewColumnBefore(Cell::stringFromColumnIndex($pBefore), $pNumCols);
2085
        }
2086
2087
        throw new Exception('Columns can only be inserted before at least column A (0).');
2088
    }
2089
2090
    /**
2091
     * Delete a row, updating all possible related data.
2092
     *
2093
     * @param int $pRow Remove starting with this one
2094
     * @param int $pNumRows Number of rows to remove
2095
     *
2096
     * @throws Exception
2097
     *
2098
     * @return Worksheet
2099
     */
2100 15
    public function removeRow($pRow, $pNumRows = 1)
2101
    {
2102 15
        if ($pRow >= 1) {
2103 15
            $highestRow = $this->getHighestDataRow();
2104 15
            $objReferenceHelper = ReferenceHelper::getInstance();
2105 15
            $objReferenceHelper->insertNewBefore('A' . ($pRow + $pNumRows), 0, -$pNumRows, $this);
2106 15
            for ($r = 0; $r < $pNumRows; ++$r) {
2107 15
                $this->getCellCollection()->removeRow($highestRow);
2108 15
                --$highestRow;
2109
            }
2110
        } else {
2111
            throw new Exception('Rows to be deleted should at least start from row 1.');
2112
        }
2113
2114 15
        return $this;
2115
    }
2116
2117
    /**
2118
     * Remove a column, updating all possible related data.
2119
     *
2120
     * @param string $pColumn Remove starting with this one, eg: 'A'
2121
     * @param int $pNumCols Number of columns to remove
2122
     *
2123
     * @throws Exception
2124
     *
2125
     * @return Worksheet
2126
     */
2127 12
    public function removeColumn($pColumn, $pNumCols = 1)
2128
    {
2129 12
        if (!is_numeric($pColumn)) {
2130 12
            $highestColumn = $this->getHighestDataColumn();
2131 12
            $pColumn = Cell::stringFromColumnIndex(Cell::columnIndexFromString($pColumn) - 1 + $pNumCols);
2132 12
            $objReferenceHelper = ReferenceHelper::getInstance();
2133 12
            $objReferenceHelper->insertNewBefore($pColumn . '1', -$pNumCols, 0, $this);
2134 12
            for ($c = 0; $c < $pNumCols; ++$c) {
2135 12
                $this->getCellCollection()->removeColumn($highestColumn);
2136 12
                $highestColumn = Cell::stringFromColumnIndex(Cell::columnIndexFromString($highestColumn) - 2);
2137
            }
2138
        } else {
2139
            throw new Exception('Column references should not be numeric.');
2140
        }
2141
2142 12
        return $this;
2143
    }
2144
2145
    /**
2146
     * Remove a column, updating all possible related data.
2147
     *
2148
     * @param int $pColumn Remove starting with this one (numeric column coordinate of the cell A = 0)
2149
     * @param int $pNumCols Number of columns to remove
2150
     *
2151
     * @throws Exception
2152
     *
2153
     * @return Worksheet
2154
     */
2155
    public function removeColumnByIndex($pColumn, $pNumCols = 1)
2156
    {
2157
        if ($pColumn >= 0) {
2158
            return $this->removeColumn(Cell::stringFromColumnIndex($pColumn), $pNumCols);
2159
        }
2160
2161
        throw new Exception('Columns to be deleted should at least start from column 0');
2162
    }
2163
2164
    /**
2165
     * Show gridlines?
2166
     *
2167
     * @return bool
2168
     */
2169 62
    public function getShowGridlines()
2170
    {
2171 62
        return $this->showGridlines;
2172
    }
2173
2174
    /**
2175
     * Set show gridlines.
2176
     *
2177
     * @param bool $pValue Show gridlines (true/false)
2178
     *
2179
     * @return Worksheet
2180
     */
2181 26
    public function setShowGridlines($pValue)
2182
    {
2183 26
        $this->showGridlines = $pValue;
2184
2185 26
        return $this;
2186
    }
2187
2188
    /**
2189
     * Print gridlines?
2190
     *
2191
     * @return bool
2192
     */
2193 58
    public function getPrintGridlines()
2194
    {
2195 58
        return $this->printGridlines;
2196
    }
2197
2198
    /**
2199
     * Set print gridlines.
2200
     *
2201
     * @param bool $pValue Print gridlines (true/false)
2202
     *
2203
     * @return Worksheet
2204
     */
2205 17
    public function setPrintGridlines($pValue)
2206
    {
2207 17
        $this->printGridlines = $pValue;
2208
2209 17
        return $this;
2210
    }
2211
2212
    /**
2213
     * Show row and column headers?
2214
     *
2215
     * @return bool
2216
     */
2217 58
    public function getShowRowColHeaders()
2218
    {
2219 58
        return $this->showRowColHeaders;
2220
    }
2221
2222
    /**
2223
     * Set show row and column headers.
2224
     *
2225
     * @param bool $pValue Show row and column headers (true/false)
2226
     *
2227
     * @return Worksheet
2228
     */
2229 23
    public function setShowRowColHeaders($pValue)
2230
    {
2231 23
        $this->showRowColHeaders = $pValue;
2232
2233 23
        return $this;
2234
    }
2235
2236
    /**
2237
     * Show summary below? (Row/Column outlining).
2238
     *
2239
     * @return bool
2240
     */
2241 58
    public function getShowSummaryBelow()
2242
    {
2243 58
        return $this->showSummaryBelow;
2244
    }
2245
2246
    /**
2247
     * Set show summary below.
2248
     *
2249
     * @param bool $pValue Show summary below (true/false)
2250
     *
2251
     * @return Worksheet
2252
     */
2253 23
    public function setShowSummaryBelow($pValue)
2254
    {
2255 23
        $this->showSummaryBelow = $pValue;
2256
2257 23
        return $this;
2258
    }
2259
2260
    /**
2261
     * Show summary right? (Row/Column outlining).
2262
     *
2263
     * @return bool
2264
     */
2265 58
    public function getShowSummaryRight()
2266
    {
2267 58
        return $this->showSummaryRight;
2268
    }
2269
2270
    /**
2271
     * Set show summary right.
2272
     *
2273
     * @param bool $pValue Show summary right (true/false)
2274
     *
2275
     * @return Worksheet
2276
     */
2277 23
    public function setShowSummaryRight($pValue)
2278
    {
2279 23
        $this->showSummaryRight = $pValue;
2280
2281 23
        return $this;
2282
    }
2283
2284
    /**
2285
     * Get comments.
2286
     *
2287
     * @return Comment[]
2288
     */
2289 63
    public function getComments()
2290
    {
2291 63
        return $this->comments;
2292
    }
2293
2294
    /**
2295
     * Set comments array for the entire sheet.
2296
     *
2297
     * @param array of Comment
2298
     * @param mixed $pValue
2299
     *
2300
     * @return Worksheet
2301
     */
2302 15
    public function setComments(array $pValue)
2303
    {
2304 15
        $this->comments = $pValue;
2305
2306 15
        return $this;
2307
    }
2308
2309
    /**
2310
     * Get comment for cell.
2311
     *
2312
     * @param string $pCellCoordinate Cell coordinate to get comment for, eg: 'A1'
2313
     *
2314
     * @throws Exception
2315
     *
2316
     * @return Comment
2317
     */
2318 21
    public function getComment($pCellCoordinate)
2319
    {
2320
        // Uppercase coordinate
2321 21
        $pCellCoordinate = strtoupper($pCellCoordinate);
2322
2323 21 View Code Duplication
        if (strpos($pCellCoordinate, ':') !== false || strpos($pCellCoordinate, ',') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2324
            throw new Exception('Cell coordinate string can not be a range of cells.');
2325 21
        } elseif (strpos($pCellCoordinate, '$') !== false) {
2326
            throw new Exception('Cell coordinate string must not be absolute.');
2327 21
        } elseif ($pCellCoordinate == '') {
2328
            throw new Exception('Cell coordinate can not be zero-length string.');
2329
        }
2330
2331
        // Check if we already have a comment for this cell.
2332 21
        if (isset($this->comments[$pCellCoordinate])) {
2333 13
            return $this->comments[$pCellCoordinate];
2334
        }
2335
2336
        // If not, create a new comment.
2337 21
        $newComment = new Comment();
2338 21
        $this->comments[$pCellCoordinate] = $newComment;
2339
2340 21
        return $newComment;
2341
    }
2342
2343
    /**
2344
     * Get comment for cell by using numeric cell coordinates.
2345
     *
2346
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
2347
     * @param int $pRow Numeric row coordinate of the cell
2348
     *
2349
     * @return Comment
2350
     */
2351 2
    public function getCommentByColumnAndRow($pColumn, $pRow)
2352
    {
2353 2
        return $this->getComment(Cell::stringFromColumnIndex($pColumn) . $pRow);
2354
    }
2355
2356
    /**
2357
     * Get active cell.
2358
     *
2359
     * @return string Example: 'A1'
2360
     */
2361 63
    public function getActiveCell()
2362
    {
2363 63
        return $this->activeCell;
2364
    }
2365
2366
    /**
2367
     * Get selected cells.
2368
     *
2369
     * @return string
2370
     */
2371 62
    public function getSelectedCells()
2372
    {
2373 62
        return $this->selectedCells;
2374
    }
2375
2376
    /**
2377
     * Selected cell.
2378
     *
2379
     * @param string $pCoordinate Cell (i.e. A1)
2380
     *
2381
     * @return Worksheet
2382
     */
2383
    public function setSelectedCell($pCoordinate)
2384
    {
2385
        return $this->setSelectedCells($pCoordinate);
2386
    }
2387
2388
    /**
2389
     * Select a range of cells.
2390
     *
2391
     * @param string $pCoordinate Cell range, examples: 'A1', 'B2:G5', 'A:C', '3:6'
2392
     *
2393
     * @throws Exception
2394
     *
2395
     * @return Worksheet
2396
     */
2397 70
    public function setSelectedCells($pCoordinate)
2398
    {
2399
        // Uppercase coordinate
2400 70
        $pCoordinate = strtoupper($pCoordinate);
2401
2402
        // Convert 'A' to 'A:A'
2403 70
        $pCoordinate = preg_replace('/^([A-Z]+)$/', '${1}:${1}', $pCoordinate);
2404
2405
        // Convert '1' to '1:1'
2406 70
        $pCoordinate = preg_replace('/^([0-9]+)$/', '${1}:${1}', $pCoordinate);
2407
2408
        // Convert 'A:C' to 'A1:C1048576'
2409 70
        $pCoordinate = preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $pCoordinate);
2410
2411
        // Convert '1:3' to 'A1:XFD3'
2412 70
        $pCoordinate = preg_replace('/^([0-9]+):([0-9]+)$/', 'A${1}:XFD${2}', $pCoordinate);
2413
2414 70
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
2415 45
            list($first) = Cell::splitRange($pCoordinate);
2416 45
            $this->activeCell = $first[0];
2417
        } else {
2418 58
            $this->activeCell = $pCoordinate;
2419
        }
2420 70
        $this->selectedCells = $pCoordinate;
2421
2422 70
        return $this;
2423
    }
2424
2425
    /**
2426
     * Selected cell by using numeric cell coordinates.
2427
     *
2428
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
2429
     * @param int $pRow Numeric row coordinate of the cell
2430
     *
2431
     * @throws Exception
2432
     *
2433
     * @return Worksheet
2434
     */
2435
    public function setSelectedCellByColumnAndRow($pColumn, $pRow)
2436
    {
2437
        return $this->setSelectedCells(Cell::stringFromColumnIndex($pColumn) . $pRow);
2438
    }
2439
2440
    /**
2441
     * Get right-to-left.
2442
     *
2443
     * @return bool
2444
     */
2445 58
    public function getRightToLeft()
2446
    {
2447 58
        return $this->rightToLeft;
2448
    }
2449
2450
    /**
2451
     * Set right-to-left.
2452
     *
2453
     * @param bool $value Right-to-left true/false
2454
     *
2455
     * @return Worksheet
2456
     */
2457 18
    public function setRightToLeft($value)
2458
    {
2459 18
        $this->rightToLeft = $value;
2460
2461 18
        return $this;
2462
    }
2463
2464
    /**
2465
     * Fill worksheet from values in array.
2466
     *
2467
     * @param array $source Source array
2468
     * @param mixed $nullValue Value in source array that stands for blank cell
2469
     * @param string $startCell Insert array starting from this cell address as the top left coordinate
2470
     * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array
2471
     *
2472
     * @throws Exception
2473
     *
2474
     * @return Worksheet
2475
     */
2476 30
    public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false)
2477
    {
2478
        //    Convert a 1-D array to 2-D (for ease of looping)
2479 30
        if (!is_array(end($source))) {
2480 3
            $source = [$source];
2481
        }
2482
2483
        // start coordinate
2484 30
        list($startColumn, $startRow) = Cell::coordinateFromString($startCell);
2485
2486
        // Loop through $source
2487 30
        foreach ($source as $rowData) {
2488 30
            $currentColumn = $startColumn;
2489 30
            foreach ($rowData as $cellValue) {
2490 30
                if ($strictNullComparison) {
2491 3
                    if ($cellValue !== $nullValue) {
2492
                        // Set cell value
2493 3
                        $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2494
                    }
2495
                } else {
2496 27
                    if ($cellValue != $nullValue) {
2497
                        // Set cell value
2498 27
                        $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2499
                    }
2500
                }
2501 30
                ++$currentColumn;
2502
            }
2503 30
            ++$startRow;
2504
        }
2505
2506 30
        return $this;
2507
    }
2508
2509
    /**
2510
     * Create array from a range of cells.
2511
     *
2512
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
2513
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2514
     * @param bool $calculateFormulas Should formulas be calculated?
2515
     * @param bool $formatData Should formatting be applied to cell values?
2516
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2517
     *                               True - Return rows and columns indexed by their actual row and column IDs
2518
     *
2519
     * @return array
2520
     */
2521 25
    public function rangeToArray($pRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2522
    {
2523
        // Returnvalue
2524 25
        $returnValue = [];
2525
        //    Identify the range that we need to extract from the worksheet
2526 25
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange);
2527 25
        $minCol = Cell::stringFromColumnIndex($rangeStart[0] - 1);
2528 25
        $minRow = $rangeStart[1];
2529 25
        $maxCol = Cell::stringFromColumnIndex($rangeEnd[0] - 1);
2530 25
        $maxRow = $rangeEnd[1];
2531
2532 25
        ++$maxCol;
2533
        // Loop through rows
2534 25
        $r = -1;
2535 25
        for ($row = $minRow; $row <= $maxRow; ++$row) {
2536 25
            $rRef = ($returnCellRef) ? $row : ++$r;
2537 25
            $c = -1;
2538
            // Loop through columns in the current row
2539 25
            for ($col = $minCol; $col != $maxCol; ++$col) {
2540 25
                $cRef = ($returnCellRef) ? $col : ++$c;
2541
                //    Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
2542
                //        so we test and retrieve directly against cellCollection
2543 25
                if ($this->cellCollection->has($col . $row)) {
2544
                    // Cell exists
2545 25
                    $cell = $this->cellCollection->get($col . $row);
2546 25
                    if ($cell->getValue() !== null) {
2547 25
                        if ($cell->getValue() instanceof RichText) {
2548 2
                            $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
2549
                        } else {
2550 25
                            if ($calculateFormulas) {
2551 24
                                $returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
2552
                            } else {
2553 2
                                $returnValue[$rRef][$cRef] = $cell->getValue();
2554
                            }
2555
                        }
2556
2557 25
                        if ($formatData) {
2558 24
                            $style = $this->parent->getCellXfByIndex($cell->getXfIndex());
2559 24
                            $returnValue[$rRef][$cRef] = NumberFormat::toFormattedString(
2560 24
                                $returnValue[$rRef][$cRef],
2561 25
                                ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL
2562
                            );
2563
                        }
2564
                    } else {
2565
                        // Cell holds a NULL
2566 25
                        $returnValue[$rRef][$cRef] = $nullValue;
2567
                    }
2568
                } else {
2569
                    // Cell doesn't exist
2570 12
                    $returnValue[$rRef][$cRef] = $nullValue;
2571
                }
2572
            }
2573
        }
2574
2575
        // Return
2576 25
        return $returnValue;
2577
    }
2578
2579
    /**
2580
     * Create array from a range of cells.
2581
     *
2582
     * @param string $pNamedRange Name of the Named Range
2583
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2584
     * @param bool $calculateFormulas Should formulas be calculated?
2585
     * @param bool $formatData Should formatting be applied to cell values?
2586
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2587
     *                                True - Return rows and columns indexed by their actual row and column IDs
2588
     *
2589
     * @throws Exception
2590
     *
2591
     * @return array
2592
     */
2593
    public function namedRangeToArray($pNamedRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2594
    {
2595
        $namedRange = NamedRange::resolveRange($pNamedRange, $this);
2596
        if ($namedRange !== null) {
2597
            $pWorkSheet = $namedRange->getWorksheet();
2598
            $pCellRange = $namedRange->getRange();
2599
2600
            return $pWorkSheet->rangeToArray($pCellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2601
        }
2602
2603
        throw new Exception('Named Range ' . $pNamedRange . ' does not exist.');
2604
    }
2605
2606
    /**
2607
     * Create array from worksheet.
2608
     *
2609
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2610
     * @param bool $calculateFormulas Should formulas be calculated?
2611
     * @param bool $formatData Should formatting be applied to cell values?
2612
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2613
     *                               True - Return rows and columns indexed by their actual row and column IDs
2614
     *
2615
     * @return array
2616
     */
2617 12
    public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2618
    {
2619
        // Garbage collect...
2620 12
        $this->garbageCollect();
2621
2622
        //    Identify the range that we need to extract from the worksheet
2623 12
        $maxCol = $this->getHighestColumn();
2624 12
        $maxRow = $this->getHighestRow();
2625
        // Return
2626 12
        return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2627
    }
2628
2629
    /**
2630
     * Get row iterator.
2631
     *
2632
     * @param int $startRow The row number at which to start iterating
2633
     * @param int $endRow The row number at which to stop iterating
2634
     *
2635
     * @return RowIterator
2636
     */
2637 4
    public function getRowIterator($startRow = 1, $endRow = null)
2638
    {
2639 4
        return new RowIterator($this, $startRow, $endRow);
2640
    }
2641
2642
    /**
2643
     * Get column iterator.
2644
     *
2645
     * @param string $startColumn The column address at which to start iterating
2646
     * @param string $endColumn The column address at which to stop iterating
2647
     *
2648
     * @return ColumnIterator
2649
     */
2650
    public function getColumnIterator($startColumn = 'A', $endColumn = null)
2651
    {
2652
        return new ColumnIterator($this, $startColumn, $endColumn);
2653
    }
2654
2655
    /**
2656
     * Run PhpSpreadsheet garabage collector.
2657
     *
2658
     * @return Worksheet
2659
     */
2660 74
    public function garbageCollect()
2661
    {
2662
        // Flush cache
2663 74
        $this->cellCollection->get('A1');
2664
2665
        // Lookup highest column and highest row if cells are cleaned
2666 74
        $colRow = $this->cellCollection->getHighestRowAndColumn();
2667 74
        $highestRow = $colRow['row'];
2668 74
        $highestColumn = Cell::columnIndexFromString($colRow['column']);
2669
2670
        // Loop through column dimensions
2671 74
        foreach ($this->columnDimensions as $dimension) {
2672 33
            $highestColumn = max($highestColumn, Cell::columnIndexFromString($dimension->getColumnIndex()));
2673
        }
2674
2675
        // Loop through row dimensions
2676 74
        foreach ($this->rowDimensions as $dimension) {
2677 47
            $highestRow = max($highestRow, $dimension->getRowIndex());
2678
        }
2679
2680
        // Cache values
2681 74
        if ($highestColumn < 0) {
2682
            $this->cachedHighestColumn = 'A';
2683
        } else {
2684 74
            $this->cachedHighestColumn = Cell::stringFromColumnIndex(--$highestColumn);
2685
        }
2686 74
        $this->cachedHighestRow = $highestRow;
2687
2688
        // Return
2689 74
        return $this;
2690
    }
2691
2692
    /**
2693
     * Get hash code.
2694
     *
2695
     * @return string Hash code
2696
     */
2697 90
    public function getHashCode()
2698
    {
2699 90
        if ($this->dirty) {
2700 90
            $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__);
2701 90
            $this->dirty = false;
2702
        }
2703
2704 90
        return $this->hash;
2705
    }
2706
2707
    /**
2708
     * Extract worksheet title from range.
2709
     *
2710
     * Example: extractSheetTitle("testSheet!A1") ==> 'A1'
2711
     * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> array('testSheet 1', 'A1');
2712
     *
2713
     * @param string $pRange Range to extract title from
2714
     * @param bool $returnRange Return range? (see example)
2715
     *
2716
     * @return mixed
2717
     */
2718 1
    public static function extractSheetTitle($pRange, $returnRange = false)
2719
    {
2720
        // Sheet title included?
2721 1
        if (($sep = strpos($pRange, '!')) === false) {
2722
            return '';
2723
        }
2724
2725 1
        if ($returnRange) {
2726 1
            return [trim(substr($pRange, 0, $sep), "'"), substr($pRange, $sep + 1)];
2727
        }
2728
2729
        return substr($pRange, $sep + 1);
2730
    }
2731
2732
    /**
2733
     * Get hyperlink.
2734
     *
2735
     * @param string $pCellCoordinate Cell coordinate to get hyperlink for, eg: 'A1'
2736
     */
2737 21
    public function getHyperlink($pCellCoordinate)
2738
    {
2739
        // return hyperlink if we already have one
2740 21
        if (isset($this->hyperlinkCollection[$pCellCoordinate])) {
2741 15
            return $this->hyperlinkCollection[$pCellCoordinate];
2742
        }
2743
2744
        // else create hyperlink
2745 21
        $this->hyperlinkCollection[$pCellCoordinate] = new Hyperlink();
2746
2747 21
        return $this->hyperlinkCollection[$pCellCoordinate];
2748
    }
2749
2750
    /**
2751
     * Set hyperlink.
2752
     *
2753
     * @param string $pCellCoordinate Cell coordinate to insert hyperlink, eg: 'A1'
2754
     * @param null|Hyperlink $pHyperlink
2755
     *
2756
     * @return Worksheet
2757
     */
2758 13
    public function setHyperlink($pCellCoordinate, Hyperlink $pHyperlink = null)
2759
    {
2760 13
        if ($pHyperlink === null) {
2761 13
            unset($this->hyperlinkCollection[$pCellCoordinate]);
2762
        } else {
2763 13
            $this->hyperlinkCollection[$pCellCoordinate] = $pHyperlink;
2764
        }
2765
2766 13
        return $this;
2767
    }
2768
2769
    /**
2770
     * Hyperlink at a specific coordinate exists?
2771
     *
2772
     * @param string $pCoordinate eg: 'A1'
2773
     *
2774
     * @return bool
2775
     */
2776 6
    public function hyperlinkExists($pCoordinate)
2777
    {
2778 6
        return isset($this->hyperlinkCollection[$pCoordinate]);
2779
    }
2780
2781
    /**
2782
     * Get collection of hyperlinks.
2783
     *
2784
     * @return Hyperlink[]
2785
     */
2786 62
    public function getHyperlinkCollection()
2787
    {
2788 62
        return $this->hyperlinkCollection;
2789
    }
2790
2791
    /**
2792
     * Get data validation.
2793
     *
2794
     * @param string $pCellCoordinate Cell coordinate to get data validation for, eg: 'A1'
2795
     */
2796 2
    public function getDataValidation($pCellCoordinate)
2797
    {
2798
        // return data validation if we already have one
2799 2
        if (isset($this->dataValidationCollection[$pCellCoordinate])) {
2800
            return $this->dataValidationCollection[$pCellCoordinate];
2801
        }
2802
2803
        // else create data validation
2804 2
        $this->dataValidationCollection[$pCellCoordinate] = new DataValidation();
2805
2806 2
        return $this->dataValidationCollection[$pCellCoordinate];
2807
    }
2808
2809
    /**
2810
     * Set data validation.
2811
     *
2812
     * @param string $pCellCoordinate Cell coordinate to insert data validation, eg: 'A1'
2813
     * @param null|DataValidation $pDataValidation
2814
     *
2815
     * @return Worksheet
2816
     */
2817
    public function setDataValidation($pCellCoordinate, DataValidation $pDataValidation = null)
2818
    {
2819
        if ($pDataValidation === null) {
2820
            unset($this->dataValidationCollection[$pCellCoordinate]);
2821
        } else {
2822
            $this->dataValidationCollection[$pCellCoordinate] = $pDataValidation;
2823
        }
2824
2825
        return $this;
2826
    }
2827
2828
    /**
2829
     * Data validation at a specific coordinate exists?
2830
     *
2831
     * @param string $pCoordinate eg: 'A1'
2832
     *
2833
     * @return bool
2834
     */
2835
    public function dataValidationExists($pCoordinate)
2836
    {
2837
        return isset($this->dataValidationCollection[$pCoordinate]);
2838
    }
2839
2840
    /**
2841
     * Get collection of data validations.
2842
     *
2843
     * @return DataValidation[]
2844
     */
2845 62
    public function getDataValidationCollection()
2846
    {
2847 62
        return $this->dataValidationCollection;
2848
    }
2849
2850
    /**
2851
     * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet.
2852
     *
2853
     * @param string $range
2854
     *
2855
     * @return string Adjusted range value
2856
     */
2857
    public function shrinkRangeToFit($range)
2858
    {
2859
        $maxCol = $this->getHighestColumn();
2860
        $maxRow = $this->getHighestRow();
2861
        $maxCol = Cell::columnIndexFromString($maxCol);
2862
2863
        $rangeBlocks = explode(' ', $range);
2864
        foreach ($rangeBlocks as &$rangeSet) {
2865
            $rangeBoundaries = Cell::getRangeBoundaries($rangeSet);
2866
2867 View Code Duplication
            if (Cell::columnIndexFromString($rangeBoundaries[0][0]) > $maxCol) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2868
                $rangeBoundaries[0][0] = Cell::stringFromColumnIndex($maxCol);
2869
            }
2870
            if ($rangeBoundaries[0][1] > $maxRow) {
2871
                $rangeBoundaries[0][1] = $maxRow;
2872
            }
2873 View Code Duplication
            if (Cell::columnIndexFromString($rangeBoundaries[1][0]) > $maxCol) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2874
                $rangeBoundaries[1][0] = Cell::stringFromColumnIndex($maxCol);
2875
            }
2876
            if ($rangeBoundaries[1][1] > $maxRow) {
2877
                $rangeBoundaries[1][1] = $maxRow;
2878
            }
2879
            $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1];
2880
        }
2881
        unset($rangeSet);
2882
        $stRange = implode(' ', $rangeBlocks);
2883
2884
        return $stRange;
2885
    }
2886
2887
    /**
2888
     * Get tab color.
2889
     *
2890
     * @return Color
2891
     */
2892 12
    public function getTabColor()
2893
    {
2894 12
        if ($this->tabColor === null) {
2895 12
            $this->tabColor = new Color();
2896
        }
2897
2898 12
        return $this->tabColor;
2899
    }
2900
2901
    /**
2902
     * Reset tab color.
2903
     *
2904
     * @return Worksheet
2905
     */
2906
    public function resetTabColor()
2907
    {
2908
        $this->tabColor = null;
2909
        unset($this->tabColor);
2910
2911
        return $this;
2912
    }
2913
2914
    /**
2915
     * Tab color set?
2916
     *
2917
     * @return bool
2918
     */
2919 67
    public function isTabColorSet()
2920
    {
2921 67
        return $this->tabColor !== null;
2922
    }
2923
2924
    /**
2925
     * Copy worksheet (!= clone!).
2926
     *
2927
     * @return Worksheet
2928
     */
2929
    public function copy()
2930
    {
2931
        $copied = clone $this;
2932
2933
        return $copied;
2934
    }
2935
2936
    /**
2937
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
2938
     */
2939 1
    public function __clone()
2940
    {
2941 1
        foreach ($this as $key => $val) {
0 ignored issues
show
Bug introduced by
The expression $this of type this<PhpOffice\PhpSpread...et\Worksheet\Worksheet> is not traversable.
Loading history...
2942 1
            if ($key == 'parent') {
2943 1
                continue;
2944
            }
2945
2946 1
            if (is_object($val) || (is_array($val))) {
2947 1
                if ($key == 'cellCollection') {
2948 1
                    $newCollection = $this->cellCollection->cloneCellCollection($this);
2949 1
                    $this->cellCollection = $newCollection;
2950 1
                } elseif ($key == 'drawingCollection') {
2951 1
                    $newCollection = new ArrayObject();
2952 1
                    foreach ($this->drawingCollection as $id => $item) {
2953
                        if (is_object($item)) {
2954
                            $newCollection[$id] = clone $this->drawingCollection[$id];
2955
                        }
2956
                    }
2957 1
                    $this->drawingCollection = $newCollection;
0 ignored issues
show
Documentation Bug introduced by
It seems like $newCollection of type object<ArrayObject> is incompatible with the declared type array<integer,object<Php...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...
2958 1
                } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) {
2959 1
                    $newAutoFilter = clone $this->autoFilter;
2960 1
                    $this->autoFilter = $newAutoFilter;
2961 1
                    $this->autoFilter->setParent($this);
2962
                } else {
2963 1
                    $this->{$key} = unserialize(serialize($val));
2964
                }
2965
            }
2966
        }
2967 1
    }
2968
2969
    /**
2970
     * Define the code name of the sheet.
2971
     *
2972
     * @param string $pValue Same rule as Title minus space not allowed (but, like Excel, change
2973
     *                       silently space to underscore)
2974
     * @param bool $validate False to skip validation of new title. WARNING: This should only be set
2975
     *                       at parse time (by Readers), where titles can be assumed to be valid.
2976
     *
2977
     * @throws Exception
2978
     *
2979
     * @return Worksheet
2980
     */
2981 150
    public function setCodeName($pValue, $validate = true)
2982
    {
2983
        // Is this a 'rename' or not?
2984 150
        if ($this->getCodeName() == $pValue) {
2985
            return $this;
2986
        }
2987
2988 150
        if ($validate) {
2989 150
            $pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same
2990
2991
            // Syntax check
2992
            // throw an exception if not valid
2993 150
            self::checkSheetCodeName($pValue);
2994
2995
            // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
2996
2997 150
            if ($this->getParent()) {
2998
                // Is there already such sheet name?
2999 134
                if ($this->getParent()->sheetCodeNameExists($pValue)) {
3000
                    // Use name, but append with lowest possible integer
3001
3002 39
                    if (Shared\StringHelper::countCharacters($pValue) > 29) {
3003
                        $pValue = Shared\StringHelper::substring($pValue, 0, 29);
3004
                    }
3005 39
                    $i = 1;
3006 39 View Code Duplication
                    while ($this->getParent()->sheetCodeNameExists($pValue . '_' . $i)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3007 10
                        ++$i;
3008 10
                        if ($i == 10) {
3009
                            if (Shared\StringHelper::countCharacters($pValue) > 28) {
3010
                                $pValue = Shared\StringHelper::substring($pValue, 0, 28);
3011
                            }
3012 10
                        } elseif ($i == 100) {
3013
                            if (Shared\StringHelper::countCharacters($pValue) > 27) {
3014
                                $pValue = Shared\StringHelper::substring($pValue, 0, 27);
3015
                            }
3016
                        }
3017
                    }
3018
3019 39
                    $pValue = $pValue . '_' . $i; // ok, we have a valid name
3020
                }
3021
            }
3022
        }
3023
3024 150
        $this->codeName = $pValue;
3025
3026 150
        return $this;
3027
    }
3028
3029
    /**
3030
     * Return the code name of the sheet.
3031
     *
3032
     * @return null|string
3033
     */
3034 150
    public function getCodeName()
3035
    {
3036 150
        return $this->codeName;
3037
    }
3038
3039
    /**
3040
     * Sheet has a code name ?
3041
     *
3042
     * @return bool
3043
     */
3044
    public function hasCodeName()
3045
    {
3046
        return !($this->codeName === null);
3047
    }
3048
}
3049