Completed
Push — develop ( 93e220...f99eb8 )
by Adrien
22:43
created

Worksheet::setHyperlink()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 10
ccs 5
cts 5
cp 1
crap 2
rs 9.4285
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
use ArrayObject;
6
7
/**
8
 * Copyright (c) 2006 - 2016 PhpSpreadsheet.
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
 * License as published by the Free Software Foundation; either
13
 * version 2.1 of the License, or (at your option) any later version.
14
 *
15
 * This library is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
 * License along with this library; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23
 *
24
 * @category   PhpSpreadsheet
25
 *
26
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
27
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
28
 */
29
class Worksheet implements IComparable
30
{
31
    /* Break types */
32
    const BREAK_NONE = 0;
33
    const BREAK_ROW = 1;
34
    const BREAK_COLUMN = 2;
35
36
    /* Sheet state */
37
    const SHEETSTATE_VISIBLE = 'visible';
38
    const SHEETSTATE_HIDDEN = 'hidden';
39
    const SHEETSTATE_VERYHIDDEN = 'veryHidden';
40
41
    /**
42
     * Invalid characters in sheet title.
43
     *
44
     * @var array
45
     */
46
    private static $invalidCharacters = ['*', ':', '/', '\\', '?', '[', ']'];
47
48
    /**
49
     * Parent spreadsheet.
50
     *
51
     * @var Spreadsheet
52
     */
53
    private $parent;
54
55
    /**
56
     * Cacheable collection of cells.
57
     *
58
     * @var CachedObjectStorage_xxx
59
     */
60
    private $cellCollection;
61
62
    /**
63
     * Collection of row dimensions.
64
     *
65
     * @var Worksheet\RowDimension[]
66
     */
67
    private $rowDimensions = [];
68
69
    /**
70
     * Default row dimension.
71
     *
72
     * @var Worksheet\RowDimension
73
     */
74
    private $defaultRowDimension;
75
76
    /**
77
     * Collection of column dimensions.
78
     *
79
     * @var Worksheet\ColumnDimension[]
80
     */
81
    private $columnDimensions = [];
82
83
    /**
84
     * Default column dimension.
85
     *
86
     * @var Worksheet\ColumnDimension
87
     */
88
    private $defaultColumnDimension = null;
89
90
    /**
91
     * Collection of drawings.
92
     *
93
     * @var Worksheet\BaseDrawing[]
94
     */
95
    private $drawingCollection = null;
96
97
    /**
98
     * Collection of Chart objects.
99
     *
100
     * @var Chart[]
101
     */
102
    private $chartCollection = [];
103
104
    /**
105
     * Worksheet title.
106
     *
107
     * @var string
108
     */
109
    private $title;
110
111
    /**
112
     * Sheet state.
113
     *
114
     * @var string
115
     */
116
    private $sheetState;
117
118
    /**
119
     * Page setup.
120
     *
121
     * @var Worksheet\PageSetup
122
     */
123
    private $pageSetup;
124
125
    /**
126
     * Page margins.
127
     *
128
     * @var Worksheet\PageMargins
129
     */
130
    private $pageMargins;
131
132
    /**
133
     * Page header/footer.
134
     *
135
     * @var Worksheet\HeaderFooter
136
     */
137
    private $headerFooter;
138
139
    /**
140
     * Sheet view.
141
     *
142
     * @var Worksheet\SheetView
143
     */
144
    private $sheetView;
145
146
    /**
147
     * Protection.
148
     *
149
     * @var Worksheet\Protection
150
     */
151
    private $protection;
152
153
    /**
154
     * Collection of styles.
155
     *
156
     * @var Style[]
157
     */
158
    private $styles = [];
159
160
    /**
161
     * Conditional styles. Indexed by cell coordinate, e.g. 'A1'.
162
     *
163
     * @var array
164
     */
165
    private $conditionalStylesCollection = [];
166
167
    /**
168
     * Is the current cell collection sorted already?
169
     *
170
     * @var bool
171
     */
172
    private $cellCollectionIsSorted = false;
173
174
    /**
175
     * Collection of breaks.
176
     *
177
     * @var array
178
     */
179
    private $breaks = [];
180
181
    /**
182
     * Collection of merged cell ranges.
183
     *
184
     * @var array
185
     */
186
    private $mergeCells = [];
187
188
    /**
189
     * Collection of protected cell ranges.
190
     *
191
     * @var array
192
     */
193
    private $protectedCells = [];
194
195
    /**
196
     * Autofilter Range and selection.
197
     *
198
     * @var Worksheet\AutoFilter
199
     */
200
    private $autoFilter;
201
202
    /**
203
     * Freeze pane.
204
     *
205
     * @var string
206
     */
207
    private $freezePane = '';
208
209
    /**
210
     * Show gridlines?
211
     *
212
     * @var bool
213
     */
214
    private $showGridlines = true;
215
216
    /**
217
     * Print gridlines?
218
     *
219
     * @var bool
220
     */
221
    private $printGridlines = false;
222
223
    /**
224
     * Show row and column headers?
225
     *
226
     * @var bool
227
     */
228
    private $showRowColHeaders = true;
229
230
    /**
231
     * Show summary below? (Row/Column outline).
232
     *
233
     * @var bool
234
     */
235
    private $showSummaryBelow = true;
236
237
    /**
238
     * Show summary right? (Row/Column outline).
239
     *
240
     * @var bool
241
     */
242
    private $showSummaryRight = true;
243
244
    /**
245
     * Collection of comments.
246
     *
247
     * @var Comment[]
248
     */
249
    private $comments = [];
250
251
    /**
252
     * Active cell. (Only one!).
253
     *
254
     * @var string
255
     */
256
    private $activeCell = 'A1';
257
258
    /**
259
     * Selected cells.
260
     *
261
     * @var string
262
     */
263
    private $selectedCells = 'A1';
264
265
    /**
266
     * Cached highest column.
267
     *
268
     * @var string
269
     */
270
    private $cachedHighestColumn = 'A';
271
272
    /**
273
     * Cached highest row.
274
     *
275
     * @var int
276
     */
277
    private $cachedHighestRow = 1;
278
279
    /**
280
     * Right-to-left?
281
     *
282
     * @var bool
283
     */
284
    private $rightToLeft = false;
285
286
    /**
287
     * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'.
288
     *
289
     * @var array
290
     */
291
    private $hyperlinkCollection = [];
292
293
    /**
294
     * Data validation objects. Indexed by cell coordinate, e.g. 'A1'.
295
     *
296
     * @var array
297
     */
298
    private $dataValidationCollection = [];
299
300
    /**
301
     * Tab color.
302
     *
303
     * @var Style\Color
304
     */
305
    private $tabColor;
306
307
    /**
308
     * Dirty flag.
309
     *
310
     * @var bool
311
     */
312
    private $dirty = true;
313
314
    /**
315
     * Hash.
316
     *
317
     * @var string
318
     */
319
    private $hash;
320
321
    /**
322
     * CodeName.
323
     *
324
     * @var string
325
     */
326
    private $codeName = null;
327
328
    /**
329
     * Create a new worksheet.
330
     *
331
     * @param Spreadsheet $parent
332
     * @param string $pTitle
333
     */
334 81
    public function __construct(Spreadsheet $parent = null, $pTitle = 'Worksheet')
335
    {
336
        // Set parent and title
337 81
        $this->parent = $parent;
338 81
        $this->setTitle($pTitle, false);
339
        // setTitle can change $pTitle
340 81
        $this->setCodeName($this->getTitle());
341 81
        $this->setSheetState(self::SHEETSTATE_VISIBLE);
342
343 81
        $this->cellCollection = CachedObjectStorageFactory::getInstance($this);
0 ignored issues
show
Documentation Bug introduced by
It seems like \PhpOffice\PhpSpreadshee...ory::getInstance($this) of type object<PhpOffice\PhpSpre...edObjectStorage\ICache> is incompatible with the declared type object<PhpOffice\PhpSpre...achedObjectStorage_xxx> of property $cellCollection.

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...
344
        // Set page setup
345 81
        $this->pageSetup = new Worksheet\PageSetup();
346
        // Set page margins
347 81
        $this->pageMargins = new Worksheet\PageMargins();
348
        // Set page header/footer
349 81
        $this->headerFooter = new Worksheet\HeaderFooter();
350
        // Set sheet view
351 81
        $this->sheetView = new Worksheet\SheetView();
352
        // Drawing collection
353 81
        $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...
354
        // Chart collection
355 81
        $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...
356
        // Protection
357 81
        $this->protection = new Worksheet\Protection();
358
        // Default row dimension
359 81
        $this->defaultRowDimension = new Worksheet\RowDimension(null);
360
        // Default column dimension
361 81
        $this->defaultColumnDimension = new Worksheet\ColumnDimension(null);
362 81
        $this->autoFilter = new Worksheet\AutoFilter(null, $this);
363 81
    }
364
365
    /**
366
     * Disconnect all cells from this Worksheet object,
367
     * typically so that the worksheet object can be unset.
368
     */
369 1
    public function disconnectCells()
370
    {
371 1
        if ($this->cellCollection !== null) {
372 1
            $this->cellCollection->unsetWorksheetCells();
373 1
            $this->cellCollection = null;
374
        }
375
        //    detach ourself from the workbook, so that it can then delete this worksheet successfully
376 1
        $this->parent = null;
377 1
    }
378
379
    /**
380
     * Code to execute when this worksheet is unset().
381
     */
382 1
    public function __destruct()
383
    {
384 1
        Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title);
385
386 1
        $this->disconnectCells();
387 1
    }
388
389
    /**
390
     * Return the cache controller for the cell collection.
391
     *
392
     * @return CachedObjectStorage_xxx
393
     */
394 73
    public function getCellCacheController()
395
    {
396 73
        return $this->cellCollection;
397
    }
398
399
    /**
400
     * Get array of invalid characters for sheet title.
401
     *
402
     * @return array
403
     */
404
    public static function getInvalidCharacters()
405
    {
406
        return self::$invalidCharacters;
407
    }
408
409
    /**
410
     * Check sheet code name for valid Excel syntax.
411
     *
412
     * @param string $pValue The string to check
413
     *
414
     * @throws Exception
415
     *
416
     * @return string The valid string
417
     */
418 81
    private static function checkSheetCodeName($pValue)
419
    {
420 81
        $CharCount = Shared\StringHelper::countCharacters($pValue);
421 81
        if ($CharCount == 0) {
422
            throw new Exception('Sheet code name cannot be empty.');
423
        }
424
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ] and  first and last characters cannot be a "'"
425 81
        if ((str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) ||
426 81
            (Shared\StringHelper::substring($pValue, -1, 1) == '\'') ||
427 81
            (Shared\StringHelper::substring($pValue, 0, 1) == '\'')) {
428
            throw new Exception('Invalid character found in sheet code name');
429
        }
430
431
        // Maximum 31 characters allowed for sheet title
432 81
        if ($CharCount > 31) {
433
            throw new Exception('Maximum 31 characters allowed in sheet code name.');
434
        }
435
436 81
        return $pValue;
437
    }
438
439
    /**
440
     * Check sheet title for valid Excel syntax.
441
     *
442
     * @param string $pValue The string to check
443
     *
444
     * @throws Exception
445
     *
446
     * @return string The valid string
447
     */
448 81
    private static function checkSheetTitle($pValue)
449
    {
450
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ]
451 81
        if (str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) {
452
            throw new Exception('Invalid character found in sheet title');
453
        }
454
455
        // Maximum 31 characters allowed for sheet title
456 81
        if (Shared\StringHelper::countCharacters($pValue) > 31) {
457
            throw new Exception('Maximum 31 characters allowed in sheet title.');
458
        }
459
460 81
        return $pValue;
461
    }
462
463
    /**
464
     * Get collection of cells.
465
     *
466
     * @param bool $pSorted Also sort the cell collection?
467
     *
468
     * @return Cell[]
469
     */
470 64
    public function getCellCollection($pSorted = true)
