Completed
Push — develop ( 888c61...656149 )
by Adrien
35:11
created

Worksheet::calculateWorksheetDataDimension()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
ccs 0
cts 2
cp 0
crap 2
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 153
    public function __construct(Spreadsheet $parent = null, $pTitle = 'Worksheet')
333
    {
334
        // Set parent and title
335 153
        $this->parent = $parent;
336 153
        $this->setTitle($pTitle, false);
337
        // setTitle can change $pTitle
338 153
        $this->setCodeName($this->getTitle());
339 153
        $this->setSheetState(self::SHEETSTATE_VISIBLE);
340
341 153
        $this->cellCollection = CellsFactory::getInstance($this);
342
        // Set page setup
343 153
        $this->pageSetup = new PageSetup();
344
        // Set page margins
345 153
        $this->pageMargins = new PageMargins();
346
        // Set page header/footer
347 153
        $this->headerFooter = new HeaderFooter();
348
        // Set sheet view
349 153
        $this->sheetView = new SheetView();
350
        // Drawing collection
351 153
        $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 153
        $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 153
        $this->protection = new Protection();
356
        // Default row dimension
357 153
        $this->defaultRowDimension = new RowDimension(null);
358
        // Default column dimension
359 153
        $this->defaultColumnDimension = new ColumnDimension(null);
360 153
        $this->autoFilter = new AutoFilter(null, $this);
361 153
    }
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 117
    public function getCellCollection()
