Completed
Push — develop ( 75d3bd...a06533 )
by Adrien
21:48
created

Worksheet::setMergeCells()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 6
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
use ArrayObject;
6
use PhpOffice\PhpSpreadsheet\Collection\Cells;
7
use PhpOffice\PhpSpreadsheet\Collection\CellsFactory;
8
9
/**
10
 * Copyright (c) 2006 - 2016 PhpSpreadsheet.
11
 *
12
 * This library is free software; you can redistribute it and/or
13
 * modify it under the terms of the GNU Lesser General Public
14
 * License as published by the Free Software Foundation; either
15
 * version 2.1 of the License, or (at your option) any later version.
16
 *
17
 * This library is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20
 * Lesser General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Lesser General Public
23
 * License along with this library; if not, write to the Free Software
24
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25
 *
26
 * @category   PhpSpreadsheet
27
 *
28
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
29
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
30
 */
31
class Worksheet implements IComparable
32
{
33
    /* Break types */
34
    const BREAK_NONE = 0;
35
    const BREAK_ROW = 1;
36
    const BREAK_COLUMN = 2;
37
38
    /* Sheet state */
39
    const SHEETSTATE_VISIBLE = 'visible';
40
    const SHEETSTATE_HIDDEN = 'hidden';
41
    const SHEETSTATE_VERYHIDDEN = 'veryHidden';
42
43
    /**
44
     * Invalid characters in sheet title.
45
     *
46
     * @var array
47
     */
48
    private static $invalidCharacters = ['*', ':', '/', '\\', '?', '[', ']'];
49
50
    /**
51
     * Parent spreadsheet.
52
     *
53
     * @var Spreadsheet
54
     */
55
    private $parent;
56
57
    /**
58
     * Collection of cells.
59
     *
60
     * @var Cells
61
     */
62
    private $cellCollection;
63
64
    /**
65
     * Collection of row dimensions.
66
     *
67
     * @var Worksheet\RowDimension[]
68
     */
69
    private $rowDimensions = [];
70
71
    /**
72
     * Default row dimension.
73
     *
74
     * @var Worksheet\RowDimension
75
     */
76
    private $defaultRowDimension;
77
78
    /**
79
     * Collection of column dimensions.
80
     *
81
     * @var Worksheet\ColumnDimension[]
82
     */
83
    private $columnDimensions = [];
84
85
    /**
86
     * Default column dimension.
87
     *
88
     * @var Worksheet\ColumnDimension
89
     */
90
    private $defaultColumnDimension = null;
91
92
    /**
93
     * Collection of drawings.
94
     *
95
     * @var Worksheet\BaseDrawing[]
96
     */
97
    private $drawingCollection = null;
98
99
    /**
100
     * Collection of Chart objects.
101
     *
102
     * @var Chart[]
103
     */
104
    private $chartCollection = [];
105
106
    /**
107
     * Worksheet title.
108
     *
109
     * @var string
110
     */
111
    private $title;
112
113
    /**
114
     * Sheet state.
115
     *
116
     * @var string
117
     */
118
    private $sheetState;
119
120
    /**
121
     * Page setup.
122
     *
123
     * @var Worksheet\PageSetup
124
     */
125
    private $pageSetup;
126
127
    /**
128
     * Page margins.
129
     *
130
     * @var Worksheet\PageMargins
131
     */
132
    private $pageMargins;
133
134
    /**
135
     * Page header/footer.
136
     *
137
     * @var Worksheet\HeaderFooter
138
     */
139
    private $headerFooter;
140
141
    /**
142
     * Sheet view.
143
     *
144
     * @var Worksheet\SheetView
145
     */
146
    private $sheetView;
147
148
    /**
149
     * Protection.
150
     *
151
     * @var Worksheet\Protection
152
     */
153
    private $protection;
154
155
    /**
156
     * Collection of styles.
157
     *
158
     * @var Style[]
159
     */
160
    private $styles = [];
161
162
    /**
163
     * Conditional styles. Indexed by cell coordinate, e.g. 'A1'.
164
     *
165
     * @var array
166
     */
167
    private $conditionalStylesCollection = [];
168
169
    /**
170
     * Is the current cell collection sorted already?
171
     *
172
     * @var bool
173
     */
174
    private $cellCollectionIsSorted = false;
175
176
    /**
177
     * Collection of breaks.
178
     *
179
     * @var array
180
     */
181
    private $breaks = [];
182
183
    /**
184
     * Collection of merged cell ranges.
185
     *
186
     * @var array
187
     */
188
    private $mergeCells = [];
189
190
    /**
191
     * Collection of protected cell ranges.
192
     *
193
     * @var array
194
     */
195
    private $protectedCells = [];
196
197
    /**
198
     * Autofilter Range and selection.
199
     *
200
     * @var Worksheet\AutoFilter
201
     */
202
    private $autoFilter;
203
204
    /**
205
     * Freeze pane.
206
     *
207
     * @var string
208
     */
209
    private $freezePane = '';
210
211
    /**
212
     * Show gridlines?
213
     *
214
     * @var bool
215
     */
216
    private $showGridlines = true;
217
218
    /**
219
     * Print gridlines?
220
     *
221
     * @var bool
222
     */
223
    private $printGridlines = false;
224
225
    /**
226
     * Show row and column headers?
227
     *
228
     * @var bool
229
     */
230
    private $showRowColHeaders = true;
231
232
    /**
233
     * Show summary below? (Row/Column outline).
234
     *
235
     * @var bool
236
     */
237
    private $showSummaryBelow = true;
238
239
    /**
240
     * Show summary right? (Row/Column outline).
241
     *
242
     * @var bool
243
     */
244
    private $showSummaryRight = true;
245
246
    /**
247
     * Collection of comments.
248
     *
249
     * @var Comment[]
250
     */
251
    private $comments = [];
252
253
    /**
254
     * Active cell. (Only one!).
255
     *
256
     * @var string
257
     */
258
    private $activeCell = 'A1';
259
260
    /**
261
     * Selected cells.
262
     *
263
     * @var string
264
     */
265
    private $selectedCells = 'A1';
266
267
    /**
268
     * Cached highest column.
269
     *
270
     * @var string
271
     */
272
    private $cachedHighestColumn = 'A';
273
274
    /**
275
     * Cached highest row.
276
     *
277
     * @var int
278
     */
279
    private $cachedHighestRow = 1;
280
281
    /**
282
     * Right-to-left?
283
     *
284
     * @var bool
285
     */
286
    private $rightToLeft = false;
287
288
    /**
289
     * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'.
290
     *
291
     * @var array
292
     */
293
    private $hyperlinkCollection = [];
294
295
    /**
296
     * Data validation objects. Indexed by cell coordinate, e.g. 'A1'.
297
     *
298
     * @var array
299
     */
300
    private $dataValidationCollection = [];
301
302
    /**
303
     * Tab color.
304
     *
305
     * @var Style\Color
306
     */
307
    private $tabColor;
308
309
    /**
310
     * Dirty flag.
311
     *
312
     * @var bool
313
     */
314
    private $dirty = true;
315
316
    /**
317
     * Hash.
318
     *
319
     * @var string
320
     */
321
    private $hash;
322
323
    /**
324
     * CodeName.
325
     *
326
     * @var string
327
     */
328
    private $codeName = null;
329
330
    /**
331
     * Create a new worksheet.
332
     *
333
     * @param Spreadsheet $parent
334
     * @param string $pTitle
335
     */
336 85
    public function __construct(Spreadsheet $parent = null, $pTitle = 'Worksheet')
337
    {
338
        // Set parent and title
339 85
        $this->parent = $parent;
340 85
        $this->setTitle($pTitle, false);
341
        // setTitle can change $pTitle
342 85
        $this->setCodeName($this->getTitle());
343 85
        $this->setSheetState(self::SHEETSTATE_VISIBLE);
344
345 85
        $this->cellCollection = CellsFactory::getInstance($this);
346
        // Set page setup
347 85
        $this->pageSetup = new Worksheet\PageSetup();
348
        // Set page margins
349 85
        $this->pageMargins = new Worksheet\PageMargins();
350
        // Set page header/footer
351 85
        $this->headerFooter = new Worksheet\HeaderFooter();
352
        // Set sheet view
353 85
        $this->sheetView = new Worksheet\SheetView();
354
        // Drawing collection
355 85
        $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...
356
        // Chart collection
357 85
        $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...\PhpSpreadsheet\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...
358
        // Protection
359 85
        $this->protection = new Worksheet\Protection();
360
        // Default row dimension
361 85
        $this->defaultRowDimension = new Worksheet\RowDimension(null);
362
        // Default column dimension
363 85
        $this->defaultColumnDimension = new Worksheet\ColumnDimension(null);
364 85
        $this->autoFilter = new Worksheet\AutoFilter(null, $this);
365 85
    }
366
367
    /**
368
     * Disconnect all cells from this Worksheet object,
369
     * typically so that the worksheet object can be unset.
370
     */
371 1
    public function disconnectCells()
372
    {
373 1
        if ($this->cellCollection !== null) {
374 1
            $this->cellCollection->unsetWorksheetCells();
375 1
            $this->cellCollection = null;
376
        }
377
        //    detach ourself from the workbook, so that it can then delete this worksheet successfully
378 1
        $this->parent = null;
379 1
    }
380
381
    /**
382
     * Code to execute when this worksheet is unset().
383
     */
384 1
    public function __destruct()
385
    {
386 1
        Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title);
387
388 1
        $this->disconnectCells();
389 1
    }