471
    {
472 64
        if ($pSorted) {
473
            // Re-order cell collection
474 63
            return $this->sortCellCollection();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sortCellCollection(); (PhpOffice\PhpSpreadsheet\Worksheet) is incompatible with the return type documented by PhpOffice\PhpSpreadsheet...heet::getCellCollection of type PhpOffice\PhpSpreadsheet\Cell[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
475
        }
476 63
        if ($this->cellCollection !== null) {
477 63
            return $this->cellCollection->getCellList();
478
        }
479
480
        return [];
481
    }
482
483
    /**
484
     * Sort collection of cells.
485
     *
486
     * @return Worksheet
487
     */
488 63
    public function sortCellCollection()
489
    {
490 63
        if ($this->cellCollection !== null) {
491 63
            return $this->cellCollection->getSortedCellList();
492
        }
493
494
        return [];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type documented by PhpOffice\PhpSpreadsheet...eet::sortCellCollection of type PhpOffice\PhpSpreadsheet\Worksheet.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
495
    }
496
497
    /**
498
     * Get collection of row dimensions.
499
     *
500
     * @return Worksheet\RowDimension[]
501
     */
502 62
    public function getRowDimensions()
503
    {
504 62
        return $this->rowDimensions;
505
    }
506
507
    /**
508
     * Get default row dimension.
509
     *
510
     * @return Worksheet\RowDimension
511
     */
512 62
    public function getDefaultRowDimension()
513
    {
514 62
        return $this->defaultRowDimension;
515
    }
516
517
    /**
518
     * Get collection of column dimensions.
519
     *
520
     * @return Worksheet\ColumnDimension[]
521
     */
522 62
    public function getColumnDimensions()
523
    {
524 62
        return $this->columnDimensions;
525
    }
526
527
    /**
528
     * Get default column dimension.
529
     *
530
     * @return Worksheet\ColumnDimension
531
     */
532 58
    public function getDefaultColumnDimension()
533
    {
534 58
        return $this->defaultColumnDimension;
535
    }
536
537
    /**
538
     * Get collection of drawings.
539
     *
540
     * @return Worksheet\BaseDrawing[]
541
     */
542 62
    public function getDrawingCollection()
543
    {
544 62
        return $this->drawingCollection;
545
    }
546
547
    /**
548
     * Get collection of charts.
549
     *
550
     * @return Chart[]
551
     */
552 14
    public function getChartCollection()
553
    {
554 14
        return $this->chartCollection;
555
    }
556
557
    /**
558
     * Add chart.
559
     *
560
     * @param Chart $pChart
561
     * @param int|null $iChartIndex Index where chart should go (0,1,..., or null for last)
562
     *
563
     * @return Chart
564
     */
565 14
    public function addChart(Chart $pChart = null, $iChartIndex = null)
566
    {
567 14
        $pChart->setWorksheet($this);
0 ignored issues
show
Bug introduced by
It seems like $pChart is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
568 14
        if (is_null($iChartIndex)) {
569 14
            $this->chartCollection[] = $pChart;
570
        } else {
571
            // Insert the chart at the requested index
572
            array_splice($this->chartCollection, $iChartIndex, 0, [$pChart]);
573
        }
574
575 14
        return $pChart;
576
    }
577
578
    /**
579
     * Return the count of charts on this worksheet.
580
     *
581
     * @return int The number of charts
582
     */
583 14
    public function getChartCount()
584
    {
585 14
        return count($this->chartCollection);
586
    }
587
588
    /**
589
     * Get a chart by its index position.
590
     *
591
     * @param string $index Chart index position
592
     *
593
     * @throws Exception
594
     *
595
     * @return false|Chart
596
     */
597 13
    public function getChartByIndex($index = null)
598
    {
599 13
        $chartCount = count($this->chartCollection);
600 13
        if ($chartCount == 0) {
601
            return false;
602
        }
603 13
        if (is_null($index)) {
604
            $index = --$chartCount;
605
        }
606 13
        if (!isset($this->chartCollection[$index])) {
607
            return false;
608
        }
609
610 13
        return $this->chartCollection[$index];
611
    }
612
613
    /**
614
     * Return an array of the names of charts on this worksheet.
615
     *
616
     * @throws Exception
617
     *
618
     * @return string[] The names of charts
619
     */
620 1
    public function getChartNames()
621
    {
622 1
        $chartNames = [];
623 1
        foreach ($this->chartCollection as $chart) {
624 1
            $chartNames[] = $chart->getName();
625
        }
626
627 1
        return $chartNames;
628
    }
629
630
    /**
631
     * Get a chart by name.
632
     *
633
     * @param string $chartName Chart name
634
     *
635
     * @throws Exception
636
     *
637
     * @return false|Chart
638
     */
639 1
    public function getChartByName($chartName = '')
640
    {
641 1
        $chartCount = count($this->chartCollection);
642 1
        if ($chartCount == 0) {
643
            return false;
644
        }
645 1
        foreach ($this->chartCollection as $index => $chart) {
646 1
            if ($chart->getName() == $chartName) {
647 1
                return $this->chartCollection[$index];
648
            }
649
        }
650
651
        return false;
652
    }
653
654
    /**
655
     * Refresh column dimensions.
656
     *
657
     * @return Worksheet
658
     */
659 15
    public function refreshColumnDimensions()
660
    {
661 15
        $currentColumnDimensions = $this->getColumnDimensions();
662 15
        $newColumnDimensions = [];
663
664 15
        foreach ($currentColumnDimensions as $objColumnDimension) {
665 15
            $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension;
666
        }
667
668 15
        $this->columnDimensions = $newColumnDimensions;
669
670 15
        return $this;
671
    }
672
673
    /**
674
     * Refresh row dimensions.
675
     *
676
     * @return Worksheet
677
     */
678 2
    public function refreshRowDimensions()
679
    {
680 2
        $currentRowDimensions = $this->getRowDimensions();
681 2
        $newRowDimensions = [];
682
683 2
        foreach ($currentRowDimensions as $objRowDimension) {
684 2
            $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension;
685
        }
686
687 2
        $this->rowDimensions = $newRowDimensions;
688
689 2
        return $this;
690
    }
691
692
    /**
693
     * Calculate worksheet dimension.
694
     *
695
     * @return string String containing the dimension of this worksheet
696
     */
697 61
    public function calculateWorksheetDimension()
698
    {
699
        // Return
700 61
        return 'A1' . ':' . $this->getHighestColumn() . $this->getHighestRow();
701
    }
702
703
    /**
704
     * Calculate worksheet data dimension.
705
     *
706
     * @return string String containing the dimension of this worksheet that actually contain data
707
     */
708
    public function calculateWorksheetDataDimension()
709
    {
710
        // Return
711
        return 'A1' . ':' . $this->getHighestDataColumn() . $this->getHighestDataRow();
712
    }
713
714
    /**
715
     * Calculate widths for auto-size columns.
716
     *
717
     * @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...
718
     */
719 46
    public function calculateColumnWidths()
720
    {
721
        // initialize $autoSizes array
722 46
        $autoSizes = [];
723 46
        foreach ($this->getColumnDimensions() as $colDimension) {
724 26
            if ($colDimension->getAutoSize()) {
725 26
                $autoSizes[$colDimension->getColumnIndex()] = -1;
726
            }
727
        }
728
729
        // There is only something to do if there are some auto-size columns
730 46
        if (!empty($autoSizes)) {
731
            // build list of cells references that participate in a merge
732 14
            $isMergeCell = [];
733 14
            foreach ($this->getMergeCells() as $cells) {
734 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...
735 11
                    $isMergeCell[$cellReference] = true;
736
                }
737
            }
738
739
            // loop through all cells in the worksheet
740 14
            foreach ($this->getCellCollection(false) as $cellID) {
741 14
                $cell = $this->getCell($cellID, false);
742 14
                if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
743
                    //Determine if cell is in merge range
744 14
                    $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentAddress()]);
745
746
                    //By default merged cells should be ignored
747 14
                    $isMergedButProceed = false;
748
749
                    //The only exception is if it's a merge range value cell of a 'vertical' randge (1 column wide)
750 14
                    if ($isMerged && $cell->isMergeRangeValueCell()) {
751
                        $range = $cell->getMergeRange();
752
                        $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...
753
                        if ($rangeBoundaries[0] == 1) {
754
                            $isMergedButProceed = true;
755
                        }
756
                    }
757
758
                    // Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range
759 14
                    if (!$isMerged || $isMergedButProceed) {
760
                        // Calculated value
761
                        // To formatted string
762 14
                        $cellValue = Style\NumberFormat::toFormattedString(
763 14
                            $cell->getCalculatedValue(),
764 14
                            $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
765
                        );
766
767 14
                        $autoSizes[$this->cellCollection->getCurrentColumn()] = max(
768 14
                            (float) $autoSizes[$this->cellCollection->getCurrentColumn()],
769 14
                            (float) Shared\Font::calculateColumnWidth(
770 14
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
771
                                $cellValue,
772 14
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
773 14
                                $this->getParent()->getDefaultStyle()->getFont()
774
                            )
775
                        );
776
                    }
777
                }
778
            }
779
780
            // adjust column widths
781 14
            foreach ($autoSizes as $columnIndex => $width) {
782 14
                if ($width == -1) {
783
                    $width = $this->getDefaultColumnDimension()->getWidth();
784
                }
785 14
                $this->getColumnDimension($columnIndex)->setWidth($width);
786
            }
787
        }
788
789 46
        return $this;
790
    }
791
792
    /**
793
     * Get parent.
794
     *
795
     * @return Spreadsheet
796
     */
797 81
    public function getParent()
798
    {
799 81
        return $this->parent;
800
    }
801
802
    /**
803
     * Re-bind parent.
804
     *
805
     * @param Spreadsheet $parent
806
     *
807
     * @return Worksheet
808
     */
809 1
    public function rebindParent(Spreadsheet $parent)
810
    {
811 1
        if ($this->parent !== null) {
812 1
            $namedRanges = $this->parent->getNamedRanges();
813 1
            foreach ($namedRanges as $namedRange) {
814
                $parent->addNamedRange($namedRange);
815
            }
816
817 1
            $this->parent->removeSheetByIndex(
818 1
                $this->parent->getIndex($this)
819
            );
820
        }
821 1
        $this->parent = $parent;
822
823 1
        return $this;
824
    }
825
826
    /**
827
     * Get title.
828
     *
829
     * @return string
830
     */
831 81
    public function getTitle()
832
    {
833 81
        return $this->title;
834
    }
835
836
    /**
837
     * Set title.
838
     *
839
     * @param string $pValue String containing the dimension of this worksheet
840
     * @param string $updateFormulaCellReferences boolean Flag indicating whether cell references in formulae should
841
     *            be updated to reflect the new sheet name.
842
     *          This should be left as the default true, unless you are
843
     *          certain that no formula cells on any worksheet contain
844
     *          references to this worksheet
845
     *
846
     * @return Worksheet
847
     */
848 81
    public function setTitle($pValue = 'Worksheet', $updateFormulaCellReferences = true)
849
    {
850
        // Is this a 'rename' or not?
851 81
        if ($this->getTitle() == $pValue) {
852 4
            return $this;
853
        }
854
855
        // Syntax check
856 81
        self::checkSheetTitle($pValue);
857
858
        // Old title
859 81
        $oldTitle = $this->getTitle();
860
861 81
        if ($this->parent) {
862
            // Is there already such sheet name?
863 73
            if ($this->parent->sheetNameExists($pValue)) {
864
                // Use name, but append with lowest possible integer
865
866 2
                if (Shared\StringHelper::countCharacters($pValue) > 29) {
867
                    $pValue = Shared\StringHelper::substring($pValue, 0, 29);
868
                }
869 2
                $i = 1;
870 2 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...
871
                    ++$i;
872
                    if ($i == 10) {
873
                        if (Shared\StringHelper::countCharacters($pValue) > 28) {
874
                            $pValue = Shared\StringHelper::substring($pValue, 0, 28);
875
                        }
876
                    } elseif ($i == 100) {
877
                        if (Shared\StringHelper::countCharacters($pValue) > 27) {
878
                            $pValue = Shared\StringHelper::substring($pValue, 0, 27);
879
                        }
880
                    }
881
                }
882
883 2
                $altTitle = $pValue . ' ' . $i;
884
885 2
                return $this->setTitle($altTitle, $updateFormulaCellReferences);
886
            }
887
        }
888
889
        // Set title
890 81
        $this->title = $pValue;
891 81
        $this->dirty = true;
892
893 81
        if ($this->parent && $this->parent->getCalculationEngine()) {
894
            // New title
895 73
            $newTitle = $this->getTitle();
896 73
            $this->parent->getCalculationEngine()
897 73
                ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
898 73
            if ($updateFormulaCellReferences) {
899 27
                ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
900
            }
901
        }