393
    {
394 117
        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 153
    private static function checkSheetCodeName($pValue)
417
    {
418 153
        $CharCount = Shared\StringHelper::countCharacters($pValue);
419 153
        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 153
        if ((str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) ||
424 153
            (Shared\StringHelper::substring($pValue, -1, 1) == '\'') ||
425 153
            (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 153
        if ($CharCount > 31) {
431 1
            throw new Exception('Maximum 31 characters allowed in sheet code name.');
432
        }
433
434 153
        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 153
    private static function checkSheetTitle($pValue)
447
    {
448
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ]
449 153
        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 153
        if (Shared\StringHelper::countCharacters($pValue) > 31) {
455 1
            throw new Exception('Maximum 31 characters allowed in sheet title.');
456
        }
457
458 153
        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 153
    public function getParent()
782
    {
783 153
        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 153
    public function getTitle()
816
    {
817 153
        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 153
    public function setTitle($pValue, $updateFormulaCellReferences = true, $validate = true)
835
    {
836
        // Is this a 'rename' or not?
837 153
        if ($this->getTitle() == $pValue) {
838 7
            return $this;
839
        }
840
841
        // Old title
842 153
        $oldTitle = $this->getTitle();
843
844 153
        if ($validate) {
845
            // Syntax check
846 153
            self::checkSheetTitle($pValue);
847
848 153
            if ($this->parent) {
849
                // Is there already such sheet name?
850 137
                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 153
        $this->title = $pValue;
877 153
        $this->dirty = true;
878
879 153
        if ($this->parent && $this->parent->getCalculationEngine()) {
880
            // New title
881 137
            $newTitle = $this->getTitle();
882 137
            $this->parent->getCalculationEngine()
883 137
                ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
884 137
            if ($updateFormulaCellReferences) {
885 31
                ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
886
            }
887
        }
888
889 153
        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 153
    public function setSheetState($value)
910
    {
911 153
        $this->sheetState = $value;
912
913 153
        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 116
    public function getCell($pCoordinate, $createIfNotExists = true)
1184
    {
1185
        // Check cell collection
1186 116
        if ($this->cellCollection->has(strtoupper($pCoordinate))) {
1187 104
            return $this->cellCollection->get($pCoordinate);
1188
        }
1189
1190
        // Worksheet reference?
1191 114 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 114
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1199 114
            (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 114
        $pCoordinate = strtoupper($pCoordinate);
1210
1211 114
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
1212
            throw new Exception('Cell coordinate can not be a range of cells.');
1213 114
        } elseif (strpos($pCoordinate, '$') !== false) {
1214
            throw new Exception('Cell coordinate must not be absolute.');
1215
        }
1216
1217
        // Create new cell object, if required
1218 114
        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 117
    private function createNewCell($pCoordinate)
1252
    {
1253 117
        $cell = new Cell(null, DataType::TYPE_NULL, $this);
1254 117
        $this->cellCollection->add($pCoordinate, $cell);
1255 117
        $this->cellCollectionIsSorted = false;
1256
1257
        // Coordinates
1258 117
        $aCoordinates = Cell::coordinateFromString($pCoordinate);
1259 117
        if (Cell::columnIndexFromString($this->cachedHighestColumn) < Cell::columnIndexFromString($aCoordinates[0])) {
1260 101
            $this->cachedHighestColumn = $aCoordinates[0];
1261
        }
1262 117
        $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 117
        $rowDimension = $this->getRowDimension($aCoordinates[1], false);
1267 117
        $columnDimension = $this->getColumnDimension($aCoordinates[0], false);
1268
1269 117
        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 117
        } 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 117
        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 72
    public function cellExists($pCoordinate)
1290
    {
1291
        // Worksheet reference?
1292 72 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 72
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1300 72
            (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 72
        $pCoordinate = strtoupper($pCoordinate);
1318
1319 72
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
1320
            throw new Exception('Cell coordinate can not be a range of cells.');
1321 72
        } elseif (strpos($pCoordinate, '$') !== false) {
1322
            throw new Exception('Cell coordinate must not be absolute.');
1323
        }
1324
1325
        // Cell exists?
1326 72
        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 bool $create
1347
     *
1348
     * @return RowDimension
1349
     */
1350 117
    public function getRowDimension($pRow, $create = true)
1351
    {
1352
        // Found
1353 117
        $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 117
        if (!isset($this->rowDimensions[$pRow])) {
1357 117
            if (!$create) {
1358 103
                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 bool $create
1373
     *
1374
     * @return ColumnDimension
1375
     */
1376 117
    public function getColumnDimension($pColumn, $create = true)
1377
    {
1378
        // Uppercase coordinate
1379 117
        $pColumn = strtoupper($pColumn);
1380
1381
        // Fetch dimensions
1382 117
        if (!isset($this->columnDimensions[$pColumn])) {
1383 117
            if (!$create) {
1384 113
                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 AutoFilter|string $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
        if (is_string($pValue)) {
1923 4
            $this->autoFilter->setRange($pValue);
1924
        } elseif (is_object($pValue) && ($pValue instanceof AutoFilter)) {
1925
            $this->autoFilter = $pValue;
1926
        }
1927
1928 4
        return $this;
1929
    }
1930
1931
    /**
1932
     * Set Autofilter Range by using numeric cell coordinates.
1933
     *
1934
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1935
     * @param int $pRow1 Numeric row coordinate of the first cell
1936
     * @param int $pColumn2 Numeric column coordinate of the second cell (A = 0)
1937
     * @param int $pRow2 Numeric row coordinate of the second cell
1938
     *
1939
     * @throws Exception
1940
     *
1941
     * @return Worksheet
1942
     */
1943
    public function setAutoFilterByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2)
1944
    {
1945
        return $this->setAutoFilter(
1946
            Cell::stringFromColumnIndex($pColumn1) . $pRow1
1947
            . ':' .
1948
            Cell::stringFromColumnIndex($pColumn2) . $pRow2
1949
        );
1950
    }
1951
1952
    /**
1953
     * Remove autofilter.
1954
     *
1955
     * @return Worksheet
1956
     */
1957
    public function removeAutoFilter()
1958
    {
1959
        $this->autoFilter->setRange(null);
1960
1961
        return $this;
1962
    }
1963
1964
    /**
1965
     * Get Freeze Pane.
1966
     *
1967
     * @return string
1968
     */
1969 62
    public function getFreezePane()
1970
    {
1971 62
        return $this->freezePane;
1972
    }
1973
1974
    /**
1975
     * Freeze Pane.
1976
     *
1977
     * @param string $pCell Cell (i.e. A2)
1978
     *                                    Examples:
1979
     *                                        A2 will freeze the rows above cell A2 (i.e row 1)
1980
     *                                        B1 will freeze the columns to the left of cell B1 (i.e column A)
1981
     *                                        B2 will freeze the rows above and to the left of cell A2
1982
     *                                            (i.e row 1 and column A)
1983
     *
1984
     * @throws Exception
1985
     *
1986
     * @return Worksheet
1987
     */
1988 4
    public function freezePane($pCell)
1989
    {
1990
        // Uppercase coordinate
1991 4
        $pCell = strtoupper($pCell);
1992 4
        if (strpos($pCell, ':') === false && strpos($pCell, ',') === false) {
1993 4
            $this->freezePane = $pCell;
1994
        } else {
1995
            throw new Exception('Freeze pane can not be set on a range of cells.');
1996
        }
1997
1998 4
        return $this;
1999
    }
2000
2001
    /**
2002
     * Freeze Pane by using numeric cell coordinates.
2003
     *
2004
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
2005
     * @param int $pRow Numeric row coordinate of the cell
2006
     *
2007
     * @throws Exception
2008
     *
2009
     * @return Worksheet
2010
     */
2011
    public function freezePaneByColumnAndRow($pColumn, $pRow)
2012
    {
2013
        return $this->freezePane(Cell::stringFromColumnIndex($pColumn) . $pRow);
2014
    }
2015
2016
    /**
2017
     * Unfreeze Pane.
2018
     *
2019
     * @return Worksheet
2020
     */
2021
    public function unfreezePane()
2022
    {
2023
        return $this->freezePane('');
2024
    }
2025
2026
    /**
2027
     * Insert a new row, updating all possible related data.
2028
     *
2029
     * @param int $pBefore Insert before this one
2030
     * @param int $pNumRows Number of rows to insert
2031
     *
2032
     * @throws Exception
2033
     *
2034
     * @return Worksheet
2035
     */
2036 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...
2037
    {
2038 13
        if ($pBefore >= 1) {
2039 13
            $objReferenceHelper = ReferenceHelper::getInstance();
2040 13
            $objReferenceHelper->insertNewBefore('A' . $pBefore, 0, $pNumRows, $this);
2041
        } else {
2042
            throw new Exception('Rows can only be inserted before at least row 1.');
2043
        }
2044
2045 13
        return $this;
2046
    }
2047
2048
    /**
2049
     * Insert a new column, updating all possible related data.
2050
     *
2051
     * @param int $pBefore Insert before this one, eg: 'A'
2052
     * @param int $pNumCols Number of columns to insert
2053
     *
2054
     * @throws Exception
2055
     *
2056
     * @return Worksheet
2057
     */
2058 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...
2059
    {
2060 12
        if (!is_numeric($pBefore)) {
2061 12
            $objReferenceHelper = ReferenceHelper::getInstance();
2062 12
            $objReferenceHelper->insertNewBefore($pBefore . '1', $pNumCols, 0, $this);
2063
        } else {
2064
            throw new Exception('Column references should not be numeric.');
2065
        }
2066
2067 12
        return $this;
2068
    }
2069
2070
    /**
2071
     * Insert a new column, updating all possible related data.
2072
     *
2073
     * @param int $pBefore Insert before this one (numeric column coordinate of the cell, A = 0)
2074
     * @param int $pNumCols Number of columns to insert
2075
     *
2076
     * @throws Exception
2077
     *
2078
     * @return Worksheet
2079
     */
2080
    public function insertNewColumnBeforeByIndex($pBefore, $pNumCols = 1)
2081
    {
2082
        if ($pBefore >= 0) {
2083
            return $this->insertNewColumnBefore(Cell::stringFromColumnIndex($pBefore), $pNumCols);
2084
        }
2085
2086
        throw new Exception('Columns can only be inserted before at least column A (0).');
2087
    }
2088
2089
    /**
2090
     * Delete a row, updating all possible related data.
2091
     *
2092
     * @param int $pRow Remove starting with this one
2093
     * @param int $pNumRows Number of rows to remove
2094
     *
2095
     * @throws Exception
2096
     *
2097
     * @return Worksheet
2098
     */
2099 15
    public function removeRow($pRow, $pNumRows = 1)
2100
    {
2101 15
        if ($pRow >= 1) {
2102 15
            $highestRow = $this->getHighestDataRow();
2103 15
            $objReferenceHelper = ReferenceHelper::getInstance();
2104 15
            $objReferenceHelper->insertNewBefore('A' . ($pRow + $pNumRows), 0, -$pNumRows, $this);
2105 15
            for ($r = 0; $r < $pNumRows; ++$r) {
2106 15
                $this->getCellCollection()->removeRow($highestRow);
2107 15
                --$highestRow;
2108
            }
2109
        } else {
2110
            throw new Exception('Rows to be deleted should at least start from row 1.');
2111
        }
2112
2113 15
        return $this;
2114
    }
2115
2116
    /**
2117
     * Remove a column, updating all possible related data.
2118
     *
2119
     * @param string $pColumn Remove starting with this one, eg: 'A'
2120
     * @param int $pNumCols Number of columns to remove
2121
     *
2122
     * @throws Exception
2123
     *
2124
     * @return Worksheet
2125
     */
2126 12
    public function removeColumn($pColumn, $pNumCols = 1)
2127
    {
2128 12
        if (!is_numeric($pColumn)) {
2129 12
            $highestColumn = $this->getHighestDataColumn();
2130 12
            $pColumn = Cell::stringFromColumnIndex(Cell::columnIndexFromString($pColumn) - 1 + $pNumCols);
2131 12
            $objReferenceHelper = ReferenceHelper::getInstance();
2132 12
            $objReferenceHelper->insertNewBefore($pColumn . '1', -$pNumCols, 0, $this);
2133 12
            for ($c = 0; $c < $pNumCols; ++$c) {
2134 12
                $this->getCellCollection()->removeColumn($highestColumn);
2135 12
                $highestColumn = Cell::stringFromColumnIndex(Cell::columnIndexFromString($highestColumn) - 2);
2136
            }
2137
        } else {
2138
            throw new Exception('Column references should not be numeric.');
2139
        }
2140
2141 12
        return $this;
2142
    }
2143
2144
    /**
2145
     * Remove a column, updating all possible related data.
2146
     *
2147
     * @param int $pColumn Remove starting with this one (numeric column coordinate of the cell A = 0)
2148
     * @param int $pNumCols Number of columns to remove
2149
     *
2150
     * @throws Exception
2151
     *
2152
     * @return Worksheet
2153
     */
2154
    public function removeColumnByIndex($pColumn, $pNumCols = 1)
2155
    {
2156
        if ($pColumn >= 0) {
2157
            return $this->removeColumn(Cell::stringFromColumnIndex($pColumn), $pNumCols);
2158
        }
2159
2160
        throw new Exception('Columns to be deleted should at least start from column 0');
2161
    }
2162
2163
    /**
2164
     * Show gridlines?
2165
     *
2166
     * @return bool
2167
     */
2168 62
    public function getShowGridlines()
2169
    {
2170 62
        return $this->showGridlines;
2171
    }
2172
2173
    /**
2174
     * Set show gridlines.
2175
     *
2176
     * @param bool $pValue Show gridlines (true/false)
2177
     *
2178
     * @return Worksheet
2179
     */
2180 26
    public function setShowGridlines($pValue)
2181
    {
2182 26
        $this->showGridlines = $pValue;
2183
2184 26
        return $this;
2185
    }
2186
2187
    /**
2188
     * Print gridlines?
2189
     *
2190
     * @return bool
2191
     */
2192 58
    public function getPrintGridlines()
2193
    {
2194 58
        return $this->printGridlines;
2195
    }
2196
2197
    /**
2198
     * Set print gridlines.
2199
     *
2200
     * @param bool $pValue Print gridlines (true/false)
2201
     *
2202
     * @return Worksheet
2203
     */
2204 17
    public function setPrintGridlines($pValue)
2205
    {
2206 17
        $this->printGridlines = $pValue;
2207
2208 17
        return $this;
2209
    }
2210
2211
    /**
2212
     * Show row and column headers?
2213
     *
2214
     * @return bool
2215
     */
2216 58
    public function getShowRowColHeaders()
2217
    {
2218 58
        return $this->showRowColHeaders;
2219
    }
2220
2221
    /**
2222
     * Set show row and column headers.
2223
     *
2224
     * @param bool $pValue Show row and column headers (true/false)
2225
     *
2226
     * @return Worksheet
2227
     */
2228 23
    public function setShowRowColHeaders($pValue)
2229
    {
2230 23
        $this->showRowColHeaders = $pValue;
2231
2232 23
        return $this;
2233
    }
2234
2235
    /**
2236
     * Show summary below? (Row/Column outlining).
2237
     *
2238
     * @return bool
2239
     */
2240 58
    public function getShowSummaryBelow()
2241
    {
2242 58
        return $this->showSummaryBelow;
2243
    }
2244
2245
    /**
2246
     * Set show summary below.
2247
     *
2248
     * @param bool $pValue Show summary below (true/false)
2249
     *
2250
     * @return Worksheet
2251
     */
2252 23
    public function setShowSummaryBelow($pValue)
2253
    {
2254 23
        $this->showSummaryBelow = $pValue;
2255
2256 23
        return $this;
2257
    }
2258
2259
    /**
2260
     * Show summary right? (Row/Column outlining).
2261
     *
2262
     * @return bool
2263
     */
2264 58
    public function getShowSummaryRight()
2265
    {
2266 58
        return $this->showSummaryRight;
2267
    }
2268
2269
    /**
2270
     * Set show summary right.
2271
     *
2272
     * @param bool $pValue Show summary right (true/false)
2273
     *
2274
     * @return Worksheet
2275
     */
2276 23
    public function setShowSummaryRight($pValue)
2277
    {
2278 23
        $this->showSummaryRight = $pValue;
2279
2280 23
        return $this;
2281
    }
2282
2283
    /**
2284
     * Get comments.
2285
     *
2286
     * @return Comment[]
2287
     */
2288 63
    public function getComments()
2289
    {
2290 63
        return $this->comments;
2291
    }
2292
2293
    /**
2294
     * Set comments array for the entire sheet.
2295
     *
2296
     * @param array of Comment
2297
     * @param mixed $pValue
2298
     *
2299
     * @return Worksheet
2300
     */
2301 15
    public function setComments(array $pValue)
2302
    {
2303 15
        $this->comments = $pValue;
2304
2305 15
        return $this;
2306
    }
2307
2308
    /**
2309
     * Get comment for cell.
2310
     *
2311
     * @param string $pCellCoordinate Cell coordinate to get comment for, eg: 'A1'
2312
     *
2313
     * @throws Exception
2314
     *
2315
     * @return Comment
2316
     */
2317 21
    public function getComment($pCellCoordinate)
2318
    {
2319
        // Uppercase coordinate
2320 21
        $pCellCoordinate = strtoupper($pCellCoordinate);
2321
2322 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...
2323
            throw new Exception('Cell coordinate string can not be a range of cells.');
2324 21
        } elseif (strpos($pCellCoordinate, '$') !== false) {
2325
            throw new Exception('Cell coordinate string must not be absolute.');
2326 21
        } elseif ($pCellCoordinate == '') {
2327
            throw new Exception('Cell coordinate can not be zero-length string.');
2328
        }
2329
2330
        // Check if we already have a comment for this cell.
2331 21
        if (isset($this->comments[$pCellCoordinate])) {
2332 13
            return $this->comments[$pCellCoordinate];
2333
        }
2334
2335
        // If not, create a new comment.
2336 21
        $newComment = new Comment();
2337 21
        $this->comments[$pCellCoordinate] = $newComment;
2338
2339 21
        return $newComment;
2340
    }
2341
2342
    /**
2343
     * Get comment for cell by using numeric cell coordinates.
2344
     *
2345
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
2346
     * @param int $pRow Numeric row coordinate of the cell
2347
     *
2348
     * @return Comment
2349
     */
2350 2
    public function getCommentByColumnAndRow($pColumn, $pRow)
2351
    {
2352 2
        return $this->getComment(Cell::stringFromColumnIndex($pColumn) . $pRow);
2353
    }
2354
2355
    /**
2356
     * Get active cell.
2357
     *
2358
     * @return string Example: 'A1'
2359
     */
2360 63
    public function getActiveCell()
2361
    {
2362 63
        return $this->activeCell;
2363
    }
2364
2365
    /**
2366
     * Get selected cells.
2367
     *
2368
     * @return string
2369
     */
2370 62
    public function getSelectedCells()
2371
    {
2372 62
        return $this->selectedCells;
2373
    }
2374
2375
    /**
2376
     * Selected cell.
2377
     *
2378
     * @param string $pCoordinate Cell (i.e. A1)
2379
     *
2380
     * @return Worksheet
2381
     */
2382
    public function setSelectedCell($pCoordinate)
2383
    {
2384
        return $this->setSelectedCells($pCoordinate);
2385
    }
2386
2387
    /**
2388
     * Select a range of cells.
2389
     *
2390
     * @param string $pCoordinate Cell range, examples: 'A1', 'B2:G5', 'A:C', '3:6'
2391
     *
2392
     * @throws Exception
2393
     *
2394
     * @return Worksheet
2395
     */
2396 70
    public function setSelectedCells($pCoordinate)
2397
    {
2398
        // Uppercase coordinate
2399 70
        $pCoordinate = strtoupper($pCoordinate);
2400
2401
        // Convert 'A' to 'A:A'
2402 70
        $pCoordinate = preg_replace('/^([A-Z]+)$/', '${1}:${1}', $pCoordinate);
2403
2404
        // Convert '1' to '1:1'
2405 70
        $pCoordinate = preg_replace('/^([0-9]+)$/', '${1}:${1}', $pCoordinate);
2406
2407
        // Convert 'A:C' to 'A1:C1048576'
2408 70
        $pCoordinate = preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $pCoordinate);
2409
2410
        // Convert '1:3' to 'A1:XFD3'
2411 70
        $pCoordinate = preg_replace('/^([0-9]+):([0-9]+)$/', 'A${1}:XFD${2}', $pCoordinate);
2412
2413 70
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
2414 45
            list($first) = Cell::splitRange($pCoordinate);
2415 45
            $this->activeCell = $first[0];
2416
        } else {
2417 58
            $this->activeCell = $pCoordinate;
2418
        }
2419 70
        $this->selectedCells = $pCoordinate;
2420
2421 70
        return $this;
2422
    }
2423
2424
    /**
2425
     * Selected cell by using numeric cell coordinates.
2426
     *
2427
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
2428
     * @param int $pRow Numeric row coordinate of the cell
2429
     *
2430
     * @throws Exception
2431
     *
2432
     * @return Worksheet
2433
     */
2434
    public function setSelectedCellByColumnAndRow($pColumn, $pRow)
2435
    {
2436
        return $this->setSelectedCells(Cell::stringFromColumnIndex($pColumn) . $pRow);
2437
    }
2438
2439
    /**
2440
     * Get right-to-left.
2441
     *
2442
     * @return bool
2443
     */
2444 58
    public function getRightToLeft()
2445
    {
2446 58
        return $this->rightToLeft;
2447
    }
2448
2449
    /**
2450
     * Set right-to-left.
2451
     *
2452
     * @param bool $value Right-to-left true/false
2453
     *
2454
     * @return Worksheet
2455
     */
2456 18
    public function setRightToLeft($value)
2457
    {
2458 18
        $this->rightToLeft = $value;
2459
2460 18
        return $this;
2461
    }
2462
2463
    /**
2464
     * Fill worksheet from values in array.
2465
     *
2466
     * @param array $source Source array
2467
     * @param mixed $nullValue Value in source array that stands for blank cell
2468
     * @param string $startCell Insert array starting from this cell address as the top left coordinate
2469
     * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array
2470
     *
2471
     * @throws Exception
2472
     *
2473
     * @return Worksheet
2474
     */
2475 30
    public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false)
2476
    {
2477
        //    Convert a 1-D array to 2-D (for ease of looping)
2478 30
        if (!is_array(end($source))) {
2479 3
            $source = [$source];
2480
        }
2481
2482
        // start coordinate
2483 30
        list($startColumn, $startRow) = Cell::coordinateFromString($startCell);
2484
2485
        // Loop through $source
2486 30
        foreach ($source as $rowData) {
2487 30
            $currentColumn = $startColumn;
2488 30
            foreach ($rowData as $cellValue) {
2489 30
                if ($strictNullComparison) {
2490 3
                    if ($cellValue !== $nullValue) {
2491
                        // Set cell value
2492 3
                        $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2493
                    }
2494
                } else {
2495 27
                    if ($cellValue != $nullValue) {
2496
                        // Set cell value
2497 27
                        $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2498
                    }
2499
                }
2500 30
                ++$currentColumn;
2501
            }
2502 30
            ++$startRow;
2503
        }
2504
2505 30
        return $this;
2506
    }
2507
2508
    /**
2509
     * Create array from a range of cells.
2510
     *
2511
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
2512
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2513
     * @param bool $calculateFormulas Should formulas be calculated?
2514
     * @param bool $formatData Should formatting be applied to cell values?
2515
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2516
     *                               True - Return rows and columns indexed by their actual row and column IDs
2517
     *
2518
     * @return array
2519
     */
2520 25
    public function rangeToArray($pRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2521
    {
2522
        // Returnvalue
2523 25
        $returnValue = [];
2524
        //    Identify the range that we need to extract from the worksheet
2525 25
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange);
2526 25
        $minCol = Cell::stringFromColumnIndex($rangeStart[0] - 1);
2527 25
        $minRow = $rangeStart[1];
2528 25
        $maxCol = Cell::stringFromColumnIndex($rangeEnd[0] - 1);
2529 25
        $maxRow = $rangeEnd[1];
2530
2531 25
        ++$maxCol;
2532
        // Loop through rows
2533 25
        $r = -1;
2534 25
        for ($row = $minRow; $row <= $maxRow; ++$row) {
2535 25
            $rRef = ($returnCellRef) ? $row : ++$r;
2536 25
            $c = -1;
2537
            // Loop through columns in the current row
2538 25
            for ($col = $minCol; $col != $maxCol; ++$col) {
2539 25
                $cRef = ($returnCellRef) ? $col : ++$c;
2540
                //    Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
2541
                //        so we test and retrieve directly against cellCollection
2542 25
                if ($this->cellCollection->has($col . $row)) {
2543
                    // Cell exists
2544 25
                    $cell = $this->cellCollection->get($col . $row);
2545 25
                    if ($cell->getValue() !== null) {
2546 25
                        if ($cell->getValue() instanceof RichText) {
2547 2
                            $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
2548
                        } else {
2549 25
                            if ($calculateFormulas) {
2550 24
                                $returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
2551
                            } else {
2552 2
                                $returnValue[$rRef][$cRef] = $cell->getValue();
2553
                            }
2554
                        }
2555
2556 25
                        if ($formatData) {
2557 24
                            $style = $this->parent->getCellXfByIndex($cell->getXfIndex());
2558 24
                            $returnValue[$rRef][$cRef] = NumberFormat::toFormattedString(
2559 24
                                $returnValue[$rRef][$cRef],
2560 25
                                ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL
2561
                            );
2562
                        }
2563
                    } else {
2564
                        // Cell holds a NULL
2565 25
                        $returnValue[$rRef][$cRef] = $nullValue;
2566
                    }
2567
                } else {
2568
                    // Cell doesn't exist
2569 12
                    $returnValue[$rRef][$cRef] = $nullValue;
2570
                }
2571
            }
2572
        }
2573
2574
        // Return
2575 25
        return $returnValue;
2576
    }
2577
2578
    /**
2579
     * Create array from a range of cells.
2580
     *
2581
     * @param string $pNamedRange Name of the Named Range
2582
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2583
     * @param bool $calculateFormulas Should formulas be calculated?
2584
     * @param bool $formatData Should formatting be applied to cell values?
2585
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2586
     *                                True - Return rows and columns indexed by their actual row and column IDs
2587
     *
2588
     * @throws Exception
2589
     *
2590
     * @return array
2591
     */
2592
    public function namedRangeToArray($pNamedRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2593
    {
2594
        $namedRange = NamedRange::resolveRange($pNamedRange, $this);
2595
        if ($namedRange !== null) {
2596
            $pWorkSheet = $namedRange->getWorksheet();
2597
            $pCellRange = $namedRange->getRange();
2598
2599
            return $pWorkSheet->rangeToArray($pCellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2600
        }
2601
2602
        throw new Exception('Named Range ' . $pNamedRange . ' does not exist.');
2603
    }
2604
2605
    /**
2606
     * Create array from worksheet.
2607
     *
2608
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2609
     * @param bool $calculateFormulas Should formulas be calculated?
2610
     * @param bool $formatData Should formatting be applied to cell values?
2611
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2612
     *                               True - Return rows and columns indexed by their actual row and column IDs
2613
     *
2614
     * @return array
2615
     */
2616 12
    public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2617
    {
2618
        // Garbage collect...
2619 12
        $this->garbageCollect();
2620
2621
        //    Identify the range that we need to extract from the worksheet
2622 12
        $maxCol = $this->getHighestColumn();
2623 12
        $maxRow = $this->getHighestRow();
2624
        // Return
2625 12
        return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2626
    }
2627
2628
    /**
2629
     * Get row iterator.
2630
     *
2631
     * @param int $startRow The row number at which to start iterating
2632
     * @param int $endRow The row number at which to stop iterating
2633
     *
2634
     * @return RowIterator
2635
     */
2636 4
    public function getRowIterator($startRow = 1, $endRow = null)
2637
    {
2638 4
        return new RowIterator($this, $startRow, $endRow);
2639
    }
2640
2641
    /**
2642
     * Get column iterator.
2643
     *
2644
     * @param string $startColumn The column address at which to start iterating
2645
     * @param string $endColumn The column address at which to stop iterating
2646
     *
2647
     * @return ColumnIterator
2648
     */
2649
    public function getColumnIterator($startColumn = 'A', $endColumn = null)
2650
    {
2651
        return new ColumnIterator($this, $startColumn, $endColumn);
2652
    }
2653
2654
    /**
2655
     * Run PhpSpreadsheet garabage collector.
2656
     *
2657
     * @return Worksheet
2658
     */
2659 74
    public function garbageCollect()
2660
    {
2661
        // Flush cache
2662 74
        $this->cellCollection->get('A1');
2663
2664
        // Lookup highest column and highest row if cells are cleaned
2665 74
        $colRow = $this->cellCollection->getHighestRowAndColumn();
2666 74
        $highestRow = $colRow['row'];
2667 74
        $highestColumn = Cell::columnIndexFromString($colRow['column']);
2668
2669
        // Loop through column dimensions
2670 74
        foreach ($this->columnDimensions as $dimension) {
2671 33
            $highestColumn = max($highestColumn, Cell::columnIndexFromString($dimension->getColumnIndex()));
2672
        }
2673
2674
        // Loop through row dimensions
2675 74
        foreach ($this->rowDimensions as $dimension) {
2676 47
            $highestRow = max($highestRow, $dimension->getRowIndex());
2677
        }
2678
2679
        // Cache values
2680 74
        if ($highestColumn < 0) {
2681
            $this->cachedHighestColumn = 'A';
2682
        } else {
2683 74
            $this->cachedHighestColumn = Cell::stringFromColumnIndex(--$highestColumn);
2684
        }
2685 74
        $this->cachedHighestRow = $highestRow;
2686
2687
        // Return
2688 74
        return $this;
2689
    }
2690
2691
    /**
2692
     * Get hash code.
2693
     *
2694
     * @return string Hash code
2695
     */
2696 90
    public function getHashCode()
2697
    {
2698 90
        if ($this->dirty) {
2699 90
            $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__);
2700 90
            $this->dirty = false;
2701
        }
2702
2703 90
        return $this->hash;
2704
    }
2705
2706
    /**
2707
     * Extract worksheet title from range.
2708
     *
2709
     * Example: extractSheetTitle("testSheet!A1") ==> 'A1'
2710
     * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> array('testSheet 1', 'A1');
2711
     *
2712
     * @param string $pRange Range to extract title from
2713
     * @param bool $returnRange Return range? (see example)
2714
     *
2715
     * @return mixed
2716
     */
2717 1
    public static function extractSheetTitle($pRange, $returnRange = false)
2718
    {
2719
        // Sheet title included?
2720 1
        if (($sep = strpos($pRange, '!')) === false) {
2721
            return '';
2722
        }
2723
2724 1
        if ($returnRange) {
2725 1
            return [trim(substr($pRange, 0, $sep), "'"), substr($pRange, $sep + 1)];
2726
        }
2727
2728
        return substr($pRange, $sep + 1);
2729
    }
2730
2731
    /**
2732
     * Get hyperlink.
2733
     *
2734
     * @param string $pCellCoordinate Cell coordinate to get hyperlink for, eg: 'A1'
2735
     */
2736 21
    public function getHyperlink($pCellCoordinate)
2737
    {
2738
        // return hyperlink if we already have one
2739 21
        if (isset($this->hyperlinkCollection[$pCellCoordinate])) {
2740 15
            return $this->hyperlinkCollection[$pCellCoordinate];
2741
        }
2742
2743
        // else create hyperlink
2744 21
        $this->hyperlinkCollection[$pCellCoordinate] = new Hyperlink();
2745
2746 21
        return $this->hyperlinkCollection[$pCellCoordinate];
2747
    }
2748
2749
    /**
2750
     * Set hyperlink.
2751
     *
2752
     * @param string $pCellCoordinate Cell coordinate to insert hyperlink, eg: 'A1'
2753
     * @param null|Hyperlink $pHyperlink
2754
     *
2755
     * @return Worksheet
2756
     */
2757 13
    public function setHyperlink($pCellCoordinate, Hyperlink $pHyperlink = null)
2758
    {
2759 13
        if ($pHyperlink === null) {
2760 13
            unset($this->hyperlinkCollection[$pCellCoordinate]);
2761
        } else {
2762 13
            $this->hyperlinkCollection[$pCellCoordinate] = $pHyperlink;
2763
        }
2764
2765 13
        return $this;
2766
    }
2767
2768
    /**
2769
     * Hyperlink at a specific coordinate exists?
2770
     *
2771
     * @param string $pCoordinate eg: 'A1'
2772
     *
2773
     * @return bool
2774
     */
2775 6
    public function hyperlinkExists($pCoordinate)
2776
    {
2777 6
        return isset($this->hyperlinkCollection[$pCoordinate]);
2778
    }
2779
2780
    /**
2781
     * Get collection of hyperlinks.
2782
     *
2783
     * @return Hyperlink[]
2784
     */
2785 62
    public function getHyperlinkCollection()
2786
    {
2787 62
        return $this->hyperlinkCollection;
2788
    }
2789
2790
    /**
2791
     * Get data validation.
2792
     *
2793
     * @param string $pCellCoordinate Cell coordinate to get data validation for, eg: 'A1'
2794
     */
2795 4
    public function getDataValidation($pCellCoordinate)
2796
    {
2797
        // return data validation if we already have one
2798 4
        if (isset($this->dataValidationCollection[$pCellCoordinate])) {
2799 2
            return $this->dataValidationCollection[$pCellCoordinate];
2800
        }
2801
2802
        // else create data validation
2803 4
        $this->dataValidationCollection[$pCellCoordinate] = new DataValidation();
2804
2805 4
        return $this->dataValidationCollection[$pCellCoordinate];
2806
    }
2807
2808
    /**
2809
     * Set data validation.
2810
     *
2811
     * @param string $pCellCoordinate Cell coordinate to insert data validation, eg: 'A1'
2812
     * @param null|DataValidation $pDataValidation
2813
     *
2814
     * @return Worksheet
2815
     */
2816
    public function setDataValidation($pCellCoordinate, DataValidation $pDataValidation = null)
2817
    {
2818
        if ($pDataValidation === null) {
2819
            unset($this->dataValidationCollection[$pCellCoordinate]);
2820
        } else {
2821
            $this->dataValidationCollection[$pCellCoordinate] = $pDataValidation;
2822
        }
2823
2824
        return $this;
2825
    }
2826
2827
    /**
2828
     * Data validation at a specific coordinate exists?
2829
     *
2830
     * @param string $pCoordinate eg: 'A1'
2831
     *
2832
     * @return bool
2833
     */
2834 3
    public function dataValidationExists($pCoordinate)
2835
    {
2836 3
        return isset($this->dataValidationCollection[$pCoordinate]);
2837
    }
2838
2839
    /**
2840
     * Get collection of data validations.
2841
     *
2842
     * @return DataValidation[]
2843
     */
2844 62
    public function getDataValidationCollection()
2845
    {
2846 62
        return $this->dataValidationCollection;
2847
    }
2848
2849
    /**
2850
     * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet.
2851
     *
2852
     * @param string $range
2853
     *
2854
     * @return string Adjusted range value
2855
     */
2856
    public function shrinkRangeToFit($range)
2857
    {
2858
        $maxCol = $this->getHighestColumn();
2859
        $maxRow = $this->getHighestRow();
2860
        $maxCol = Cell::columnIndexFromString($maxCol);
2861
2862
        $rangeBlocks = explode(' ', $range);
2863
        foreach ($rangeBlocks as &$rangeSet) {
2864
            $rangeBoundaries = Cell::getRangeBoundaries($rangeSet);
2865
2866 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...
2867
                $rangeBoundaries[0][0] = Cell::stringFromColumnIndex($maxCol);
2868
            }
2869
            if ($rangeBoundaries[0][1] > $maxRow) {
2870
                $rangeBoundaries[0][1] = $maxRow;
2871
            }
2872 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...
2873
                $rangeBoundaries[1][0] = Cell::stringFromColumnIndex($maxCol);
2874
            }
2875
            if ($rangeBoundaries[1][1] > $maxRow) {
2876
                $rangeBoundaries[1][1] = $maxRow;
2877
            }
2878
            $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1];
2879
        }
2880
        unset($rangeSet);
2881
        $stRange = implode(' ', $rangeBlocks);
2882
2883
        return $stRange;
2884
    }
2885
2886
    /**
2887
     * Get tab color.
2888
     *
2889
     * @return Color
2890
     */
2891 12
    public function getTabColor()
2892
    {
2893 12
        if ($this->tabColor === null) {
2894 12
            $this->tabColor = new Color();
2895
        }
2896
2897 12
        return $this->tabColor;
2898
    }
2899
2900
    /**
2901
     * Reset tab color.
2902
     *
2903
     * @return Worksheet
2904
     */
2905
    public function resetTabColor()
2906
    {
2907
        $this->tabColor = null;
2908
        unset($this->tabColor);
2909
2910
        return $this;
2911
    }
2912
2913
    /**
2914
     * Tab color set?
2915
     *
2916
     * @return bool
2917
     */
2918 67
    public function isTabColorSet()
2919
    {
2920 67
        return $this->tabColor !== null;
2921
    }
2922
2923
    /**
2924
     * Copy worksheet (!= clone!).
2925
     *
2926
     * @return Worksheet
2927
     */
2928
    public function copy()
2929
    {
2930
        $copied = clone $this;
2931
2932
        return $copied;
2933
    }
2934
2935
    /**
2936
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
2937
     */
2938 1
    public function __clone()
2939
    {
2940 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...
2941 1
            if ($key == 'parent') {
2942 1
                continue;
2943
            }
2944
2945 1
            if (is_object($val) || (is_array($val))) {
2946 1
                if ($key == 'cellCollection') {
2947 1
                    $newCollection = $this->cellCollection->cloneCellCollection($this);
2948 1
                    $this->cellCollection = $newCollection;
2949 1
                } elseif ($key == 'drawingCollection') {
2950 1
                    $newCollection = new ArrayObject();
2951 1
                    foreach ($this->drawingCollection as $id => $item) {
2952
                        if (is_object($item)) {
2953
                            $newCollection[$id] = clone $this->drawingCollection[$id];
2954
                        }
2955
                    }
2956 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...
2957 1
                } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) {
2958 1
                    $newAutoFilter = clone $this->autoFilter;
2959 1
                    $this->autoFilter = $newAutoFilter;
2960 1
                    $this->autoFilter->setParent($this);
2961
                } else {
2962 1
                    $this->{$key} = unserialize(serialize($val));
2963
                }
2964
            }
2965
        }
2966 1
    }
2967
2968
    /**
2969
     * Define the code name of the sheet.
2970
     *
2971
     * @param string $pValue Same rule as Title minus space not allowed (but, like Excel, change
2972
     *                       silently space to underscore)
2973
     * @param bool $validate False to skip validation of new title. WARNING: This should only be set
2974
     *                       at parse time (by Readers), where titles can be assumed to be valid.
2975
     *
2976
     * @throws Exception
2977
     *
2978
     * @return Worksheet
2979
     */
2980 153
    public function setCodeName($pValue, $validate = true)
2981
    {
2982
        // Is this a 'rename' or not?
2983 153
        if ($this->getCodeName() == $pValue) {
2984
            return $this;
2985
        }
2986
2987 153
        if ($validate) {
2988 153
            $pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same
2989
2990
            // Syntax check
2991
            // throw an exception if not valid
2992 153
            self::checkSheetCodeName($pValue);
2993
2994
            // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
2995
2996 153
            if ($this->getParent()) {
2997
                // Is there already such sheet name?
2998 137
                if ($this->getParent()->sheetCodeNameExists($pValue)) {
2999
                    // Use name, but append with lowest possible integer
3000
3001 39
                    if (Shared\StringHelper::countCharacters($pValue) > 29) {
3002
                        $pValue = Shared\StringHelper::substring($pValue, 0, 29);
3003
                    }
3004 39
                    $i = 1;
3005 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...
3006 10
                        ++$i;
3007 10
                        if ($i == 10) {
3008
                            if (Shared\StringHelper::countCharacters($pValue) > 28) {
3009
                                $pValue = Shared\StringHelper::substring($pValue, 0, 28);
3010
                            }
3011 10
                        } elseif ($i == 100) {
3012
                            if (Shared\StringHelper::countCharacters($pValue) > 27) {
3013
                                $pValue = Shared\StringHelper::substring($pValue, 0, 27);
3014
                            }
3015
                        }
3016
                    }
3017
3018 39
                    $pValue = $pValue . '_' . $i; // ok, we have a valid name
3019
                }
3020
            }
3021
        }
3022
3023 153
        $this->codeName = $pValue;
3024
3025 153
        return $this;
3026
    }
3027
3028
    /**
3029
     * Return the code name of the sheet.
3030
     *
3031
     * @return null|string
3032
     */
3033 153
    public function getCodeName()
3034
    {
3035 153
        return $this->codeName;
3036
    }
3037
3038
    /**
3039
     * Sheet has a code name ?
3040
     *
3041
     * @return bool
3042
     */
3043
    public function hasCodeName()
3044
    {
3045
        return !($this->codeName === null);
3046
    }
3047
}
3048