390
391
    /**
392
     * Return the cell collection.
393
     *
394
     * @return Cells
395
     */
396 75
    public function getCellCollection()
397
    {
398 75
        return $this->cellCollection;
399
    }
400
401
    /**
402
     * Get array of invalid characters for sheet title.
403
     *
404
     * @return array
405
     */
406
    public static function getInvalidCharacters()
407
    {
408
        return self::$invalidCharacters;
409
    }
410
411
    /**
412
     * Check sheet code name for valid Excel syntax.
413
     *
414
     * @param string $pValue The string to check
415
     *
416
     * @throws Exception
417
     *
418
     * @return string The valid string
419
     */
420 85
    private static function checkSheetCodeName($pValue)
421
    {
422 85
        $CharCount = Shared\StringHelper::countCharacters($pValue);
423 85
        if ($CharCount == 0) {
424
            throw new Exception('Sheet code name cannot be empty.');
425
        }
426
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ] and  first and last characters cannot be a "'"
427 85
        if ((str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) ||
428 85
            (Shared\StringHelper::substring($pValue, -1, 1) == '\'') ||
429 85
            (Shared\StringHelper::substring($pValue, 0, 1) == '\'')) {
430
            throw new Exception('Invalid character found in sheet code name');
431
        }
432
433
        // Maximum 31 characters allowed for sheet title
434 85
        if ($CharCount > 31) {
435
            throw new Exception('Maximum 31 characters allowed in sheet code name.');
436
        }
437
438 85
        return $pValue;
439
    }
440
441
    /**
442
     * Check sheet title for valid Excel syntax.
443
     *
444
     * @param string $pValue The string to check
445
     *
446
     * @throws Exception
447
     *
448
     * @return string The valid string
449
     */
450 85
    private static function checkSheetTitle($pValue)
451
    {
452
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ]
453 85
        if (str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) {
454
            throw new Exception('Invalid character found in sheet title');
455
        }
456
457
        // Maximum 31 characters allowed for sheet title
458 85
        if (Shared\StringHelper::countCharacters($pValue) > 31) {
459
            throw new Exception('Maximum 31 characters allowed in sheet title.');
460
        }
461
462 85
        return $pValue;
463
    }
464
465
    /**
466
     * Get a sorted list of all cell coordinates currently held in the collection by row and column.
467
     *
468
     * @param bool $sorted Also sort the cell collection?
469
     *
470
     * @return string[]
471
     */
472 63
    public function getCoordinates($sorted = true)
473
    {
474 63
        if ($this->cellCollection == null) {
475
            return [];
476
        }
477
478 63
        if ($sorted) {
479 62
            return $this->cellCollection->getSortedCoordinates();
480
        }
481
482 62
        return $this->cellCollection->getCoordinates();
483
    }
484
485
    /**
486
     * Get collection of row dimensions.
487
     *
488
     * @return Worksheet\RowDimension[]
489
     */
490 61
    public function getRowDimensions()
491
    {
492 61
        return $this->rowDimensions;
493
    }
494
495
    /**
496
     * Get default row dimension.
497
     *
498
     * @return Worksheet\RowDimension
499
     */
500 61
    public function getDefaultRowDimension()
501
    {
502 61
        return $this->defaultRowDimension;
503
    }
504
505
    /**
506
     * Get collection of column dimensions.
507
     *
508
     * @return Worksheet\ColumnDimension[]
509
     */
510 61
    public function getColumnDimensions()
511
    {
512 61
        return $this->columnDimensions;
513
    }
514
515
    /**
516
     * Get default column dimension.
517
     *
518
     * @return Worksheet\ColumnDimension
519
     */
520 57
    public function getDefaultColumnDimension()
521
    {
522 57
        return $this->defaultColumnDimension;
523
    }
524
525
    /**
526
     * Get collection of drawings.
527
     *
528
     * @return Worksheet\BaseDrawing[]
529
     */
530 61
    public function getDrawingCollection()
531
    {
532 61
        return $this->drawingCollection;
533
    }
534
535
    /**
536
     * Get collection of charts.
537
     *
538
     * @return Chart[]
539
     */
540 14
    public function getChartCollection()
541
    {
542 14
        return $this->chartCollection;
543
    }
544
545
    /**
546
     * Add chart.
547
     *
548
     * @param Chart $pChart
549
     * @param int|null $iChartIndex Index where chart should go (0,1,..., or null for last)
550
     *
551
     * @return Chart
552
     */
553 14
    public function addChart(Chart $pChart, $iChartIndex = null)
554
    {
555 14
        $pChart->setWorksheet($this);
556 14
        if (is_null($iChartIndex)) {
557 14
            $this->chartCollection[] = $pChart;
558
        } else {
559
            // Insert the chart at the requested index
560
            array_splice($this->chartCollection, $iChartIndex, 0, [$pChart]);
561
        }
562
563 14
        return $pChart;
564
    }
565
566
    /**
567
     * Return the count of charts on this worksheet.
568
     *
569
     * @return int The number of charts
570
     */