902
903 81
        return $this;
904
    }
905
906
    /**
907
     * Get sheet state.
908
     *
909
     * @return string Sheet state (visible, hidden, veryHidden)
910
     */
911 58
    public function getSheetState()
912
    {
913 58
        return $this->sheetState;
914
    }
915
916
    /**
917
     * Set sheet state.
918
     *
919
     * @param string $value Sheet state (visible, hidden, veryHidden)
920
     *
921
     * @return Worksheet
922
     */
923 81
    public function setSheetState($value = self::SHEETSTATE_VISIBLE)
924
    {
925 81
        $this->sheetState = $value;
926
927 81
        return $this;
928
    }
929
930
    /**
931
     * Get page setup.
932
     *
933
     * @return Worksheet\PageSetup
934
     */
935 62
    public function getPageSetup()
936
    {
937 62
        return $this->pageSetup;
938
    }
939
940
    /**
941
     * Set page setup.
942
     *
943
     * @param Worksheet\PageSetup $pValue
944
     *
945
     * @return Worksheet
946
     */
947
    public function setPageSetup(Worksheet\PageSetup $pValue)
948
    {
949
        $this->pageSetup = $pValue;
950
951
        return $this;
952
    }
953
954
    /**
955
     * Get page margins.
956
     *
957
     * @return Worksheet\PageMargins
958
     */
959 62
    public function getPageMargins()
960
    {
961 62
        return $this->pageMargins;
962
    }
963
964
    /**
965
     * Set page margins.
966
     *
967
     * @param Worksheet\PageMargins $pValue
968
     *
969
     * @return Worksheet
970
     */
971
    public function setPageMargins(Worksheet\PageMargins $pValue)
972
    {
973
        $this->pageMargins = $pValue;
974
975
        return $this;
976
    }
977
978
    /**
979
     * Get page header/footer.
980
     *
981
     * @return Worksheet\HeaderFooter
982
     */
983 62
    public function getHeaderFooter()
984
    {
985 62
        return $this->headerFooter;
986
    }
987
988
    /**
989
     * Set page header/footer.
990
     *
991
     * @param Worksheet\HeaderFooter $pValue
992
     *
993
     * @return Worksheet
994
     */
995
    public function setHeaderFooter(Worksheet\HeaderFooter $pValue)
996
    {
997
        $this->headerFooter = $pValue;
998
999
        return $this;
1000
    }
1001
1002
    /**
1003
     * Get sheet view.
1004
     *
1005
     * @return Worksheet\SheetView
1006
     */
1007 58
    public function getSheetView()
1008
    {
1009 58
        return $this->sheetView;
1010
    }
1011
1012
    /**
1013
     * Set sheet view.
1014
     *
1015
     * @param Worksheet\SheetView $pValue
1016
     *
1017
     * @return Worksheet
1018
     */
1019
    public function setSheetView(Worksheet\SheetView $pValue)
1020
    {
1021
        $this->sheetView = $pValue;
1022
1023
        return $this;
1024
    }
1025
1026
    /**
1027
     * Get Protection.
1028
     *
1029
     * @return Worksheet\Protection
1030
     */
1031 62
    public function getProtection()
1032
    {
1033 62
        return $this->protection;
1034
    }
1035
1036
    /**
1037
     * Set Protection.
1038
     *
1039
     * @param Worksheet\Protection $pValue
1040
     *
1041
     * @return Worksheet
1042
     */
1043
    public function setProtection(Worksheet\Protection $pValue)
1044
    {
1045
        $this->protection = $pValue;
1046
        $this->dirty = true;
1047
1048
        return $this;
1049
    }
1050
1051
    /**
1052
     * Get highest worksheet column.
1053
     *
1054
     * @param string $row Return the data highest column for the specified row,
1055
     *                                     or the highest column of any row if no row number is passed
1056
     *
1057
     * @return string Highest column name
1058
     */
1059 66
    public function getHighestColumn($row = null)
1060
    {
1061 66
        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...
1062 66
            return $this->cachedHighestColumn;
1063
        }
1064
1065
        return $this->getHighestDataColumn($row);
1066
    }
1067
1068
    /**
1069
     * Get highest worksheet column that contains data.
1070
     *
1071
     * @param string $row Return the highest data column for the specified row,
1072
     *                                     or the highest data column of any row if no row number is passed
1073
     *
1074
     * @return string Highest column name that contains data
1075
     */
1076 13
    public function getHighestDataColumn($row = null)
1077
    {
1078 13
        return $this->cellCollection->getHighestColumn($row);
1079
    }
1080
1081
    /**
1082
     * Get highest worksheet row.
1083
     *
1084
     * @param string $column Return the highest data row for the specified column,
1085
     *                                     or the highest row of any column if no column letter is passed
1086
     *
1087
     * @return int Highest row number
1088
     */
1089 66
    public function getHighestRow($column = null)
1090
    {
1091 66
        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...
1092 66
            return $this->cachedHighestRow;
1093
        }
1094
1095
        return $this->getHighestDataRow($column);
1096
    }
1097
1098
    /**
1099
     * Get highest worksheet row that contains data.
1100
     *
1101
     * @param string $column Return the highest data row for the specified column,
1102
     *                                     or the highest data row of any column if no column letter is passed
1103
     *
1104
     * @return string Highest row number that contains data
1105
     */
1106 15
    public function getHighestDataRow($column = null)
1107
    {
1108 15
        return $this->cellCollection->getHighestRow($column);
1109
    }
1110
1111
    /**
1112
     * Get highest worksheet column and highest row that have cell records.
1113
     *
1114
     * @return array Highest column name and highest row number
1115
     */
1116
    public function getHighestRowAndColumn()
1117
    {
1118
        return $this->cellCollection->getHighestRowAndColumn();
1119
    }
1120
1121
    /**
1122
     * Set a cell value.
1123
     *
1124
     * @param string $pCoordinate Coordinate of the cell
1125
     * @param mixed $pValue Value of the cell
1126
     * @param bool $returnCell Return the worksheet (false, default) or the cell (true)
1127
     *
1128
     * @return Worksheet|Cell Depending on the last parameter being specified
1129
     */
1130 40
    public function setCellValue($pCoordinate = 'A1', $pValue = null, $returnCell = false)
1131
    {
1132 40
        $cell = $this->getCell(strtoupper($pCoordinate))->setValue($pValue);
1133
1134 40
        return ($returnCell) ? $cell : $this;
1135
    }
1136
1137
    /**
1138
     * Set a cell value by using numeric cell coordinates.
1139
     *
1140
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1141
     * @param int $pRow Numeric row coordinate of the cell
1142
     * @param mixed $pValue Value of the cell
1143
     * @param bool $returnCell Return the worksheet (false, default) or the cell (true)
1144
     *
1145
     * @return Worksheet|Cell Depending on the last parameter being specified
1146
     */
1147
    public function setCellValueByColumnAndRow($pColumn = 0, $pRow = 1, $pValue = null, $returnCell = false)
1148
    {
1149
        $cell = $this->getCellByColumnAndRow($pColumn, $pRow)->setValue($pValue);
1150
1151
        return ($returnCell) ? $cell : $this;
1152
    }
1153
1154
    /**
1155
     * Set a cell value.
1156
     *
1157
     * @param string $pCoordinate Coordinate of the cell
1158
     * @param mixed $pValue Value of the cell
1159
     * @param string $pDataType Explicit data type
1160
     * @param bool $returnCell Return the worksheet (false, default) or the cell (true)
1161
     *
1162
     * @return Worksheet|Cell Depending on the last parameter being specified
1163
     */
1164 1
    public function setCellValueExplicit($pCoordinate = 'A1', $pValue = null, $pDataType = Cell\DataType::TYPE_STRING, $returnCell = false)
1165
    {
1166
        // Set value
1167 1
        $cell = $this->getCell(strtoupper($pCoordinate))->setValueExplicit($pValue, $pDataType);
1168
1169 1
        return ($returnCell) ? $cell : $this;
1170
    }
1171
1172
    /**
1173
     * Set a cell value by using numeric cell coordinates.
1174
     *
1175
     * @param int $pColumn Numeric column coordinate of the cell
1176
     * @param int $pRow Numeric row coordinate of the cell
1177
     * @param mixed $pValue Value of the cell
1178
     * @param string $pDataType Explicit data type
1179
     * @param bool $returnCell Return the worksheet (false, default) or the cell (true)
1180
     *
1181
     * @return Worksheet|Cell Depending on the last parameter being specified
1182
     */
1183
    public function setCellValueExplicitByColumnAndRow($pColumn = 0, $pRow = 1, $pValue = null, $pDataType = Cell\DataType::TYPE_STRING, $returnCell = false)
1184
    {
1185
        $cell = $this->getCellByColumnAndRow($pColumn, $pRow)->setValueExplicit($pValue, $pDataType);
1186
1187
        return ($returnCell) ? $cell : $this;
1188
    }
1189
1190
    /**
1191
     * Get cell at a specific coordinate.
1192
     *
1193
     * @param string $pCoordinate Coordinate of the cell
1194
     * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
1195
     *                                       already exist, or a null should be returned instead
1196
     *
1197
     * @throws Exception
1198
     *
1199
     * @return null|Cell Cell that was found/created or null
1200
     */
1201 72
    public function getCell($pCoordinate = 'A1', $createIfNotExists = true)
1202
    {
1203
        // Check cell collection
1204 72
        if ($this->cellCollection->isDataSet(strtoupper($pCoordinate))) {
1205 70
            return $this->cellCollection->getCacheData($pCoordinate);
1206
        }
1207
1208
        // Worksheet reference?
1209 71 View Code Duplication
        if (strpos($pCoordinate, '!') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1210 1
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1211
1212 1
            return $this->parent->getSheetByName($worksheetReference[0])->getCell(strtoupper($worksheetReference[1]), $createIfNotExists);
1213
        }
1214
1215
        // Named range?
1216 71
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1217 71
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1218
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1219
            if ($namedRange !== null) {
1220
                $pCoordinate = $namedRange->getRange();
1221
1222
                return $namedRange->getWorksheet()->getCell($pCoordinate, $createIfNotExists);
1223
            }
1224
        }
1225
1226
        // Uppercase coordinate
1227 71
        $pCoordinate = strtoupper($pCoordinate);
1228
1229 71 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...
1230
            throw new Exception('Cell coordinate can not be a range of cells.');
1231 71
        } elseif (strpos($pCoordinate, '$') !== false) {
1232
            throw new Exception('Cell coordinate must not be absolute.');
1233
        }
1234
1235
        // Create new cell object, if required
1236 71
        return $createIfNotExists ? $this->createNewCell($pCoordinate) : null;
1237
    }
1238
1239
    /**
1240
     * Get cell at a specific coordinate by using numeric cell coordinates.
1241
     *
1242
     * @param string $pColumn Numeric column coordinate of the cell
1243
     * @param string $pRow Numeric row coordinate of the cell
1244
     * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
1245
     *                                       already exist, or a null should be returned instead
1246
     *
1247
     * @return null|Cell Cell that was found/created or null
1248
     */
1249 40
    public function getCellByColumnAndRow($pColumn = 0, $pRow = 1, $createIfNotExists = true)
1250
    {
1251 40
        $columnLetter = Cell::stringFromColumnIndex($pColumn);
1252 40
        $coordinate = $columnLetter . $pRow;
1253
1254 40
        if ($this->cellCollection->isDataSet($coordinate)) {
1255 39
            return $this->cellCollection->getCacheData($coordinate);
1256
        }
1257
1258
        // Create new cell object, if required
1259 22
        return $createIfNotExists ? $this->createNewCell($coordinate) : null;
1260
    }
1261
1262
    /**
1263
     * Create a new cell at the specified coordinate.
1264
     *
1265
     * @param string $pCoordinate Coordinate of the cell
1266
     *
1267
     * @return Cell Cell that was created
1268
     */
1269 73
    private function createNewCell($pCoordinate)
1270
    {
1271 73
        $cell = $this->cellCollection->addCacheData(
1272
            $pCoordinate,
1273 73
            new Cell(null, Cell\DataType::TYPE_NULL, $this)
1274
        );
1275 73
        $this->cellCollectionIsSorted = false;
1276
1277
        // Coordinates
1278 73
        $aCoordinates = Cell::coordinateFromString($pCoordinate);
1279 73
        if (Cell::columnIndexFromString($this->cachedHighestColumn) < Cell::columnIndexFromString($aCoordinates[0])) {
1280 64
            $this->cachedHighestColumn = $aCoordinates[0];
1281
        }
1282 73
        $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...
1283
1284
        // Cell needs appropriate xfIndex from dimensions records
1285
        //    but don't create dimension records if they don't already exist
1286 73
        $rowDimension = $this->getRowDimension($aCoordinates[1], false);
1287 73
        $columnDimension = $this->getColumnDimension($aCoordinates[0], false);
1288
1289 73
        if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) {
1290
            // then there is a row dimension with explicit style, assign it to the cell
1291
            $cell->setXfIndex($rowDimension->getXfIndex());
1292 73
        } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) {
1293
            // then there is a column dimension, assign it to the cell
1294
            $cell->setXfIndex($columnDimension->getXfIndex());
1295
        }
1296
1297 73
        return $cell;
1298
    }
1299
1300
    /**
1301
     * Does the cell at a specific coordinate exist?
1302
     *
1303
     * @param string $pCoordinate Coordinate of the cell
1304
     *
1305
     * @throws Exception
1306
     *
1307
     * @return bool
1308
     */
1309 42
    public function cellExists($pCoordinate = 'A1')
1310
    {
1311
        // Worksheet reference?
1312 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...
1313
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1314
1315
            return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1]));
1316
        }
1317
1318
        // Named range?
1319 42
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1320 42
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1321
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1322
            if ($namedRange !== null) {
1323
                $pCoordinate = $namedRange->getRange();
1324
                if ($this->getHashCode() != $namedRange->getWorksheet()->getHashCode()) {
1325
                    if (!$namedRange->getLocalOnly()) {
1326
                        return $namedRange->getWorksheet()->cellExists($pCoordinate);
1327
                    }
1328
                    throw new Exception('Named range ' . $namedRange->getName() . ' is not accessible from within sheet ' . $this->getTitle());
1329
                }
1330
            } else {
1331
                return false;
1332
            }
1333
        }
1334
1335
        // Uppercase coordinate
1336 42
        $pCoordinate = strtoupper($pCoordinate);
1337
1338 42
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
1339
            throw new Exception('Cell coordinate can not be a range of cells.');
1340 42
        } elseif (strpos($pCoordinate, '$') !== false) {
1341
            throw new Exception('Cell coordinate must not be absolute.');
1342
        }
1343
            // Coordinates
1344 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...
1345
1346
            // Cell exists?
1347 42
            return $this->cellCollection->isDataSet($pCoordinate);
1348
    }
1349
1350
    /**
1351
     * Cell at a specific coordinate by using numeric cell coordinates exists?
1352
     *
1353
     * @param string $pColumn Numeric column coordinate of the cell
1354
     * @param string $pRow Numeric row coordinate of the cell
1355
     *
1356
     * @return bool
1357
     */
1358 6
    public function cellExistsByColumnAndRow($pColumn = 0, $pRow = 1)
1359
    {
1360 6
        return $this->cellExists(Cell::stringFromColumnIndex($pColumn) . $pRow);
1361
    }
1362
1363
    /**
1364
     * Get row dimension at a specific row.
1365
     *
1366
     * @param int $pRow Numeric index of the row
1367
     * @param mixed $create
1368
     *
1369
     * @return Worksheet\RowDimension
1370
     */
1371 73
    public function getRowDimension($pRow = 1, $create = true)
1372
    {
1373
        // Found
1374 73
        $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...
1375
1376
        // Get row dimension
1377 73
        if (!isset($this->rowDimensions[$pRow])) {
1378 73
            if (!$create) {
1379 72
                return null;
1380
            }
1381 58
            $this->rowDimensions[$pRow] = new Worksheet\RowDimension($pRow);
1382
1383 58
            $this->cachedHighestRow = max($this->cachedHighestRow, $pRow);
1384
        }
1385
1386 58
        return $this->rowDimensions[$pRow];
1387
    }
1388
1389
    /**
1390
     * Get column dimension at a specific column.
1391
     *
1392
     * @param string $pColumn String index of the column
1393
     * @param mixed $create
1394
     *
1395
     * @return Worksheet\ColumnDimension
1396
     */
1397 73
    public function getColumnDimension($pColumn = 'A', $create = true)
1398
    {
1399
        // Uppercase coordinate
1400 73
        $pColumn = strtoupper($pColumn);
1401
1402
        // Fetch dimensions
1403 73
        if (!isset($this->columnDimensions[$pColumn])) {
1404 73
            if (!$create) {
1405 72
                return null;
1406
            }
1407 29
            $this->columnDimensions[$pColumn] = new Worksheet\ColumnDimension($pColumn);
1408
1409 29
            if (Cell::columnIndexFromString($this->cachedHighestColumn) < Cell::columnIndexFromString($pColumn)) {
1410 12
                $this->cachedHighestColumn = $pColumn;
1411
            }
1412
        }
1413
1414 29
        return $this->columnDimensions[$pColumn];
1415
    }
1416
1417
    /**
1418
     * Get column dimension at a specific column by using numeric cell coordinates.
1419
     *
1420
     * @param int $pColumn Numeric column coordinate of the cell
1421
     *
1422
     * @return Worksheet\ColumnDimension
1423
     */
1424 3
    public function getColumnDimensionByColumn($pColumn = 0)
1425
    {
1426 3
        return $this->getColumnDimension(Cell::stringFromColumnIndex($pColumn));
1427
    }
1428
1429
    /**
1430
     * Get styles.
1431
     *
1432
     * @return Style[]
1433
     */
1434
    public function getStyles()
1435
    {
1436
        return $this->styles;
1437
    }
1438
1439
    /**
1440
     * Get style for cell.
1441
     *
1442
     * @param string $pCellCoordinate Cell coordinate (or range) to get style for
1443
     *
1444
     * @throws Exception
1445
     *
1446
     * @return Style
1447
     */
1448 39
    public function getStyle($pCellCoordinate = 'A1')
1449
    {
1450
        // set this sheet as active
1451 39
        $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
1452
1453
        // set cell coordinate as active
1454 39
        $this->setSelectedCells(strtoupper($pCellCoordinate));
1455
1456 39
        return $this->parent->getCellXfSupervisor();
1457
    }
1458
1459
    /**
1460
     * Get conditional styles for a cell.
1461
     *
1462
     * @param string $pCoordinate
1463
     *
1464
     * @return Style\Conditional[]
1465
     */
1466 2
    public function getConditionalStyles($pCoordinate = 'A1')
1467
    {
1468 2
        $pCoordinate = strtoupper($pCoordinate);
1469 2
        if (!isset($this->conditionalStylesCollection[$pCoordinate])) {
1470 2
            $this->conditionalStylesCollection[$pCoordinate] = [];
1471
        }
1472
1473 2
        return $this->conditionalStylesCollection[$pCoordinate];
1474
    }
1475
1476
    /**
1477
     * Do conditional styles exist for this cell?
1478
     *
1479
     * @param string $pCoordinate
1480
     *
1481
     * @return bool
1482
     */
1483 13
    public function conditionalStylesExists($pCoordinate = 'A1')
1484
    {
1485 13
        if (isset($this->conditionalStylesCollection[strtoupper($pCoordinate)])) {
1486
            return true;
1487
        }
1488
1489 13
        return false;
1490
    }
1491
1492
    /**
1493
     * Removes conditional styles for a cell.
1494
     *
1495
     * @param string $pCoordinate
1496
     *
1497
     * @return Worksheet
1498
     */
1499 14
    public function removeConditionalStyles($pCoordinate = 'A1')
1500
    {
1501 14
        unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
1502
1503 14
        return $this;
1504
    }
1505
1506
    /**
1507
     * Get collection of conditional styles.
1508
     *
1509
     * @return array
1510
     */
1511 58
    public function getConditionalStylesCollection()
1512
    {
1513 58
        return $this->conditionalStylesCollection;
1514
    }
1515
1516
    /**
1517
     * Set conditional styles.
1518
     *
1519
     * @param string $pCoordinate eg: 'A1'
1520
     * @param $pValue Style\Conditional[]
1521
     *
1522
     * @return Worksheet
1523
     */
1524 2
    public function setConditionalStyles($pCoordinate, $pValue)
1525
    {
1526 2
        $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue;
1527
1528 2
        return $this;
1529
    }
1530
1531
    /**
1532
     * Get style for cell by using numeric cell coordinates.
1533
     *
1534
     * @param int $pColumn Numeric column coordinate of the cell
1535
     * @param int $pRow Numeric row coordinate of the cell
1536
     * @param int pColumn2 Numeric column coordinate of the range cell
1537
     * @param int pRow2 Numeric row coordinate of the range cell
1538
     * @param null|mixed $pColumn2
1539
     * @param null|mixed $pRow2
1540
     *
1541
     * @return Style
1542
     */
1543
    public function getStyleByColumnAndRow($pColumn = 0, $pRow = 1, $pColumn2 = null, $pRow2 = null)
1544
    {
1545
        if (!is_null($pColumn2) && !is_null($pRow2)) {
1546
            $cellRange = Cell::stringFromColumnIndex($pColumn) . $pRow . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1547
1548
            return $this->getStyle($cellRange);
1549
        }
1550
1551
        return $this->getStyle(Cell::stringFromColumnIndex($pColumn) . $pRow);
1552
    }
1553
1554
    /**
1555
     * Duplicate cell style to a range of cells.
1556
     *
1557
     * Please note that this will overwrite existing cell styles for cells in range!
1558
     *
1559
     * @param Style $pCellStyle Cell style to duplicate
1560
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1561
     *
1562
     * @throws Exception
1563
     *
1564
     * @return Worksheet
1565
     */
1566 2
    public function duplicateStyle(Style $pCellStyle = null, $pRange = '')
1567
    {
1568
        // make sure we have a real style and not supervisor
1569 2
        $style = $pCellStyle->getIsSupervisor() ? $pCellStyle->getSharedComponent() : $pCellStyle;
0 ignored issues
show
Bug introduced by
It seems like $pCellStyle is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
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...
1570
1571
        // Add the style to the workbook if necessary
1572 2
        $workbook = $this->parent;
1573 2
        if ($existingStyle = $this->parent->getCellXfByHashCode($pCellStyle->getHashCode())) {
1574
            // there is already such cell Xf in our collection
1575 1
            $xfIndex = $existingStyle->getIndex();
1576
        } else {
1577
            // we don't have such a cell Xf, need to add
1578 2
            $workbook->addCellXf($pCellStyle);
0 ignored issues
show
Bug introduced by
It seems like $pCellStyle defined by parameter $pCellStyle on line 1566 can be null; however, PhpOffice\PhpSpreadsheet\Spreadsheet::addCellXf() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
1579 2
            $xfIndex = $pCellStyle->getIndex();
1580
        }
1581
1582
        // Calculate range outer borders
1583 2
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange . ':' . $pRange);
1584
1585
        // Make sure we can loop upwards on rows and columns
1586 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...
1587
            $tmp = $rangeStart;
1588
            $rangeStart = $rangeEnd;
1589
            $rangeEnd = $tmp;
1590
        }
1591
1592
        // Loop through cells and apply styles
1593 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...
1594 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1595 2
                $this->getCell(Cell::stringFromColumnIndex($col - 1) . $row)->setXfIndex($xfIndex);
1596
            }
1597
        }
1598
1599 2
        return $this;
1600
    }
1601
1602
    /**
1603
     * Duplicate conditional style to a range of cells.
1604
     *
1605
     * Please note that this will overwrite existing cell styles for cells in range!
1606
     *
1607
     * @param Style\Conditional[] $pCellStyle Cell style to duplicate
1608
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1609
     *
1610
     * @throws Exception
1611
     *
1612
     * @return Worksheet
1613
     */
1614 2
    public function duplicateConditionalStyle(array $pCellStyle = null, $pRange = '')
1615
    {
1616 2
        foreach ($pCellStyle as $cellStyle) {
0 ignored issues
show
Bug introduced by
The expression $pCellStyle of type null|array<integer,objec...eet\Style\Conditional>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1617 2
            if (!($cellStyle instanceof Style\Conditional)) {
1618 2
                throw new Exception('Style is not a conditional style');
1619
            }
1620
        }
1621
1622
        // Calculate range outer borders
1623 2
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange . ':' . $pRange);
1624
1625
        // Make sure we can loop upwards on rows and columns