571 14
    public function getChartCount()
572
    {
573 14
        return count($this->chartCollection);
574
    }
575
576
    /**
577
     * Get a chart by its index position.
578
     *
579
     * @param string $index Chart index position
580
     *
581
     * @throws Exception
582
     *
583
     * @return false|Chart
584
     */
585 13
    public function getChartByIndex($index)
586
    {
587 13
        $chartCount = count($this->chartCollection);
588 13
        if ($chartCount == 0) {
589
            return false;
590
        }
591 13
        if (is_null($index)) {
592
            $index = --$chartCount;
593
        }
594 13
        if (!isset($this->chartCollection[$index])) {
595
            return false;
596
        }
597
598 13
        return $this->chartCollection[$index];
599
    }
600
601
    /**
602
     * Return an array of the names of charts on this worksheet.
603
     *
604
     * @throws Exception
605
     *
606
     * @return string[] The names of charts
607
     */
608 1
    public function getChartNames()
609
    {
610 1
        $chartNames = [];
611 1
        foreach ($this->chartCollection as $chart) {
612 1
            $chartNames[] = $chart->getName();
613
        }
614
615 1
        return $chartNames;
616
    }
617
618
    /**
619
     * Get a chart by name.
620
     *
621
     * @param string $chartName Chart name
622
     *
623
     * @throws Exception
624
     *
625
     * @return false|Chart
626
     */
627 1
    public function getChartByName($chartName)
628
    {
629 1
        $chartCount = count($this->chartCollection);
630 1
        if ($chartCount == 0) {
631
            return false;
632
        }
633 1
        foreach ($this->chartCollection as $index => $chart) {
634 1
            if ($chart->getName() == $chartName) {
635 1
                return $this->chartCollection[$index];
636
            }
637
        }
638
639
        return false;
640
    }
641
642
    /**
643
     * Refresh column dimensions.
644
     *
645
     * @return Worksheet
646
     */
647 15
    public function refreshColumnDimensions()
648
    {
649 15
        $currentColumnDimensions = $this->getColumnDimensions();
650 15
        $newColumnDimensions = [];
651
652 15
        foreach ($currentColumnDimensions as $objColumnDimension) {
653 15
            $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension;
654
        }
655
656 15
        $this->columnDimensions = $newColumnDimensions;
657
658 15
        return $this;
659
    }
660
661
    /**
662
     * Refresh row dimensions.
663
     *
664
     * @return Worksheet
665
     */
666 2
    public function refreshRowDimensions()
667
    {
668 2
        $currentRowDimensions = $this->getRowDimensions();
669 2
        $newRowDimensions = [];
670
671 2
        foreach ($currentRowDimensions as $objRowDimension) {
672 2
            $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension;
673
        }
674
675 2
        $this->rowDimensions = $newRowDimensions;
676
677 2
        return $this;
678
    }
679
680
    /**
681
     * Calculate worksheet dimension.
682
     *
683
     * @return string String containing the dimension of this worksheet
684
     */
685 60
    public function calculateWorksheetDimension()
686
    {
687
        // Return
688 60
        return 'A1' . ':' . $this->getHighestColumn() . $this->getHighestRow();
689
    }
690
691
    /**
692
     * Calculate worksheet data dimension.
693
     *
694
     * @return string String containing the dimension of this worksheet that actually contain data
695
     */
696
    public function calculateWorksheetDataDimension()
697
    {
698
        // Return
699
        return 'A1' . ':' . $this->getHighestDataColumn() . $this->getHighestDataRow();
700
    }
701
702
    /**
703
     * Calculate widths for auto-size columns.
704
     *
705
     * @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...
706
     */
707 46
    public function calculateColumnWidths()
708
    {
709
        // initialize $autoSizes array
710 46
        $autoSizes = [];
711 46
        foreach ($this->getColumnDimensions() as $colDimension) {
712 26
            if ($colDimension->getAutoSize()) {
713 14
                $autoSizes[$colDimension->getColumnIndex()] = -1;
714
            }
715
        }
716
717
        // There is only something to do if there are some auto-size columns
718 46
        if (!empty($autoSizes)) {
719
            // build list of cells references that participate in a merge
720 14
            $isMergeCell = [];
721 14
            foreach ($this->getMergeCells() as $cells) {
722 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...
723 11
                    $isMergeCell[$cellReference] = true;
724
                }
725
            }
726
727
            // loop through all cells in the worksheet
728 14
            foreach ($this->getCoordinates(false) as $coordinate) {
729 14
                $cell = $this->getCell($coordinate, false);
730 14
                if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
731
                    //Determine if cell is in merge range
732 14
                    $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
733
734
                    //By default merged cells should be ignored
735 14
                    $isMergedButProceed = false;
736
737
                    //The only exception is if it's a merge range value cell of a 'vertical' randge (1 column wide)
738 14
                    if ($isMerged && $cell->isMergeRangeValueCell()) {
739
                        $range = $cell->getMergeRange();
740
                        $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...
741
                        if ($rangeBoundaries[0] == 1) {
742
                            $isMergedButProceed = true;
743
                        }
744
                    }
745
746
                    // Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range
747 14
                    if (!$isMerged || $isMergedButProceed) {
748
                        // Calculated value
749
                        // To formatted string
750 14
                        $cellValue = Style\NumberFormat::toFormattedString(
751 14
                            $cell->getCalculatedValue(),
752 14
                            $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
753
                        );
754
755 14
                        $autoSizes[$this->cellCollection->getCurrentColumn()] = max(
756 14
                            (float) $autoSizes[$this->cellCollection->getCurrentColumn()],
757 14
                            (float) Shared\Font::calculateColumnWidth(
758 14
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
759
                                $cellValue,
760 14
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
761 14
                                $this->getParent()->getDefaultStyle()->getFont()
762
                            )
763
                        );
764
                    }
765
                }
766
            }
767
768
            // adjust column widths
769 14
            foreach ($autoSizes as $columnIndex => $width) {
770 14
                if ($width == -1) {
771
                    $width = $this->getDefaultColumnDimension()->getWidth();
772
                }
773 14
                $this->getColumnDimension($columnIndex)->setWidth($width);
774
            }
775
        }
776
777 46
        return $this;
778
    }
779
780
    /**
781
     * Get parent.
782
     *
783
     * @return Spreadsheet
784
     */
785 85
    public function getParent()
786
    {
787 85
        return $this->parent;
788
    }
789
790
    /**
791
     * Re-bind parent.
792
     *
793
     * @param Spreadsheet $parent
794
     *
795
     * @return Worksheet
796
     */
797 1
    public function rebindParent(Spreadsheet $parent)
798
    {
799 1
        if ($this->parent !== null) {
800 1
            $namedRanges = $this->parent->getNamedRanges();
801 1
            foreach ($namedRanges as $namedRange) {
802
                $parent->addNamedRange($namedRange);
803
            }
804
805 1
            $this->parent->removeSheetByIndex(
806 1
                $this->parent->getIndex($this)
807
            );
808
        }
809 1
        $this->parent = $parent;
810
811 1
        return $this;
812
    }
813
814
    /**
815
     * Get title.
816
     *
817
     * @return string
818
     */
819 85
    public function getTitle()