1626 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...
1627
            $tmp = $rangeStart;
1628
            $rangeStart = $rangeEnd;
1629
            $rangeEnd = $tmp;
1630
        }
1631
1632
        // Loop through cells and apply styles
1633 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...
1634 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1635 2
                $this->setConditionalStyles(Cell::stringFromColumnIndex($col - 1) . $row, $pCellStyle);
1636
            }
1637
        }
1638
1639 2
        return $this;
1640
    }
1641
1642
    /**
1643
     * Set break on a cell.
1644
     *
1645
     * @param string $pCell Cell coordinate (e.g. A1)
1646
     * @param int $pBreak Break type (type of Worksheet::BREAK_*)
1647
     *
1648
     * @throws Exception
1649
     *
1650
     * @return Worksheet
1651
     */
1652 1
    public function setBreak($pCell = 'A1', $pBreak = self::BREAK_NONE)
1653
    {
1654
        // Uppercase coordinate
1655 1
        $pCell = strtoupper($pCell);
1656
1657 1
        if ($pCell != '') {
1658 1
            if ($pBreak == self::BREAK_NONE) {
1659
                if (isset($this->breaks[$pCell])) {
1660
                    unset($this->breaks[$pCell]);
1661
                }
1662
            } else {
1663 1
                $this->breaks[$pCell] = $pBreak;
1664
            }
1665
        } else {
1666
            throw new Exception('No cell coordinate specified.');
1667
        }
1668
1669 1
        return $this;
1670
    }
1671
1672
    /**
1673
     * Set break on a cell by using numeric cell coordinates.
1674
     *
1675
     * @param int $pColumn Numeric column coordinate of the cell
1676
     * @param int $pRow Numeric row coordinate of the cell
1677
     * @param int $pBreak Break type (type of \PhpOffice\PhpSpreadsheet\Worksheet::BREAK_*)
1678
     *
1679
     * @return Worksheet
1680
     */
1681
    public function setBreakByColumnAndRow($pColumn = 0, $pRow = 1, $pBreak = self::BREAK_NONE)
1682
    {
1683
        return $this->setBreak(Cell::stringFromColumnIndex($pColumn) . $pRow, $pBreak);
1684
    }
1685
1686
    /**
1687
     * Get breaks.
1688
     *
1689
     * @return array[]
1690
     */
1691 62
    public function getBreaks()
1692
    {
1693 62
        return $this->breaks;
1694
    }
1695
1696
    /**
1697
     * Set merge on a cell range.
1698
     *
1699
     * @param string $pRange Cell range (e.g. A1:E1)
1700
     *
1701
     * @throws Exception
1702
     *
1703
     * @return Worksheet
1704
     */
1705 17
    public function mergeCells($pRange = 'A1:A1')
1706
    {
1707
        // Uppercase coordinate
1708 17
        $pRange = strtoupper($pRange);
1709
1710 17
        if (strpos($pRange, ':') !== false) {
1711 17
            $this->mergeCells[$pRange] = $pRange;
1712
1713
            // make sure cells are created
1714
1715
            // get the cells in the range
1716 17
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
1717
1718
            // create upper left cell if it does not already exist
1719 17
            $upperLeft = $aReferences[0];
1720 17
            if (!$this->cellExists($upperLeft)) {
1721 13
                $this->getCell($upperLeft)->setValueExplicit(null, Cell\DataType::TYPE_NULL);
1722
            }
1723
1724
            // Blank out the rest of the cells in the range (if they exist)
1725 17
            $count = count($aReferences);
1726 17
            for ($i = 1; $i < $count; ++$i) {
1727 17
                if ($this->cellExists($aReferences[$i])) {
1728 4
                    $this->getCell($aReferences[$i])->setValueExplicit(null, Cell\DataType::TYPE_NULL);
1729
                }
1730
            }
1731
        } else {
1732
            throw new Exception('Merge must be set on a range of cells.');
1733
        }
1734
1735 17
        return $this;
1736
    }
1737
1738
    /**
1739
     * Set merge on a cell range by using numeric cell coordinates.
1740
     *
1741
     * @param int $pColumn1 Numeric column coordinate of the first cell
1742
     * @param int $pRow1 Numeric row coordinate of the first cell
1743
     * @param int $pColumn2 Numeric column coordinate of the last cell
1744
     * @param int $pRow2 Numeric row coordinate of the last cell
1745
     *
1746
     * @throws Exception
1747
     *
1748
     * @return Worksheet
1749
     */
1750
    public function mergeCellsByColumnAndRow($pColumn1 = 0, $pRow1 = 1, $pColumn2 = 0, $pRow2 = 1)
1751
    {
1752
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1753
1754
        return $this->mergeCells($cellRange);
1755
    }
1756
1757
    /**
1758
     * Remove merge on a cell range.
1759
     *
1760
     * @param string $pRange Cell range (e.g. A1:E1)
1761
     *
1762
     * @throws Exception
1763
     *
1764
     * @return Worksheet
1765
     */
1766 12
    public function unmergeCells($pRange = 'A1:A1')
1767
    {
1768
        // Uppercase coordinate
1769 12
        $pRange = strtoupper($pRange);
1770
1771 12
        if (strpos($pRange, ':') !== false) {
1772 12
            if (isset($this->mergeCells[$pRange])) {
1773 12
                unset($this->mergeCells[$pRange]);
1774
            } else {
1775 12
                throw new Exception('Cell range ' . $pRange . ' not known as merged.');
1776
            }
1777
        } else {
1778
            throw new Exception('Merge can only be removed from a range of cells.');
1779
        }
1780
1781 12
        return $this;
1782
    }
1783
1784
    /**
1785
     * Remove merge on a cell range by using numeric cell coordinates.
1786
     *
1787
     * @param int $pColumn1 Numeric column coordinate of the first cell
1788
     * @param int $pRow1 Numeric row coordinate of the first cell
1789
     * @param int $pColumn2 Numeric column coordinate of the last cell
1790
     * @param int $pRow2 Numeric row coordinate of the last cell
1791
     *
1792
     * @throws Exception
1793
     *
1794
     * @return Worksheet
1795
     */
1796
    public function unmergeCellsByColumnAndRow($pColumn1 = 0, $pRow1 = 1, $pColumn2 = 0, $pRow2 = 1)
1797
    {
1798
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1799
1800
        return $this->unmergeCells($cellRange);
1801
    }
1802
1803
    /**
1804
     * Get merge cells array.
1805
     *
1806
     * @return array[]
1807
     */
1808 62
    public function getMergeCells()
1809
    {
1810 62
        return $this->mergeCells;
1811
    }
1812
1813
    /**
1814
     * Set merge cells array for the entire sheet. Use instead mergeCells() to merge
1815
     * a single cell range.
1816
     *
1817
     * @param array
1818
     * @param mixed $pValue
1819
     */
1820 15
    public function setMergeCells($pValue = [])
1821
    {
1822 15
        $this->mergeCells = $pValue;
0 ignored issues
show
Documentation Bug introduced by
It seems like $pValue of type * is incompatible with the declared type array of property $mergeCells.

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...
1823
1824 15
        return $this;
1825
    }
1826
1827
    /**
1828
     * Set protection on a cell range.
1829
     *
1830
     * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
1831
     * @param string $pPassword Password to unlock the protection
1832
     * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
1833
     *
1834
     * @throws Exception
1835
     *
1836
     * @return Worksheet
1837
     */
1838 12
    public function protectCells($pRange = 'A1', $pPassword = '', $pAlreadyHashed = false)
1839
    {
1840
        // Uppercase coordinate
1841 12
        $pRange = strtoupper($pRange);
1842
1843 12
        if (!$pAlreadyHashed) {
1844 12
            $pPassword = Shared\PasswordHasher::hashPassword($pPassword);
1845
        }
1846 12
        $this->protectedCells[$pRange] = $pPassword;
1847
1848 12
        return $this;
1849
    }
1850
1851
    /**
1852
     * Set protection on a cell range by using numeric cell coordinates.
1853
     *
1854
     * @param int $pColumn1 Numeric column coordinate of the first cell
1855
     * @param int $pRow1 Numeric row coordinate of the first cell
1856
     * @param int $pColumn2 Numeric column coordinate of the last cell
1857
     * @param int $pRow2 Numeric row coordinate of the last cell
1858
     * @param string $pPassword Password to unlock the protection
1859
     * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
1860
     *
1861
     * @throws Exception
1862
     *
1863
     * @return Worksheet
1864
     */
1865
    public function protectCellsByColumnAndRow($pColumn1 = 0, $pRow1 = 1, $pColumn2 = 0, $pRow2 = 1, $pPassword = '', $pAlreadyHashed = false)
1866
    {
1867
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1868
1869
        return $this->protectCells($cellRange, $pPassword, $pAlreadyHashed);
1870
    }
1871
1872
    /**
1873
     * Remove protection on a cell range.
1874
     *
1875
     * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
1876
     *
1877
     * @throws Exception
1878
     *
1879
     * @return Worksheet
1880
     */
1881 12
    public function unprotectCells($pRange = 'A1')
1882
    {
1883
        // Uppercase coordinate
1884 12
        $pRange = strtoupper($pRange);
1885
1886 12
        if (isset($this->protectedCells[$pRange])) {
1887 12
            unset($this->protectedCells[$pRange]);
1888
        } else {
1889
            throw new Exception('Cell range ' . $pRange . ' not known as protected.');
1890
        }
1891
1892 12
        return $this;
1893
    }
1894
1895
    /**
1896
     * Remove protection on a cell range by using numeric cell coordinates.
1897
     *
1898
     * @param int $pColumn1 Numeric column coordinate of the first cell
1899
     * @param int $pRow1 Numeric row coordinate of the first cell
1900
     * @param int $pColumn2 Numeric column coordinate of the last cell
1901
     * @param int $pRow2 Numeric row coordinate of the last cell
1902
     * @param string $pPassword Password to unlock the protection
1903
     * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
1904
     *
1905
     * @throws Exception
1906
     *
1907
     * @return Worksheet
1908
     */