820
    {
821 85
        return $this->title;
822
    }
823
824
    /**
825
     * Set title.
826
     *
827
     * @param string $pValue String containing the dimension of this worksheet
828
     * @param string $updateFormulaCellReferences boolean Flag indicating whether cell references in formulae should
829
     *            be updated to reflect the new sheet name.
830
     *          This should be left as the default true, unless you are
831
     *          certain that no formula cells on any worksheet contain
832
     *          references to this worksheet
833
     *
834
     * @return Worksheet
835
     */
836 85
    public function setTitle($pValue, $updateFormulaCellReferences = true)
837
    {
838
        // Is this a 'rename' or not?
839 85
        if ($this->getTitle() == $pValue) {
840 4
            return $this;
841
        }
842
843
        // Syntax check
844 85
        self::checkSheetTitle($pValue);
845
846
        // Old title
847 85
        $oldTitle = $this->getTitle();
848
849 85
        if ($this->parent) {
850
            // Is there already such sheet name?
851 75
            if ($this->parent->sheetNameExists($pValue)) {
852
                // Use name, but append with lowest possible integer
853
854 3
                if (Shared\StringHelper::countCharacters($pValue) > 29) {
855
                    $pValue = Shared\StringHelper::substring($pValue, 0, 29);
856
                }
857 3
                $i = 1;
858 3 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...
859
                    ++$i;
860
                    if ($i == 10) {
861
                        if (Shared\StringHelper::countCharacters($pValue) > 28) {
862
                            $pValue = Shared\StringHelper::substring($pValue, 0, 28);
863
                        }
864
                    } elseif ($i == 100) {
865
                        if (Shared\StringHelper::countCharacters($pValue) > 27) {
866
                            $pValue = Shared\StringHelper::substring($pValue, 0, 27);
867
                        }
868
                    }
869
                }
870
871 3
                $altTitle = $pValue . ' ' . $i;
872
873 3
                return $this->setTitle($altTitle, $updateFormulaCellReferences);
874
            }
875
        }
876
877
        // Set title
878 85
        $this->title = $pValue;
879 85
        $this->dirty = true;
880
881 85
        if ($this->parent && $this->parent->getCalculationEngine()) {
882
            // New title
883 75
            $newTitle = $this->getTitle();
884 75
            $this->parent->getCalculationEngine()
885 75
                ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
886 75
            if ($updateFormulaCellReferences) {
887 26
                ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
888
            }
889
        }
890
891 85
        return $this;
892
    }
893
894
    /**
895
     * Get sheet state.
896
     *
897
     * @return string Sheet state (visible, hidden, veryHidden)
898
     */
899 57
    public function getSheetState()
900
    {
901 57
        return $this->sheetState;
902
    }
903
904
    /**
905
     * Set sheet state.
906
     *
907
     * @param string $value Sheet state (visible, hidden, veryHidden)
908
     *
909
     * @return Worksheet
910
     */
911 85
    public function setSheetState($value)
912
    {
913 85
        $this->sheetState = $value;
914
915 85
        return $this;
916
    }
917
918
    /**
919
     * Get page setup.
920
     *
921
     * @return Worksheet\PageSetup
922
     */
923 61
    public function getPageSetup()
924
    {
925 61
        return $this->pageSetup;
926
    }
927
928
    /**
929
     * Set page setup.
930
     *
931
     * @param Worksheet\PageSetup $pValue
932
     *
933
     * @return Worksheet
934
     */
935
    public function setPageSetup(Worksheet\PageSetup $pValue)
936
    {
937
        $this->pageSetup = $pValue;
938
939
        return $this;
940
    }
941
942
    /**
943
     * Get page margins.
944
     *
945
     * @return Worksheet\PageMargins
946
     */
947 61
    public function getPageMargins()
948
    {
949 61
        return $this->pageMargins;
950
    }
951
952
    /**
953
     * Set page margins.
954
     *
955
     * @param Worksheet\PageMargins $pValue
956
     *
957
     * @return Worksheet
958
     */
959
    public function setPageMargins(Worksheet\PageMargins $pValue)
960
    {
961
        $this->pageMargins = $pValue;
962
963
        return $this;
964
    }
965
966
    /**
967
     * Get page header/footer.
968
     *
969
     * @return Worksheet\HeaderFooter
970
     */
971 61
    public function getHeaderFooter()
972
    {
973 61
        return $this->headerFooter;
974
    }
975
976
    /**
977
     * Set page header/footer.
978
     *
979
     * @param Worksheet\HeaderFooter $pValue
980
     *
981
     * @return Worksheet
982
     */
983
    public function setHeaderFooter(Worksheet\HeaderFooter $pValue)
984
    {
985
        $this->headerFooter = $pValue;
986
987
        return $this;
988
    }
989
990
    /**
991
     * Get sheet view.
992
     *
993
     * @return Worksheet\SheetView
994
     */
995 57
    public function getSheetView()
996
    {
997 57
        return $this->sheetView;
998
    }
999
1000
    /**
1001
     * Set sheet view.
1002
     *
1003
     * @param Worksheet\SheetView $pValue
1004
     *
1005
     * @return Worksheet
1006
     */
1007
    public function setSheetView(Worksheet\SheetView $pValue)
1008
    {
1009
        $this->sheetView = $pValue;
1010
1011
        return $this;
1012
    }
1013
1014
    /**
1015
     * Get Protection.
1016
     *
1017
     * @return Worksheet\Protection
1018
     */
1019 61
    public function getProtection()
1020
    {
1021 61
        return $this->protection;
1022
    }
1023
1024
    /**
1025
     * Set Protection.
1026
     *
1027
     * @param Worksheet\Protection $pValue
1028
     *
1029
     * @return Worksheet
1030
     */
1031
    public function setProtection(Worksheet\Protection $pValue)
1032
    {
1033
        $this->protection = $pValue;
1034
        $this->dirty = true;
1035
1036
        return $this;
1037
    }
1038
1039
    /**
1040
     * Get highest worksheet column.
1041
     *
1042
     * @param string $row Return the data highest column for the specified row,
1043
     *                                     or the highest column of any row if no row number is passed
1044
     *
1045
     * @return string Highest column name
1046
     */
1047 65
    public function getHighestColumn($row = null)
1048
    {
1049 65
        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...
1050 65
            return $this->cachedHighestColumn;
1051
        }
1052
1053
        return $this->getHighestDataColumn($row);
1054
    }
1055
1056
    /**
1057
     * Get highest worksheet column that contains data.
1058
     *
1059
     * @param string $row Return the highest data column for the specified row,
1060
     *                                     or the highest data column of any row if no row number is passed
1061
     *
1062
     * @return string Highest column name that contains data
1063
     */
1064 14
    public function getHighestDataColumn($row = null)
1065
    {
1066 14
        return $this->cellCollection->getHighestColumn($row);
1067
    }
1068
1069
    /**
1070
     * Get highest worksheet row.
1071
     *
1072
     * @param string $column Return the highest data row for the specified column,
1073
     *                                     or the highest row of any column if no column letter is passed
1074
     *
1075
     * @return int Highest row number
1076
     */
1077 65
    public function getHighestRow($column = null)
1078
    {
1079 65
        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...
1080 65
            return $this->cachedHighestRow;
1081
        }