1909
    public function unprotectCellsByColumnAndRow($pColumn1 = 0, $pRow1 = 1, $pColumn2 = 0, $pRow2 = 1, $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...
1910
    {
1911
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1912
1913
        return $this->unprotectCells($cellRange);
1914
    }
1915
1916
    /**
1917
     * Get protected cells.
1918
     *
1919
     * @return array[]
1920
     */
1921 62
    public function getProtectedCells()
1922
    {
1923 62
        return $this->protectedCells;
1924
    }
1925
1926
    /**
1927
     * Get Autofilter.
1928
     *
1929
     * @return Worksheet\AutoFilter
1930
     */
1931 63
    public function getAutoFilter()
1932
    {
1933 63
        return $this->autoFilter;
1934
    }
1935
1936
    /**
1937
     * Set AutoFilter.
1938
     *
1939
     * @param Worksheet\AutoFilter|string $pValue
1940
     *            A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility
1941
     *
1942
     * @throws Exception
1943
     *
1944
     * @return Worksheet
1945
     */
1946 4
    public function setAutoFilter($pValue)
1947
    {
1948 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...
1949 4
        if (is_string($pValue)) {
1950 4
            $this->autoFilter->setRange($pValue);
1951
        } elseif (is_object($pValue) && ($pValue instanceof Worksheet\AutoFilter)) {
1952
            $this->autoFilter = $pValue;
1953
        }
1954
1955 4
        return $this;
1956
    }
1957
1958
    /**
1959
     * Set Autofilter Range by using numeric cell coordinates.
1960
     *
1961
     * @param int $pColumn1 Numeric column coordinate of the first cell
1962
     * @param int $pRow1 Numeric row coordinate of the first cell
1963
     * @param int $pColumn2 Numeric column coordinate of the second cell
1964
     * @param int $pRow2 Numeric row coordinate of the second cell
1965
     *
1966
     * @throws Exception
1967
     *
1968
     * @return Worksheet
1969
     */
1970
    public function setAutoFilterByColumnAndRow($pColumn1 = 0, $pRow1 = 1, $pColumn2 = 0, $pRow2 = 1)
1971
    {
1972
        return $this->setAutoFilter(
1973
            Cell::stringFromColumnIndex($pColumn1) . $pRow1
1974
            . ':' .
1975
            Cell::stringFromColumnIndex($pColumn2) . $pRow2
1976
        );
1977
    }
1978
1979
    /**
1980
     * Remove autofilter.
1981
     *
1982
     * @return Worksheet
1983
     */
1984
    public function removeAutoFilter()
1985
    {
1986
        $this->autoFilter->setRange(null);
1987
1988
        return $this;
1989
    }
1990
1991
    /**
1992
     * Get Freeze Pane.
1993
     *
1994
     * @return string
1995
     */
1996 62
    public function getFreezePane()
1997
    {
1998 62
        return $this->freezePane;
1999
    }
2000
2001
    /**
2002
     * Freeze Pane.
2003
     *
2004
     * @param string $pCell Cell (i.e. A2)
2005
     *                                    Examples:
2006
     *                                        A2 will freeze the rows above cell A2 (i.e row 1)
2007
     *                                        B1 will freeze the columns to the left of cell B1 (i.e column A)
2008
     *                                        B2 will freeze the rows above and to the left of cell A2
2009
     *                                            (i.e row 1 and column A)
2010
     *
2011
     * @throws Exception
2012
     *
2013
     * @return Worksheet
2014
     */
2015 4
    public function freezePane($pCell = '')
2016
    {
2017
        // Uppercase coordinate
2018 4
        $pCell = strtoupper($pCell);
2019 4
        if (strpos($pCell, ':') === false && strpos($pCell, ',') === false) {
2020 4
            $this->freezePane = $pCell;
2021
        } else {
2022
            throw new Exception('Freeze pane can not be set on a range of cells.');
2023
        }
2024
2025 4
        return $this;
2026
    }
2027
2028
    /**
2029
     * Freeze Pane by using numeric cell coordinates.
2030
     *
2031
     * @param int $pColumn Numeric column coordinate of the cell
2032
     * @param int $pRow Numeric row coordinate of the cell
2033
     *
2034
     * @throws Exception
2035
     *
2036
     * @return Worksheet
2037
     */
2038
    public function freezePaneByColumnAndRow($pColumn = 0, $pRow = 1)
2039
    {
2040
        return $this->freezePane(Cell::stringFromColumnIndex($pColumn) . $pRow);
2041
    }
2042
2043
    /**
2044
     * Unfreeze Pane.
2045
     *
2046
     * @return Worksheet
2047
     */
2048
    public function unfreezePane()
2049
    {
2050
        return $this->freezePane('');
2051
    }
2052
2053
    /**
2054
     * Insert a new row, updating all possible related data.
2055
     *
2056
     * @param int $pBefore Insert before this one
2057
     * @param int $pNumRows Number of rows to insert
2058
     *
2059
     * @throws Exception
2060
     *
2061
     * @return Worksheet
2062
     */
2063 13 View Code Duplication
    public function insertNewRowBefore($pBefore = 1, $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...
2064
    {
2065 13
        if ($pBefore >= 1) {
2066 13
            $objReferenceHelper = ReferenceHelper::getInstance();
2067 13
            $objReferenceHelper->insertNewBefore('A' . $pBefore, 0, $pNumRows, $this);
2068
        } else {
2069
            throw new Exception('Rows can only be inserted before at least row 1.');
2070
        }
2071
2072 13
        return $this;
2073
    }
2074
2075
    /**
2076
     * Insert a new column, updating all possible related data.
2077
     *
2078
     * @param int $pBefore Insert before this one
2079
     * @param int $pNumCols Number of columns to insert
2080
     *
2081
     * @throws Exception
2082
     *
2083
     * @return Worksheet
2084
     */
2085 12 View Code Duplication
    public function insertNewColumnBefore($pBefore = 'A', $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...
2086
    {
2087 12
        if (!is_numeric($pBefore)) {
2088 12
            $objReferenceHelper = ReferenceHelper::getInstance();
2089 12
            $objReferenceHelper->insertNewBefore($pBefore . '1', $pNumCols, 0, $this);
2090
        } else {
2091
            throw new Exception('Column references should not be numeric.');
2092
        }
2093
2094 12
        return $this;
2095
    }
2096
2097
    /**
2098
     * Insert a new column, updating all possible related data.
2099
     *
2100
     * @param int $pBefore Insert before this one (numeric column coordinate of the cell)
2101
     * @param int $pNumCols Number of columns to insert
2102
     *
2103
     * @throws Exception
2104
     *
2105
     * @return Worksheet
2106
     */
2107
    public function insertNewColumnBeforeByIndex($pBefore = 0, $pNumCols = 1)
2108
    {
2109
        if ($pBefore >= 0) {
2110
            return $this->insertNewColumnBefore(Cell::stringFromColumnIndex($pBefore), $pNumCols);
2111
        }
2112
        throw new Exception('Columns can only be inserted before at least column A (0).');
2113
    }
2114
2115
    /**
2116
     * Delete a row, updating all possible related data.
2117
     *
2118
     * @param int $pRow Remove starting with this one
2119
     * @param int $pNumRows Number of rows to remove
2120
     *
2121
     * @throws Exception
2122
     *
2123
     * @return Worksheet
2124
     */
2125 15
    public function removeRow($pRow = 1, $pNumRows = 1)
2126
    {
2127 15
        if ($pRow >= 1) {
2128 15
            $highestRow = $this->getHighestDataRow();
2129 15
            $objReferenceHelper = ReferenceHelper::getInstance();
2130 15
            $objReferenceHelper->insertNewBefore('A' . ($pRow + $pNumRows), 0, -$pNumRows, $this);
2131 15
            for ($r = 0; $r < $pNumRows; ++$r) {
2132 15
                $this->getCellCacheController()->removeRow($highestRow);
2133 15
                --$highestRow;
2134
            }
2135
        } else {
2136
            throw new Exception('Rows to be deleted should at least start from row 1.');
2137
        }
2138
2139 15
        return $this;
2140
    }
2141
2142
    /**
2143
     * Remove a column, updating all possible related data.
2144
     *
2145
     * @param string $pColumn Remove starting with this one
2146
     * @param int $pNumCols Number of columns to remove
2147
     *
2148
     * @throws Exception
2149
     *
2150
     * @return Worksheet
2151
     */
2152 12
    public function removeColumn($pColumn = 'A', $pNumCols = 1)
2153
    {
2154 12
        if (!is_numeric($pColumn)) {
2155 12
            $highestColumn = $this->getHighestDataColumn();
2156 12
            $pColumn = Cell::stringFromColumnIndex(Cell::columnIndexFromString($pColumn) - 1 + $pNumCols);
2157 12
            $objReferenceHelper = ReferenceHelper::getInstance();
2158 12
            $objReferenceHelper->insertNewBefore($pColumn . '1', -$pNumCols, 0, $this);
2159 12
            for ($c = 0; $c < $pNumCols; ++$c) {
2160 12
                $this->getCellCacheController()->removeColumn($highestColumn);
2161 12
                $highestColumn = Cell::stringFromColumnIndex(Cell::columnIndexFromString($highestColumn) - 2);
2162
            }
2163
        } else {
2164
            throw new Exception('Column references should not be numeric.');
2165
        }
2166
2167 12
        return $this;
2168
    }
2169
2170
    /**
2171
     * Remove a column, updating all possible related data.
2172
     *
2173
     * @param int $pColumn Remove starting with this one (numeric column coordinate of the cell)
2174
     * @param int $pNumCols Number of columns to remove
2175
     *
2176
     * @throws Exception
2177
     *
2178
     * @return Worksheet
2179
     */
2180
    public function removeColumnByIndex($pColumn = 0, $pNumCols = 1)
2181
    {
2182
        if ($pColumn >= 0) {
2183
            return $this->removeColumn(Cell::stringFromColumnIndex($pColumn), $pNumCols);
2184
        }
2185
        throw new Exception('Columns to be deleted should at least start from column 0');
2186
    }
2187
2188
    /**
2189
     * Show gridlines?
2190
     *
2191
     * @return bool
2192
     */
2193 62
    public function getShowGridlines()
2194
    {
2195 62
        return $this->showGridlines;
2196
    }
2197
2198
    /**
2199
     * Set show gridlines.
2200
     *
2201
     * @param bool $pValue Show gridlines (true/false)
2202
     *
2203
     * @return Worksheet
2204
     */
2205 12
    public function setShowGridlines($pValue = false)
2206
    {
2207 12
        $this->showGridlines = $pValue;
2208
2209 12
        return $this;
2210
    }
2211
2212
    /**
2213
     * Print gridlines?
2214
     *
2215
     * @return bool
2216
     */
2217 58
    public function getPrintGridlines()
2218
    {
2219 58
        return $this->printGridlines;
2220
    }
2221
2222
    /**
2223
     * Set print gridlines.
2224
     *
2225
     * @param bool $pValue Print gridlines (true/false)
2226
     *
2227
     * @return Worksheet
2228
     */
2229 4
    public function setPrintGridlines($pValue = false)
2230
    {
2231 4
        $this->printGridlines = $pValue;
2232
2233 4
        return $this;
2234
    }
2235
2236
    /**
2237
     * Show row and column headers?
2238
     *
2239
     * @return bool
2240
     */
2241 58
    public function getShowRowColHeaders()
2242
    {
2243 58
        return $this->showRowColHeaders;
2244
    }
2245
2246
    /**
2247
     * Set show row and column headers.
2248
     *
2249
     * @param bool $pValue Show row and column headers (true/false)
2250
     *
2251
     * @return Worksheet
2252
     */
2253 9
    public function setShowRowColHeaders($pValue = false)
2254
    {
2255 9
        $this->showRowColHeaders = $pValue;
2256
2257 9
        return $this;
2258
    }
2259
2260
    /**
2261
     * Show summary below? (Row/Column outlining).
2262
     *
2263
     * @return bool
2264
     */
2265 58
    public function getShowSummaryBelow()
2266
    {
2267 58
        return $this->showSummaryBelow;
2268
    }
2269
2270
    /**
2271
     * Set show summary below.
2272
     *
2273
     * @param bool $pValue Show summary below (true/false)
2274
     *
2275
     * @return Worksheet
2276
     */
2277 9
    public function setShowSummaryBelow($pValue = true)
2278
    {
2279 9
        $this->showSummaryBelow = $pValue;
2280
2281 9
        return $this;
2282
    }
2283
2284
    /**
2285
     * Show summary right? (Row/Column outlining).
2286
     *
2287
     * @return bool
2288
     */
2289 58
    public function getShowSummaryRight()
2290
    {
2291 58
        return $this->showSummaryRight;
2292
    }
2293
2294
    /**
2295
     * Set show summary right.
2296
     *
2297
     * @param bool $pValue Show summary right (true/false)
2298
     *
2299
     * @return Worksheet
2300
     */
2301 9
    public function setShowSummaryRight($pValue = true)
2302
    {
2303 9
        $this->showSummaryRight = $pValue;
2304
2305 9
        return $this;
2306
    }
2307
2308
    /**
2309
     * Get comments.
2310
     *
2311
     * @return Comment[]
2312
     */
2313 63
    public function getComments()
2314
    {
2315 63
        return $this->comments;
2316
    }
2317
2318
    /**
2319
     * Set comments array for the entire sheet.
2320
     *
2321
     * @param array of Comment
2322
     * @param mixed $pValue
2323
     *
2324
     * @return Worksheet
2325
     */
2326 15
    public function setComments($pValue = [])
2327
    {
2328 15
        $this->comments = $pValue;
0 ignored issues
show
Documentation Bug introduced by
It seems like $pValue of type * is incompatible with the declared type array<integer,object<Php...hpSpreadsheet\Comment>> of property $comments.

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...
2329
2330 15
        return $this;
2331
    }
2332
2333
    /**
2334
     * Get comment for cell.
2335
     *
2336
     * @param string $pCellCoordinate Cell coordinate to get comment for
2337
     *
2338
     * @throws Exception
2339
     *
2340
     * @return Comment
2341
     */
2342 20
    public function getComment($pCellCoordinate = 'A1')
2343
    {
2344
        // Uppercase coordinate
2345 20
        $pCellCoordinate = strtoupper($pCellCoordinate);
2346
2347 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...
2348
            throw new Exception('Cell coordinate string can not be a range of cells.');
2349 20
        } elseif (strpos($pCellCoordinate, '$') !== false) {
2350
            throw new Exception('Cell coordinate string must not be absolute.');
2351 20
        } elseif ($pCellCoordinate == '') {
2352
            throw new Exception('Cell coordinate can not be zero-length string.');
2353
        }
2354
2355
        // Check if we already have a comment for this cell.
2356 20
        if (isset($this->comments[$pCellCoordinate])) {
2357 13
            return $this->comments[$pCellCoordinate];
2358
        }
2359
2360
        // If not, create a new comment.
2361 20
        $newComment = new Comment();
2362 20
        $this->comments[$pCellCoordinate] = $newComment;
2363
2364 20
        return $newComment;
2365
    }
2366
2367
    /**
2368
     * Get comment for cell by using numeric cell coordinates.
2369
     *
2370
     * @param int $pColumn Numeric column coordinate of the cell
2371
     * @param int $pRow Numeric row coordinate of the cell
2372
     *
2373
     * @return Comment
2374
     */
2375 2
    public function getCommentByColumnAndRow($pColumn = 0, $pRow = 1)
2376
    {
2377 2
        return $this->getComment(Cell::stringFromColumnIndex($pColumn) . $pRow);
2378
    }
2379
2380
    /**
2381
     * Get active cell.
2382
     *
2383
     * @return string Example: 'A1'
2384
     */
2385 58
    public function getActiveCell()
2386
    {
2387 58
        return $this->activeCell;
2388
    }
2389
2390
    /**
2391
     * Get selected cells.
2392
     *
2393
     * @return string
2394
     */
2395 55
    public function getSelectedCells()
2396
    {
2397 55
        return $this->selectedCells;
2398
    }
2399
2400
    /**
2401
     * Selected cell.
2402
     *
2403
     * @param string $pCoordinate Cell (i.e. A1)
2404
     *
2405
     * @return Worksheet
2406
     */
2407
    public function setSelectedCell($pCoordinate = 'A1')
2408
    {
2409
        return $this->setSelectedCells($pCoordinate);
2410
    }
2411
2412
    /**
2413
     * Select a range of cells.
2414
     *
2415
     * @param string $pCoordinate Cell range, examples: 'A1', 'B2:G5', 'A:C', '3:6'
2416
     *
2417
     * @throws Exception
2418
     *
2419
     * @return Worksheet
2420
     */
2421 48
    public function setSelectedCells($pCoordinate = 'A1')
2422
    {
2423
        // Uppercase coordinate
2424 48
        $pCoordinate = strtoupper($pCoordinate);
2425
2426
        // Convert 'A' to 'A:A'
2427 48
        $pCoordinate = preg_replace('/^([A-Z]+)$/', '${1}:${1}', $pCoordinate);
2428
2429
        // Convert '1' to '1:1'
2430 48
        $pCoordinate = preg_replace('/^([0-9]+)$/', '${1}:${1}', $pCoordinate);
2431
2432
        // Convert 'A:C' to 'A1:C1048576'
2433 48
        $pCoordinate = preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $pCoordinate);
2434
2435
        // Convert '1:3' to 'A1:XFD3'
2436 48
        $pCoordinate = preg_replace('/^([0-9]+):([0-9]+)$/', 'A${1}:XFD${2}', $pCoordinate);
2437
2438 48
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
2439 26
            list($first) = Cell::splitRange($pCoordinate);
2440 26
            $this->activeCell = $first[0];
2441
        } else {
2442 41
            $this->activeCell = $pCoordinate;
2443
        }
2444 48
        $this->selectedCells = $pCoordinate;
2445
2446 48
        return $this;
2447
    }