1082
1083
        return $this->getHighestDataRow($column);
1084
    }
1085
1086
    /**
1087
     * Get highest worksheet row that contains data.
1088
     *
1089
     * @param string $column Return the highest data row for the specified column,
1090
     *                                     or the highest data row of any column if no column letter is passed
1091
     *
1092
     * @return string Highest row number that contains data
1093
     */
1094 16
    public function getHighestDataRow($column = null)
1095
    {
1096 16
        return $this->cellCollection->getHighestRow($column);
1097
    }
1098
1099
    /**
1100
     * Get highest worksheet column and highest row that have cell records.
1101
     *
1102
     * @return array Highest column name and highest row number
1103
     */
1104
    public function getHighestRowAndColumn()
1105
    {
1106
        return $this->cellCollection->getHighestRowAndColumn();
1107
    }
1108
1109
    /**
1110
     * Set a cell value.
1111
     *
1112
     * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
1113
     * @param mixed $pValue Value of the cell
1114
     *
1115
     * @return Worksheet
1116
     */
1117 40
    public function setCellValue($pCoordinate, $pValue)
1118
    {
1119 40
        $this->getCell($pCoordinate)->setValue($pValue);
1120
1121 40
        return $this;
1122
    }
1123
1124
    /**
1125
     * Set a cell value by using numeric cell coordinates.
1126
     *
1127
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1128
     * @param int $pRow Numeric row coordinate of the cell
1129
     * @param mixed $pValue Value of the cell
1130
     *
1131
     * @return Worksheet
1132
     */
1133
    public function setCellValueByColumnAndRow($pColumn, $pRow, $pValue)
1134
    {
1135
        $this->getCellByColumnAndRow($pColumn, $pRow)->setValue($pValue);
1136
1137
        return $this;
1138
    }
1139
1140
    /**
1141
     * Set a cell value.
1142
     *
1143
     * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
1144
     * @param mixed $pValue Value of the cell
1145
     * @param string $pDataType Explicit data type, see Cell\DataType::TYPE_*
1146
     *
1147
     * @return Worksheet
1148
     */
1149 1
    public function setCellValueExplicit($pCoordinate, $pValue, $pDataType)
1150
    {
1151
        // Set value
1152 1
        $this->getCell($pCoordinate)->setValueExplicit($pValue, $pDataType);
1153
1154 1
        return $this;
1155
    }
1156
1157
    /**
1158
     * Set a cell value by using numeric cell coordinates.
1159
     *
1160
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1161
     * @param int $pRow Numeric row coordinate of the cell
1162
     * @param mixed $pValue Value of the cell
1163
     * @param string $pDataType Explicit data type, see Cell\DataType::TYPE_*
1164
     * @param bool $returnCell Return the worksheet (false, default) or the cell (true)
0 ignored issues
show
Bug introduced by
There is no parameter named $returnCell. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1165
     *
1166
     * @return Worksheet
1167
     */
1168
    public function setCellValueExplicitByColumnAndRow($pColumn, $pRow, $pValue, $pDataType)
1169
    {
1170
        $this->getCellByColumnAndRow($pColumn, $pRow)->setValueExplicit($pValue, $pDataType);
1171
1172
        return $this;
1173
    }
1174
1175
    /**
1176
     * Get cell at a specific coordinate.
1177
     *
1178
     * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
1179
     * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
1180
     *                                       already exist, or a null should be returned instead
1181
     *
1182
     * @throws Exception
1183
     *
1184
     * @return null|Cell Cell that was found/created or null
1185
     */
1186 74
    public function getCell($pCoordinate, $createIfNotExists = true)
1187
    {
1188
        // Check cell collection
1189 74
        if ($this->cellCollection->has(strtoupper($pCoordinate))) {
1190 71
            return $this->cellCollection->get($pCoordinate);
1191
        }
1192
1193
        // Worksheet reference?
1194 73 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...
1195 1
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1196
1197 1
            return $this->parent->getSheetByName($worksheetReference[0])->getCell(strtoupper($worksheetReference[1]), $createIfNotExists);
1198
        }
1199
1200
        // Named range?
1201 73
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1202
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1203
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1204
            if ($namedRange !== null) {
1205
                $pCoordinate = $namedRange->getRange();
1206
1207
                return $namedRange->getWorksheet()->getCell($pCoordinate, $createIfNotExists);
1208
            }
1209
        }
1210
1211
        // Uppercase coordinate
1212 73
        $pCoordinate = strtoupper($pCoordinate);
1213
1214 73 View Code Duplication
        if (strpos($pCoordinate, ':') !== false || 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...
1215
            throw new Exception('Cell coordinate can not be a range of cells.');
1216 73
        } elseif (strpos($pCoordinate, '$') !== false) {
1217
            throw new Exception('Cell coordinate must not be absolute.');
1218
        }
1219
1220
        // Create new cell object, if required
1221 73
        return $createIfNotExists ? $this->createNewCell($pCoordinate) : null;
1222
    }
1223
1224
    /**
1225
     * Get cell at a specific coordinate by using numeric cell coordinates.
1226
     *
1227
     * @param string $pColumn Numeric column coordinate of the cell
1228
     * @param string $pRow Numeric row coordinate of the cell
1229
     * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
1230
     *                                       already exist, or a null should be returned instead
1231
     *
1232
     * @return null|Cell Cell that was found/created or null
1233
     */
1234 39
    public function getCellByColumnAndRow($pColumn, $pRow, $createIfNotExists = true)
1235
    {
1236 39
        $columnLetter = Cell::stringFromColumnIndex($pColumn);
1237 39
        $coordinate = $columnLetter . $pRow;
1238
1239 39
        if ($this->cellCollection->has($coordinate)) {
1240 38
            return $this->cellCollection->get($coordinate);
1241
        }
1242
1243
        // Create new cell object, if required
1244 22
        return $createIfNotExists ? $this->createNewCell($coordinate) : null;
1245
    }
1246
1247
    /**
1248
     * Create a new cell at the specified coordinate.
1249
     *
1250
     * @param string $pCoordinate Coordinate of the cell
1251
     *
1252
     * @return Cell Cell that was created
1253
     */
1254 75
    private function createNewCell($pCoordinate)
1255
    {
1256 75
        $cell = new Cell(null, Cell\DataType::TYPE_NULL, $this);
1257 75
        $this->cellCollection->add($pCoordinate, $cell);
1258 75
        $this->cellCollectionIsSorted = false;
1259
1260
        // Coordinates
1261 75
        $aCoordinates = Cell::coordinateFromString($pCoordinate);
1262 75
        if (Cell::columnIndexFromString($this->cachedHighestColumn) < Cell::columnIndexFromString($aCoordinates[0])) {
1263 64
            $this->cachedHighestColumn = $aCoordinates[0];
1264
        }
1265 75
        $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...
1266
1267
        // Cell needs appropriate xfIndex from dimensions records
1268
        //    but don't create dimension records if they don't already exist
1269 75
        $rowDimension = $this->getRowDimension($aCoordinates[1], false);
1270 75
        $columnDimension = $this->getColumnDimension($aCoordinates[0], false);
1271
1272 75
        if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) {
1273
            // then there is a row dimension with explicit style, assign it to the cell
1274
            $cell->setXfIndex($rowDimension->getXfIndex());
1275 75
        } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) {
1276
            // then there is a column dimension, assign it to the cell
1277
            $cell->setXfIndex($columnDimension->getXfIndex());
1278
        }