2448
2449
    /**
2450
     * Selected cell by using numeric cell coordinates.
2451
     *
2452
     * @param int $pColumn Numeric column coordinate of the cell
2453
     * @param int $pRow Numeric row coordinate of the cell
2454
     *
2455
     * @throws Exception
2456
     *
2457
     * @return Worksheet
2458
     */
2459
    public function setSelectedCellByColumnAndRow($pColumn = 0, $pRow = 1)
2460
    {
2461
        return $this->setSelectedCells(Cell::stringFromColumnIndex($pColumn) . $pRow);
2462
    }
2463
2464
    /**
2465
     * Get right-to-left.
2466
     *
2467
     * @return bool
2468
     */
2469 58
    public function getRightToLeft()
2470
    {
2471 58
        return $this->rightToLeft;
2472
    }
2473
2474
    /**
2475
     * Set right-to-left.
2476
     *
2477
     * @param bool $value Right-to-left true/false
2478
     *
2479
     * @return Worksheet
2480
     */
2481 4
    public function setRightToLeft($value = false)
2482
    {
2483 4
        $this->rightToLeft = $value;
2484
2485 4
        return $this;
2486
    }
2487
2488
    /**
2489
     * Fill worksheet from values in array.
2490
     *
2491
     * @param array $source Source array
2492
     * @param mixed $nullValue Value in source array that stands for blank cell
2493
     * @param string $startCell Insert array starting from this cell address as the top left coordinate
2494
     * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array
2495
     *
2496
     * @throws Exception
2497
     *
2498
     * @return Worksheet
2499
     */
2500 17
    public function fromArray($source = null, $nullValue = null, $startCell = 'A1', $strictNullComparison = false)
2501
    {
2502 17
        if (is_array($source)) {
2503
            //    Convert a 1-D array to 2-D (for ease of looping)
2504 17
            if (!is_array(end($source))) {
2505 3
                $source = [$source];
2506
            }
2507
2508
            // start coordinate
2509 17
            list($startColumn, $startRow) = Cell::coordinateFromString($startCell);
2510
2511
            // Loop through $source
2512 17
            foreach ($source as $rowData) {
2513 17
                $currentColumn = $startColumn;
2514 17
                foreach ($rowData as $cellValue) {
2515 17
                    if ($strictNullComparison) {
2516 1
                        if ($cellValue !== $nullValue) {
2517
                            // Set cell value
2518 1
                            $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2519
                        }
2520
                    } else {
2521 16
                        if ($cellValue != $nullValue) {
2522
                            // Set cell value
2523 16
                            $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2524
                        }
2525
                    }
2526 17
                    ++$currentColumn;
2527
                }
2528 17
                ++$startRow;
2529
            }
2530
        } else {
2531
            throw new Exception('Parameter $source should be an array.');
2532
        }
2533
2534 17
        return $this;
2535
    }
2536
2537
    /**
2538
     * Create array from a range of cells.
2539
     *
2540
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
2541
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2542
     * @param bool $calculateFormulas Should formulas be calculated?
2543
     * @param bool $formatData Should formatting be applied to cell values?
2544
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2545
     *                               True - Return rows and columns indexed by their actual row and column IDs
2546
     *
2547
     * @return array
2548
     */
2549 2
    public function rangeToArray($pRange = 'A1', $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2550
    {
2551
        // Returnvalue
2552 2
        $returnValue = [];
2553
        //    Identify the range that we need to extract from the worksheet
2554 2
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange);
2555 2
        $minCol = Cell::stringFromColumnIndex($rangeStart[0] - 1);
2556 2
        $minRow = $rangeStart[1];
2557 2
        $maxCol = Cell::stringFromColumnIndex($rangeEnd[0] - 1);
2558 2
        $maxRow = $rangeEnd[1];
2559
2560 2
        ++$maxCol;
2561
        // Loop through rows
2562 2
        $r = -1;
2563 2
        for ($row = $minRow; $row <= $maxRow; ++$row) {
2564 2
            $rRef = ($returnCellRef) ? $row : ++$r;
2565 2
            $c = -1;
2566
            // Loop through columns in the current row
2567 2
            for ($col = $minCol; $col != $maxCol; ++$col) {
2568 2
                $cRef = ($returnCellRef) ? $col : ++$c;
2569
                //    Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
2570
                //        so we test and retrieve directly against cellCollection
2571 2
                if ($this->cellCollection->isDataSet($col . $row)) {
2572
                    // Cell exists
2573 2
                    $cell = $this->cellCollection->getCacheData($col . $row);
2574 2
                    if ($cell->getValue() !== null) {
2575 2
                        if ($cell->getValue() instanceof RichText) {
2576 2
                            $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
2577
                        } else {
2578 2
                            if ($calculateFormulas) {
2579 2
                                $returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
2580
                            } else {
2581
                                $returnValue[$rRef][$cRef] = $cell->getValue();
2582
                            }
2583
                        }
2584
2585 2
                        if ($formatData) {
2586 2
                            $style = $this->parent->getCellXfByIndex($cell->getXfIndex());
2587 2
                            $returnValue[$rRef][$cRef] = Style\NumberFormat::toFormattedString(
2588 2
                                $returnValue[$rRef][$cRef],
2589 2
                                ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : Style\NumberFormat::FORMAT_GENERAL
2590
                            );
2591
                        }
2592
                    } else {
2593
                        // Cell holds a NULL
2594 2
                        $returnValue[$rRef][$cRef] = $nullValue;
2595
                    }
2596
                } else {
2597
                    // Cell doesn't exist
2598 1
                    $returnValue[$rRef][$cRef] = $nullValue;
2599
                }
2600
            }
2601
        }
2602
2603
        // Return
2604 2
        return $returnValue;
2605
    }
2606
2607
    /**
2608
     * Create array from a range of cells.
2609
     *
2610
     * @param string $pNamedRange Name of the Named Range
2611
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2612
     * @param bool $calculateFormulas Should formulas be calculated?
2613
     * @param bool $formatData Should formatting be applied to cell values?
2614
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2615
     *                                True - Return rows and columns indexed by their actual row and column IDs
2616
     *
2617
     * @throws Exception
2618
     *
2619
     * @return array
2620
     */