1279
1280 75
        return $cell;
1281
    }
1282
1283
    /**
1284
     * Does the cell at a specific coordinate exist?
1285
     *
1286
     * @param string $pCoordinate Coordinate of the cell eg: 'A1'
1287
     *
1288
     * @throws Exception
1289
     *
1290
     * @return bool
1291
     */
1292 42
    public function cellExists($pCoordinate)
1293
    {
1294
        // Worksheet reference?
1295 42 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...
1296
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1297
1298
            return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1]));
1299
        }
1300
1301
        // Named range?
1302 42
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1303
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1304
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1305
            if ($namedRange !== null) {
1306
                $pCoordinate = $namedRange->getRange();
1307
                if ($this->getHashCode() != $namedRange->getWorksheet()->getHashCode()) {
1308
                    if (!$namedRange->getLocalOnly()) {
1309
                        return $namedRange->getWorksheet()->cellExists($pCoordinate);
1310
                    }
1311
                    throw new Exception('Named range ' . $namedRange->getName() . ' is not accessible from within sheet ' . $this->getTitle());
1312
                }
1313
            } else {
1314
                return false;
1315
            }
1316
        }
1317
1318
        // Uppercase coordinate
1319 42
        $pCoordinate = strtoupper($pCoordinate);
1320
1321 42
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
1322
            throw new Exception('Cell coordinate can not be a range of cells.');
1323 42
        } elseif (strpos($pCoordinate, '$') !== false) {
1324
            throw new Exception('Cell coordinate must not be absolute.');
1325
        }
1326
            // Coordinates
1327 42
            $aCoordinates = Cell::coordinateFromString($pCoordinate);
0 ignored issues
show
Unused Code introduced by
$aCoordinates 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...
1328
1329
            // Cell exists?
1330 42
            return $this->cellCollection->has($pCoordinate);
1331
    }
1332
1333
    /**
1334
     * Cell at a specific coordinate by using numeric cell coordinates exists?
1335
     *
1336
     * @param string $pColumn Numeric column coordinate of the cell (A = 0)
1337
     * @param string $pRow Numeric row coordinate of the cell
1338
     *
1339
     * @return bool
1340
     */
1341 6
    public function cellExistsByColumnAndRow($pColumn, $pRow)
1342
    {
1343 6
        return $this->cellExists(Cell::stringFromColumnIndex($pColumn) . $pRow);
1344
    }
1345
1346
    /**
1347
     * Get row dimension at a specific row.
1348
     *
1349
     * @param int $pRow Numeric index of the row
1350
     * @param mixed $create
1351
     *
1352
     * @return Worksheet\RowDimension
1353
     */
1354 75
    public function getRowDimension($pRow, $create = true)
1355
    {
1356
        // Found
1357 75
        $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...
1358
1359
        // Get row dimension
1360 75
        if (!isset($this->rowDimensions[$pRow])) {
1361 75
            if (!$create) {
1362 74
                return null;
1363
            }
1364 57
            $this->rowDimensions[$pRow] = new Worksheet\RowDimension($pRow);
1365
1366 57
            $this->cachedHighestRow = max($this->cachedHighestRow, $pRow);
1367
        }
1368
1369 57
        return $this->rowDimensions[$pRow];
1370
    }
1371
1372
    /**
1373
     * Get column dimension at a specific column.
1374
     *
1375
     * @param string $pColumn String index of the column eg: 'A'
1376
     * @param mixed $create
1377
     *
1378
     * @return Worksheet\ColumnDimension
1379
     */
1380 75
    public function getColumnDimension($pColumn, $create = true)
1381
    {
1382
        // Uppercase coordinate
1383 75
        $pColumn = strtoupper($pColumn);
1384
1385
        // Fetch dimensions
1386 75
        if (!isset($this->columnDimensions[$pColumn])) {
1387 75
            if (!$create) {
1388 74
                return null;
1389
            }
1390 29
            $this->columnDimensions[$pColumn] = new Worksheet\ColumnDimension($pColumn);
1391
1392 29
            if (Cell::columnIndexFromString($this->cachedHighestColumn) < Cell::columnIndexFromString($pColumn)) {
1393 12
                $this->cachedHighestColumn = $pColumn;
1394
            }
1395
        }
1396
1397 29
        return $this->columnDimensions[$pColumn];
1398
    }
1399
1400
    /**
1401
     * Get column dimension at a specific column by using numeric cell coordinates.
1402
     *
1403
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1404
     *
1405
     * @return Worksheet\ColumnDimension
1406
     */
1407 3
    public function getColumnDimensionByColumn($pColumn)
1408
    {
1409 3
        return $this->getColumnDimension(Cell::stringFromColumnIndex($pColumn));
1410
    }
1411
1412
    /**
1413
     * Get styles.
1414
     *
1415
     * @return Style[]
1416
     */
1417
    public function getStyles()
1418
    {
1419
        return $this->styles;
1420
    }
1421
1422
    /**
1423
     * Get style for cell.
1424
     *
1425
     * @param string $pCellCoordinate Cell coordinate (or range) to get style for, eg: 'A1'
1426
     *
1427
     * @throws Exception
1428
     *
1429
     * @return Style
1430
     */
1431 38
    public function getStyle($pCellCoordinate)
1432
    {
1433
        // set this sheet as active
1434 38
        $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
1435
1436
        // set cell coordinate as active
1437 38
        $this->setSelectedCells(strtoupper($pCellCoordinate));
1438
1439 38
        return $this->parent->getCellXfSupervisor();
1440
    }
1441
1442
    /**
1443
     * Get conditional styles for a cell.
1444
     *
1445
     * @param string $pCoordinate eg: 'A1'
1446
     *
1447
     * @return Style\Conditional[]
1448
     */
1449 2
    public function getConditionalStyles($pCoordinate)
1450
    {
1451 2
        $pCoordinate = strtoupper($pCoordinate);
1452 2
        if (!isset($this->conditionalStylesCollection[$pCoordinate])) {
1453 2
            $this->conditionalStylesCollection[$pCoordinate] = [];
1454
        }
1455
1456 2
        return $this->conditionalStylesCollection[$pCoordinate];
1457
    }
1458
1459
    /**
1460
     * Do conditional styles exist for this cell?
1461
     *
1462
     * @param string $pCoordinate eg: 'A1'
1463
     *
1464
     * @return bool
1465
     */
1466 13
    public function conditionalStylesExists($pCoordinate)
1467
    {
1468 13
        if (isset($this->conditionalStylesCollection[strtoupper($pCoordinate)])) {
1469
            return true;
1470
        }
1471
1472 13
        return false;
1473
    }
1474
1475
    /**
1476
     * Removes conditional styles for a cell.
1477
     *
1478
     * @param string $pCoordinate eg: 'A1'
1479
     *
1480
     * @return Worksheet
1481
     */
1482 14
    public function removeConditionalStyles($pCoordinate)
1483
    {
1484 14
        unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
1485
1486 14
        return $this;
1487
    }
1488
1489
    /**
1490
     * Get collection of conditional styles.
1491
     *
1492
     * @return array
1493
     */