2621
    public function namedRangeToArray($pNamedRange = '', $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2622
    {
2623
        $namedRange = NamedRange::resolveRange($pNamedRange, $this);
2624
        if ($namedRange !== null) {
2625
            $pWorkSheet = $namedRange->getWorksheet();
2626
            $pCellRange = $namedRange->getRange();
2627
2628
            return $pWorkSheet->rangeToArray($pCellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2629
        }
2630
2631
        throw new Exception('Named Range ' . $pNamedRange . ' does not exist.');
2632
    }
2633
2634
    /**
2635
     * Create array from worksheet.
2636
     *
2637
     * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
2638
     * @param bool $calculateFormulas Should formulas be calculated?
2639
     * @param bool $formatData Should formatting be applied to cell values?
2640
     * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
2641
     *                               True - Return rows and columns indexed by their actual row and column IDs
2642
     *
2643
     * @return array
2644
     */
2645
    public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
2646
    {
2647
        // Garbage collect...
2648
        $this->garbageCollect();
2649
2650
        //    Identify the range that we need to extract from the worksheet
2651
        $maxCol = $this->getHighestColumn();
2652
        $maxRow = $this->getHighestRow();
2653
        // Return
2654
        return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2655
    }
2656
2657
    /**
2658
     * Get row iterator.
2659
     *
2660
     * @param int $startRow The row number at which to start iterating
2661
     * @param int $endRow The row number at which to stop iterating
2662
     *
2663
     * @return Worksheet\RowIterator
2664
     */
2665 4
    public function getRowIterator($startRow = 1, $endRow = null)
2666
    {
2667 4
        return new Worksheet\RowIterator($this, $startRow, $endRow);
2668
    }
2669
2670
    /**
2671
     * Get column iterator.
2672
     *
2673
     * @param string $startColumn The column address at which to start iterating
2674
     * @param string $endColumn The column address at which to stop iterating
2675
     *
2676
     * @return Worksheet\ColumnIterator
2677
     */
2678
    public function getColumnIterator($startColumn = 'A', $endColumn = null)
2679
    {
2680
        return new Worksheet\ColumnIterator($this, $startColumn, $endColumn);
2681
    }
2682
2683
    /**
2684
     * Run PhpSpreadsheet garabage collector.
2685
     *
2686
     * @return Worksheet
2687
     */
2688 62
    public function garbageCollect()
2689
    {
2690
        // Flush cache
2691 62
        $this->cellCollection->getCacheData('A1');
2692
2693
        // Lookup highest column and highest row if cells are cleaned
2694 62
        $colRow = $this->cellCollection->getHighestRowAndColumn();
2695 62
        $highestRow = $colRow['row'];
2696 62
        $highestColumn = Cell::columnIndexFromString($colRow['column']);
2697
2698
        // Loop through column dimensions
2699 62
        foreach ($this->columnDimensions as $dimension) {
2700 27
            $highestColumn = max($highestColumn, Cell::columnIndexFromString($dimension->getColumnIndex()));
2701
        }
2702
2703
        // Loop through row dimensions
2704 62
        foreach ($this->rowDimensions as $dimension) {
2705 39
            $highestRow = max($highestRow, $dimension->getRowIndex());
2706
        }
2707
2708
        // Cache values
2709 62
        if ($highestColumn < 0) {
2710
            $this->cachedHighestColumn = 'A';
2711
        } else {
2712 62
            $this->cachedHighestColumn = Cell::stringFromColumnIndex(--$highestColumn);
2713
        }
2714 62
        $this->cachedHighestRow = $highestRow;
2715
2716
        // Return
2717 62
        return $this;
2718
    }
2719
2720
    /**
2721
     * Get hash code.
2722
     *
2723
     * @return string Hash code
2724
     */
2725 70
    public function getHashCode()
2726
    {
2727 70
        if ($this->dirty) {
2728 70
            $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__);
2729 70
            $this->dirty = false;
2730
        }
2731
2732 70
        return $this->hash;
2733
    }
2734
2735
    /**
2736
     * Extract worksheet title from range.
2737
     *
2738
     * Example: extractSheetTitle("testSheet!A1") ==> 'A1'
2739
     * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> array('testSheet 1', 'A1');
2740
     *
2741
     * @param string $pRange Range to extract title from
2742
     * @param bool $returnRange Return range? (see example)
2743
     *
2744
     * @return mixed
2745
     */
2746 1
    public static function extractSheetTitle($pRange, $returnRange = false)
2747
    {
2748
        // Sheet title included?
2749 1
        if (($sep = strpos($pRange, '!')) === false) {
2750
            return '';
2751
        }
2752
2753 1
        if ($returnRange) {
2754 1
            return [trim(substr($pRange, 0, $sep), "'"), substr($pRange, $sep + 1)];
2755
        }
2756
2757
        return substr($pRange, $sep + 1);
2758
    }
2759
2760
    /**
2761
     * Get hyperlink.
2762
     *
2763
     * @param string $pCellCoordinate Cell coordinate to get hyperlink for
2764
     */
2765 19 View Code Duplication
    public function getHyperlink($pCellCoordinate = 'A1')
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...
2766
    {
2767
        // return hyperlink if we already have one
2768 19
        if (isset($this->hyperlinkCollection[$pCellCoordinate])) {
2769 14
            return $this->hyperlinkCollection[$pCellCoordinate];
2770
        }
2771
2772
        // else create hyperlink
2773 19
        $this->hyperlinkCollection[$pCellCoordinate] = new Cell\Hyperlink();
2774
2775 19
        return $this->hyperlinkCollection[$pCellCoordinate];
2776
    }
2777
2778
    /**
2779
     * Set hyperlnk.
2780
     *
2781
     * @param string $pCellCoordinate Cell coordinate to insert hyperlink
2782
     * @param Cell\Hyperlink $pHyperlink
2783
     *
2784
     * @return Worksheet
2785
     */
2786 13
    public function setHyperlink($pCellCoordinate = 'A1', Cell\Hyperlink $pHyperlink = null)
2787
    {
2788 13
        if ($pHyperlink === null) {
2789 13
            unset($this->hyperlinkCollection[$pCellCoordinate]);
2790
        } else {
2791 13
            $this->hyperlinkCollection[$pCellCoordinate] = $pHyperlink;
2792
        }
2793
2794 13
        return $this;
2795
    }
2796
2797
    /**
2798
     * Hyperlink at a specific coordinate exists?
2799
     *
2800
     * @param string $pCoordinate
2801
     *
2802
     * @return bool
2803
     */
2804 6
    public function hyperlinkExists($pCoordinate = 'A1')
2805
    {
2806 6
        return isset($this->hyperlinkCollection[$pCoordinate]);
2807
    }
2808
2809
    /**
2810
     * Get collection of hyperlinks.
2811
     *
2812
     * @return Cell\Hyperlink[]
2813
     */
2814 62
    public function getHyperlinkCollection()
2815
    {
2816 62
        return $this->hyperlinkCollection;
2817
    }
2818
2819
    /**
2820
     * Get data validation.
2821
     *
2822
     * @param string $pCellCoordinate Cell coordinate to get data validation for
2823
     */
2824 2 View Code Duplication
    public function getDataValidation($pCellCoordinate = 'A1')
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...
2825
    {
2826
        // return data validation if we already have one
2827 2
        if (isset($this->dataValidationCollection[$pCellCoordinate])) {
2828
            return $this->dataValidationCollection[$pCellCoordinate];
2829
        }
2830
2831
        // else create data validation
2832 2
        $this->dataValidationCollection[$pCellCoordinate] = new Cell\DataValidation();
2833
2834 2
        return $this->dataValidationCollection[$pCellCoordinate];
2835
    }
2836
2837
    /**
2838
     * Set data validation.
2839
     *
2840
     * @param string $pCellCoordinate Cell coordinate to insert data validation
2841
     * @param Cell\DataValidation $pDataValidation
2842
     *
2843
     * @return Worksheet
2844
     */
2845
    public function setDataValidation($pCellCoordinate = 'A1', Cell\DataValidation $pDataValidation = null)
2846
    {
2847
        if ($pDataValidation === null) {
2848
            unset($this->dataValidationCollection[$pCellCoordinate]);
2849
        } else {
2850
            $this->dataValidationCollection[$pCellCoordinate] = $pDataValidation;
2851
        }
2852
2853
        return $this;
2854
    }
2855
2856
    /**
2857
     * Data validation at a specific coordinate exists?
2858
     *
2859
     * @param string $pCoordinate
2860
     *
2861
     * @return bool
2862
     */
2863
    public function dataValidationExists($pCoordinate = 'A1')
2864
    {
2865
        return isset($this->dataValidationCollection[$pCoordinate]);
2866
    }
2867
2868
    /**
2869
     * Get collection of data validations.
2870
     *
2871
     * @return Cell\DataValidation[]
2872
     */
2873 62
    public function getDataValidationCollection()
2874
    {
2875 62
        return $this->dataValidationCollection;
2876
    }
2877
2878
    /**
2879
     * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet.
2880
     *
2881
     * @param string $range
2882
     *
2883
     * @return string Adjusted range value
2884
     */
2885
    public function shrinkRangeToFit($range)
2886
    {
2887
        $maxCol = $this->getHighestColumn();
2888
        $maxRow = $this->getHighestRow();
2889
        $maxCol = Cell::columnIndexFromString($maxCol);
2890
2891
        $rangeBlocks = explode(' ', $range);
2892
        foreach ($rangeBlocks as &$rangeSet) {
2893
            $rangeBoundaries = Cell::getRangeBoundaries($rangeSet);
2894
2895 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...
2896
                $rangeBoundaries[0][0] = Cell::stringFromColumnIndex($maxCol);
2897
            }
2898
            if ($rangeBoundaries[0][1] > $maxRow) {
2899
                $rangeBoundaries[0][1] = $maxRow;
2900
            }
2901 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...
2902
                $rangeBoundaries[1][0] = Cell::stringFromColumnIndex($maxCol);
2903
            }
2904
            if ($rangeBoundaries[1][1] > $maxRow) {
2905
                $rangeBoundaries[1][1] = $maxRow;
2906
            }
2907
            $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1];
2908
        }
2909
        unset($rangeSet);
2910
        $stRange = implode(' ', $rangeBlocks);
2911
2912
        return $stRange;
2913
    }
2914
2915
    /**
2916
     * Get tab color.
2917
     *
2918
     * @return Style\Color
2919
     */
2920 12
    public function getTabColor()
2921
    {
2922 12
        if ($this->tabColor === null) {
2923 12
            $this->tabColor = new Style\Color();
2924
        }
2925
2926 12
        return $this->tabColor;
2927
    }
2928
2929
    /**
2930
     * Reset tab color.
2931
     *
2932
     * @return Worksheet
2933
     */
2934
    public function resetTabColor()
2935
    {
2936
        $this->tabColor = null;
2937
        unset($this->tabColor);
2938
2939
        return $this;
2940
    }
2941
2942
    /**
2943
     * Tab color set?
2944
     *
2945
     * @return bool
2946
     */
2947 58
    public function isTabColorSet()
2948
    {
2949 58
        return $this->tabColor !== null;
2950
    }
2951
2952
    /**
2953
     * Copy worksheet (!= clone!).
2954
     *
2955
     * @return Worksheet
2956
     */
2957
    public function copy()
2958
    {
2959
        $copied = clone $this;
2960
2961
        return $copied;
2962
    }
2963
2964
    /**
2965
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
2966
     */
2967 1
    public function __clone()
2968
    {
2969 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...
2970 1
            if ($key == 'parent') {
2971 1
                continue;
2972
            }
2973
2974 1
            if (is_object($val) || (is_array($val))) {
2975 1
                if ($key == 'cellCollection') {
2976 1
                    $newCollection = clone $this->cellCollection;
2977 1
                    $newCollection->copyCellCollection($this);
2978 1
                    $this->cellCollection = $newCollection;
2979 1
                } elseif ($key == 'drawingCollection') {
2980 1
                    $newCollection = new ArrayObject();
2981 1
                    foreach ($this->drawingCollection as $id => $item) {
2982
                        if (is_object($item)) {
2983
                            $newCollection[$id] = clone $this->drawingCollection[$id];
2984
                        }
2985
                    }
2986 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...
2987 1
                } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof Worksheet\AutoFilter)) {
2988 1
                    $newAutoFilter = clone $this->autoFilter;
2989 1
                    $this->autoFilter = $newAutoFilter;
2990 1
                    $this->autoFilter->setParent($this);
2991
                } else {
2992 1
                    $this->{$key} = unserialize(serialize($val));
2993
                }
2994
            }
2995
        }
2996 1
    }
2997
2998
    /**
2999
     * Define the code name of the sheet.
3000
     *
3001
     * @param null|string Same rule as Title minus space not allowed (but, like Excel, change silently space to underscore)
3002
     * @param null|mixed $pValue
3003
     *
3004
     * @throws Exception
3005
     *
3006
     * @return objWorksheet
3007
     */
3008 81
    public function setCodeName($pValue = null)
3009
    {
3010
        // Is this a 'rename' or not?
3011 81
        if ($this->getCodeName() == $pValue) {
3012
            return $this;
3013
        }
3014 81
        $pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same
3015
        // Syntax check
3016
        // throw an exception if not valid
3017 81
        self::checkSheetCodeName($pValue);
3018
3019
        // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
3020
3021 81
        if ($this->getParent()) {
3022
            // Is there already such sheet name?
3023 73
            if ($this->getParent()->sheetCodeNameExists($pValue)) {
3024
                // Use name, but append with lowest possible integer
3025
3026 26
                if (Shared\StringHelper::countCharacters($pValue) > 29) {
3027
                    $pValue = Shared\StringHelper::substring($pValue, 0, 29);
3028
                }
3029 26
                $i = 1;
3030 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...
3031 2
                    ++$i;
3032 2
                    if ($i == 10) {
3033
                        if (Shared\StringHelper::countCharacters($pValue) > 28) {
3034
                            $pValue = Shared\StringHelper::substring($pValue, 0, 28);
3035
                        }
3036 2
                    } elseif ($i == 100) {
3037
                        if (Shared\StringHelper::countCharacters($pValue) > 27) {
3038
                            $pValue = Shared\StringHelper::substring($pValue, 0, 27);
3039
                        }
3040
                    }
3041
                }
3042
3043 26
                $pValue = $pValue . '_' . $i; // ok, we have a valid name
3044
                //codeName is'nt used in formula : no need to call for an update
3045
                //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...
3046
            }
3047
        }
3048
3049 81
        $this->codeName = $pValue;
3050
3051 81
        return $this;
3052
    }
3053
3054
    /**
3055
     * Return the code name of the sheet.
3056
     *
3057
     * @return null|string
3058
     */
3059 81
    public function getCodeName()
3060
    {
3061 81
        return $this->codeName;
3062
    }
3063
3064
    /**
3065
     * Sheet has a code name ?
3066
     *
3067
     * @return bool
3068
     */
3069
    public function hasCodeName()
3070
    {
3071
        return !(is_null($this->codeName));
3072
    }
3073
}
3074