1494 57
    public function getConditionalStylesCollection()
1495
    {
1496 57
        return $this->conditionalStylesCollection;
1497
    }
1498
1499
    /**
1500
     * Set conditional styles.
1501
     *
1502
     * @param string $pCoordinate eg: 'A1'
1503
     * @param $pValue Style\Conditional[]
1504
     *
1505
     * @return Worksheet
1506
     */
1507 2
    public function setConditionalStyles($pCoordinate, $pValue)
1508
    {
1509 2
        $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue;
1510
1511 2
        return $this;
1512
    }
1513
1514
    /**
1515
     * Get style for cell by using numeric cell coordinates.
1516
     *
1517
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1518
     * @param int $pRow Numeric row coordinate of the cell
1519
     * @param int pColumn2 Numeric column coordinate of the range cell (A = 0)
1520
     * @param int pRow2 Numeric row coordinate of the range cell
1521
     * @param null|mixed $pColumn2
1522
     * @param null|mixed $pRow2
1523
     *
1524
     * @return Style
1525
     */
1526
    public function getStyleByColumnAndRow($pColumn, $pRow, $pColumn2 = null, $pRow2 = null)
1527
    {
1528
        if (!is_null($pColumn2) && !is_null($pRow2)) {
1529
            $cellRange = Cell::stringFromColumnIndex($pColumn) . $pRow . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1530
1531
            return $this->getStyle($cellRange);
1532
        }
1533
1534
        return $this->getStyle(Cell::stringFromColumnIndex($pColumn) . $pRow);
1535
    }
1536
1537
    /**
1538
     * Duplicate cell style to a range of cells.
1539
     *
1540
     * Please note that this will overwrite existing cell styles for cells in range!
1541
     *
1542
     * @param Style $pCellStyle Cell style to duplicate
1543
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1544
     *
1545
     * @throws Exception
1546
     *
1547
     * @return Worksheet
1548
     */
1549 2
    public function duplicateStyle(Style $pCellStyle, $pRange)
1550
    {
1551
        // make sure we have a real style and not supervisor
1552 2
        $style = $pCellStyle->getIsSupervisor() ? $pCellStyle->getSharedComponent() : $pCellStyle;
0 ignored issues
show
Unused Code introduced by
$style 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...
1553
1554
        // Add the style to the workbook if necessary
1555 2
        $workbook = $this->parent;
1556 2
        if ($existingStyle = $this->parent->getCellXfByHashCode($pCellStyle->getHashCode())) {
1557
            // there is already such cell Xf in our collection
1558 1
            $xfIndex = $existingStyle->getIndex();
1559
        } else {
1560
            // we don't have such a cell Xf, need to add
1561 2
            $workbook->addCellXf($pCellStyle);
1562 2
            $xfIndex = $pCellStyle->getIndex();
1563
        }
1564
1565
        // Calculate range outer borders
1566 2
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange . ':' . $pRange);
1567
1568
        // Make sure we can loop upwards on rows and columns
1569 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...
1570
            $tmp = $rangeStart;
1571
            $rangeStart = $rangeEnd;
1572
            $rangeEnd = $tmp;
1573
        }
1574
1575
        // Loop through cells and apply styles
1576 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...
1577 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1578 2
                $this->getCell(Cell::stringFromColumnIndex($col - 1) . $row)->setXfIndex($xfIndex);
1579
            }
1580
        }
1581
1582 2
        return $this;
1583
    }
1584
1585
    /**
1586
     * Duplicate conditional style to a range of cells.
1587
     *
1588
     * Please note that this will overwrite existing cell styles for cells in range!
1589
     *
1590
     * @param Style\Conditional[] $pCellStyle Cell style to duplicate
1591
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1592
     *
1593
     * @throws Exception
1594
     *
1595
     * @return Worksheet
1596
     */
1597 2
    public function duplicateConditionalStyle(array $pCellStyle, $pRange = '')
1598
    {
1599 2
        foreach ($pCellStyle as $cellStyle) {
1600 2
            if (!($cellStyle instanceof Style\Conditional)) {
1601
                throw new Exception('Style is not a conditional style');
1602
            }
1603
        }
1604
1605
        // Calculate range outer borders
1606 2
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange . ':' . $pRange);
1607
1608
        // Make sure we can loop upwards on rows and columns
1609 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...
1610
            $tmp = $rangeStart;
1611
            $rangeStart = $rangeEnd;
1612
            $rangeEnd = $tmp;
1613
        }
1614
1615
        // Loop through cells and apply styles
1616 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...
1617 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1618 2
                $this->setConditionalStyles(Cell::stringFromColumnIndex($col - 1) . $row, $pCellStyle);
1619
            }
1620
        }
1621
1622 2
        return $this;
1623
    }
1624
1625
    /**
1626
     * Set break on a cell.
1627
     *
1628
     * @param string $pCoordinate Cell coordinate (e.g. A1)
1629
     * @param int $pBreak Break type (type of Worksheet::BREAK_*)
1630
     *
1631
     * @throws Exception
1632
     *
1633
     * @return Worksheet
1634
     */
1635 1
    public function setBreak($pCoordinate, $pBreak)
1636
    {
1637
        // Uppercase coordinate
1638 1
        $pCoordinate = strtoupper($pCoordinate);
1639
1640 1
        if ($pCoordinate != '') {
1641 1
            if ($pBreak == self::BREAK_NONE) {
1642
                if (isset($this->breaks[$pCoordinate])) {
1643
                    unset($this->breaks[$pCoordinate]);
1644
                }
1645
            } else {
1646 1
                $this->breaks[$pCoordinate] = $pBreak;
1647
            }
1648
        } else {
1649
            throw new Exception('No cell coordinate specified.');
1650
        }
1651
1652 1
        return $this;
1653
    }
1654
1655
    /**
1656
     * Set break on a cell by using numeric cell coordinates.
1657
     *
1658
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1659
     * @param int $pRow Numeric row coordinate of the cell
1660
     * @param int $pBreak Break type (type of \PhpOffice\PhpSpreadsheet\Worksheet::BREAK_*)
1661
     *
1662
     * @return Worksheet
1663
     */
1664
    public function setBreakByColumnAndRow($pColumn, $pRow, $pBreak)
1665
    {
1666
        return $this->setBreak(Cell::stringFromColumnIndex($pColumn) . $pRow, $pBreak);
1667
    }
1668
1669
    /**
1670
     * Get breaks.
1671
     *
1672
     * @return array[]
1673
     */
1674 61
    public function getBreaks()
1675
    {
1676 61
        return $this->breaks;
1677
    }
1678
1679
    /**
1680
     * Set merge on a cell range.
1681
     *
1682
     * @param string $pRange Cell range (e.g. A1:E1)
1683
     *
1684
     * @throws Exception
1685
     *
1686
     * @return Worksheet
1687
     */
1688 17
    public function mergeCells($pRange)
1689
    {
1690
        // Uppercase coordinate
1691 17
        $pRange = strtoupper($pRange);
1692
1693 17
        if (strpos($pRange, ':') !== false) {
1694 17
            $this->mergeCells[$pRange] = $pRange;
1695
1696
            // make sure cells are created
1697
1698
            // get the cells in the range
1699 17
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
1700
1701
            // create upper left cell if it does not already exist
1702 17
            $upperLeft = $aReferences[0];
1703 17
            if (!$this->cellExists($upperLeft)) {
1704 13
                $this->getCell($upperLeft)->setValueExplicit(null, Cell\DataType::TYPE_NULL);
1705
            }
1706
1707
            // Blank out the rest of the cells in the range (if they exist)
1708 17
            $count = count($aReferences);
1709 17
            for ($i = 1; $i < $count; ++$i) {
1710 17
                if ($this->cellExists($aReferences[$i])) {
1711 4
                    $this->getCell($aReferences[$i])->setValueExplicit(null, Cell\DataType::TYPE_NULL);
1712
                }
1713
            }
1714
        } else {
1715
            throw new Exception('Merge must be set on a range of cells.');
1716
        }
1717
1718 17
        return $this;
1719
    }
1720
1721
    /**
1722
     * Set merge on a cell range by using numeric cell coordinates.
1723
     *
1724
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1725
     * @param int $pRow1 Numeric row coordinate of the first cell
1726
     * @param int $pColumn2 Numeric column coordinate of the last cell (A = 0)
1727
     * @param int $pRow2 Numeric row coordinate of the last cell
1728
     *
1729
     * @throws Exception
1730
     *
1731
     * @return Worksheet
1732
     */
1733
    public function mergeCellsByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2)
1734
    {
1735
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1736
1737
        return $this->mergeCells($cellRange);
1738
    }
1739
1740
    /**
1741
     * Remove merge on a cell range.
1742
     *
1743
     * @param string $pRange Cell range (e.g. A1:E1)
1744
     *
1745
     * @throws Exception
1746
     *
1747
     * @return Worksheet
1748
     */
1749 12
    public function unmergeCells($pRange)
1750
    {
1751
        // Uppercase coordinate
1752 12
        $pRange = strtoupper($pRange);
1753
1754 12
        if (strpos($pRange, ':') !== false) {
1755 12
            if (isset($this->mergeCells[$pRange])) {
1756 12
                unset($this->mergeCells[$pRange]);
1757
            } else {
1758
                throw new Exception('Cell range ' . $pRange . ' not known as merged.');
1759
            }
1760
        } else {
1761
            throw new Exception('Merge can only be removed from a range of cells.');
1762
        }
1763
1764 12
        return $this;
1765
    }
1766
1767
    /**
1768
     * Remove merge on a cell range by using numeric cell coordinates.
1769
     *
1770
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1771
     * @param int $pRow1 Numeric row coordinate of the first cell
1772
     * @param int $pColumn2 Numeric column coordinate of the last cell (A = 0)
1773
     * @param int $pRow2 Numeric row coordinate of the last cell
1774
     *
1775
     * @throws Exception
1776
     *
1777
     * @return Worksheet
1778
     */
1779
    public function unmergeCellsByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2)
1780
    {
1781
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1782
1783
        return $this->unmergeCells($cellRange);
1784
    }
1785
1786
    /**
1787
     * Get merge cells array.
1788
     *
1789
     * @return array[]
1790
     */
1791 61
    public function getMergeCells()
1792
    {
1793 61
        return $this->mergeCells;
1794
    }
1795
1796
    /**
1797
     * Set merge cells array for the entire sheet. Use instead mergeCells() to merge
1798
     * a single cell range.
1799
     *
1800
     * @param array
1801
     * @param mixed $pValue
1802
     */
1803 15
    public function setMergeCells(array $pValue)
1804
    {
1805 15
        $this->mergeCells = $pValue;
1806
1807 15
        return $this;
1808
    }
1809
1810
    /**
1811
     * Set protection on a cell range.
1812
     *
1813
     * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
1814
     * @param string $pPassword Password to unlock the protection
1815
     * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
1816
     *
1817
     * @throws Exception
1818
     *
1819
     * @return Worksheet
1820
     */
1821 12
    public function protectCells($pRange, $pPassword, $pAlreadyHashed = false)
1822
    {
1823
        // Uppercase coordinate
1824 12
        $pRange = strtoupper($pRange);
1825
1826 12
        if (!$pAlreadyHashed) {
1827 12
            $pPassword = Shared\PasswordHasher::hashPassword($pPassword);
1828
        }
1829 12
        $this->protectedCells[$pRange] = $pPassword;
1830
1831 12
        return $this;
1832
    }
1833
1834
    /**
1835
     * Set protection on a cell range by using numeric cell coordinates.
1836
     *
1837
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1838
     * @param int $pRow1 Numeric row coordinate of the first cell
1839
     * @param int $pColumn2 Numeric column coordinate of the last cell (A = 0)
1840
     * @param int $pRow2 Numeric row coordinate of the last cell
1841
     * @param string $pPassword Password to unlock the protection
1842
     * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
1843
     *
1844
     * @throws Exception
1845
     *
1846
     * @return Worksheet
1847
     */
1848
    public function protectCellsByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2, $pPassword, $pAlreadyHashed = false)
1849
    {
1850
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1851
1852
        return $this->protectCells($cellRange, $pPassword, $pAlreadyHashed);
1853
    }
1854
1855
    /**
1856
     * Remove protection on a cell range.
1857
     *
1858
     * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
1859
     *
1860
     * @throws Exception
1861
     *
1862
     * @return Worksheet
1863
     */
1864 12
    public function unprotectCells($pRange)
1865
    {
1866
        // Uppercase coordinate
1867 12
        $pRange = strtoupper($pRange);
1868
1869 12
        if (isset($this->protectedCells[$pRange])) {
1870 12
            unset($this->protectedCells[$pRange]);
1871
        } else {
1872
            throw new Exception('Cell range ' . $pRange . ' not known as protected.');
1873
        }
1874
1875 12
        return $this;
1876
    }
1877
1878
    /**
1879
     * Remove protection on a cell range by using numeric cell coordinates.
1880
     *
1881
     * @param int $pColumn1 Numeric column coordinate of the first cell (A = 0)
1882
     * @param int $pRow1 Numeric row coordinate of the first cell
1883
     * @param int $pColumn2 Numeric column coordinate of the last cell (A = 0)
1884
     * @param int $pRow2 Numeric row coordinate of the last cell
1885
     * @param string $pPassword Password to unlock the protection
1886
     * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
1887
     *
1888
     * @throws Exception
1889
     *
1890
     * @return Worksheet
1891
     */
1892
    public function unprotectCellsByColumnAndRow($pColumn1, $pRow1, $pColumn2, $pRow2, $pPassword, $pAlreadyHashed = false)
0 ignored issues
show
Unused Code introduced by
The parameter $pPassword is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $pAlreadyHashed is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3024
            }
3025
        }
3026
3027 85
        $this->codeName = $pValue;
3028
3029 85
        return $this;
3030
    }
3031
3032
    /**
3033
     * Return the code name of the sheet.
3034
     *
3035
     * @return null|string
3036
     */
3037 85
    public function getCodeName()
3038
    {
3039 85
        return $this->codeName;
3040
    }
3041
3042
    /**
3043
     * Sheet has a code name ?
3044
     *
3045
     * @return bool
3046
     */
3047
    public function hasCodeName()
3048
    {
3049
        return !(is_null($this->codeName));
3050
    }
3051
}
3052