Completed
Push — develop ( a06731...6d4488 )
by Adrien
21:40
created

Worksheet::duplicateStyleArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 2
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
/**
6
 * Copyright (c) 2006 - 2016 PhpSpreadsheet
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 *
22
 * @category   PhpSpreadsheet
23
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
24
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
25
 */
26
class Worksheet implements IComparable
27
{
28
    /* Break types */
29
    const BREAK_NONE = 0;
30
    const BREAK_ROW = 1;
31
    const BREAK_COLUMN = 2;
32
33
    /* Sheet state */
34
    const SHEETSTATE_VISIBLE = 'visible';
35
    const SHEETSTATE_HIDDEN = 'hidden';
36
    const SHEETSTATE_VERYHIDDEN = 'veryHidden';
37
38
    /**
39
     * Invalid characters in sheet title
40
     *
41
     * @var array
42
     */
43
    private static $invalidCharacters = ['*', ':', '/', '\\', '?', '[', ']'];
44
45
    /**
46
     * Parent spreadsheet
47
     *
48
     * @var Spreadsheet
49
     */
50
    private $parent;
51
52
    /**
53
     * Cacheable collection of cells
54
     *
55
     * @var CachedObjectStorage_xxx
56
     */
57
    private $cellCollection;
58
59
    /**
60
     * Collection of row dimensions
61
     *
62
     * @var Worksheet\RowDimension[]
63
     */
64
    private $rowDimensions = [];
65
66
    /**
67
     * Default row dimension
68
     *
69
     * @var Worksheet\RowDimension
70
     */
71
    private $defaultRowDimension;
72
73
    /**
74
     * Collection of column dimensions
75
     *
76
     * @var Worksheet\ColumnDimension[]
77
     */
78
    private $columnDimensions = [];
79
80
    /**
81
     * Default column dimension
82
     *
83
     * @var Worksheet\ColumnDimension
84
     */
85
    private $defaultColumnDimension = null;
86
87
    /**
88
     * Collection of drawings
89
     *
90
     * @var Worksheet\BaseDrawing[]
91
     */
92
    private $drawingCollection = null;
93
94
    /**
95
     * Collection of Chart objects
96
     *
97
     * @var Chart[]
98
     */
99
    private $chartCollection = [];
100
101
    /**
102
     * Worksheet title
103
     *
104
     * @var string
105
     */
106
    private $title;
107
108
    /**
109
     * Sheet state
110
     *
111
     * @var string
112
     */
113
    private $sheetState;
114
115
    /**
116
     * Page setup
117
     *
118
     * @var Worksheet\PageSetup
119
     */
120
    private $pageSetup;
121
122
    /**
123
     * Page margins
124
     *
125
     * @var Worksheet\PageMargins
126
     */
127
    private $pageMargins;
128
129
    /**
130
     * Page header/footer
131
     *
132
     * @var Worksheet\HeaderFooter
133
     */
134
    private $headerFooter;
135
136
    /**
137
     * Sheet view
138
     *
139
     * @var Worksheet\SheetView
140
     */
141
    private $sheetView;
142
143
    /**
144
     * Protection
145
     *
146
     * @var Worksheet\Protection
147
     */
148
    private $protection;
149
150
    /**
151
     * Collection of styles
152
     *
153
     * @var Style[]
154
     */
155
    private $styles = [];
156
157
    /**
158
     * Conditional styles. Indexed by cell coordinate, e.g. 'A1'
159
     *
160
     * @var array
161
     */
162
    private $conditionalStylesCollection = [];
163
164
    /**
165
     * Is the current cell collection sorted already?
166
     *
167
     * @var bool
168
     */
169
    private $cellCollectionIsSorted = false;
170
171
    /**
172
     * Collection of breaks
173
     *
174
     * @var array
175
     */
176
    private $breaks = [];
177
178
    /**
179
     * Collection of merged cell ranges
180
     *
181
     * @var array
182
     */
183
    private $mergeCells = [];
184
185
    /**
186
     * Collection of protected cell ranges
187
     *
188
     * @var array
189
     */
190
    private $protectedCells = [];
191
192
    /**
193
     * Autofilter Range and selection
194
     *
195
     * @var Worksheet\AutoFilter
196
     */
197
    private $autoFilter;
198
199
    /**
200
     * Freeze pane
201
     *
202
     * @var string
203
     */
204
    private $freezePane = '';
205
206
    /**
207
     * Show gridlines?
208
     *
209
     * @var bool
210
     */
211
    private $showGridlines = true;
212
213
    /**
214
     * Print gridlines?
215
     *
216
     * @var bool
217
     */
218
    private $printGridlines = false;
219
220
    /**
221
     * Show row and column headers?
222
     *
223
     * @var bool
224
     */
225
    private $showRowColHeaders = true;
226
227
    /**
228
     * Show summary below? (Row/Column outline)
229
     *
230
     * @var bool
231
     */
232
    private $showSummaryBelow = true;
233
234
    /**
235
     * Show summary right? (Row/Column outline)
236
     *
237
     * @var bool
238
     */
239
    private $showSummaryRight = true;
240
241
    /**
242
     * Collection of comments
243
     *
244
     * @var Comment[]
245
     */
246
    private $comments = [];
247
248
    /**
249
     * Active cell. (Only one!)
250
     *
251
     * @var string
252
     */
253
    private $activeCell = 'A1';
254
255
    /**
256
     * Selected cells
257
     *
258
     * @var string
259
     */
260
    private $selectedCells = 'A1';
261
262
    /**
263
     * Cached highest column
264
     *
265
     * @var string
266
     */
267
    private $cachedHighestColumn = 'A';
268
269
    /**
270
     * Cached highest row
271
     *
272
     * @var int
273
     */
274
    private $cachedHighestRow = 1;
275
276
    /**
277
     * Right-to-left?
278
     *
279
     * @var bool
280
     */
281
    private $rightToLeft = false;
282
283
    /**
284
     * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'
285
     *
286
     * @var array
287
     */
288
    private $hyperlinkCollection = [];
289
290
    /**
291
     * Data validation objects. Indexed by cell coordinate, e.g. 'A1'
292
     *
293
     * @var array
294
     */
295
    private $dataValidationCollection = [];
296
297
    /**
298
     * Tab color
299
     *
300
     * @var Style\Color
301
     */
302
    private $tabColor;
303
304
    /**
305
     * Dirty flag
306
     *
307
     * @var bool
308
     */
309
    private $dirty = true;
310
311
    /**
312
     * Hash
313
     *
314
     * @var string
315
     */
316
    private $hash;
317
318
    /**
319
     * CodeName
320
     *
321
     * @var string
322
     */
323
    private $codeName = null;
324
325
    /**
326
     * Create a new worksheet
327
     *
328
     * @param Spreadsheet        $parent
329
     * @param string        $pTitle
330
     */
331 70
    public function __construct(Spreadsheet $parent = null, $pTitle = 'Worksheet')
332
    {
333
        // Set parent and title
334 70
        $this->parent = $parent;
335 70
        $this->setTitle($pTitle, false);
336
        // setTitle can change $pTitle
337 70
        $this->setCodeName($this->getTitle());
338 70
        $this->setSheetState(self::SHEETSTATE_VISIBLE);
339
340 70
        $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...
341
        // Set page setup
342 70
        $this->pageSetup = new Worksheet\PageSetup();
343
        // Set page margins
344 70
        $this->pageMargins = new Worksheet\PageMargins();
345
        // Set page header/footer
346 70
        $this->headerFooter = new Worksheet\HeaderFooter();
347
        // Set sheet view
348 70
        $this->sheetView = new Worksheet\SheetView();
349
        // Drawing collection
350 70
        $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...
351
        // Chart collection
352 70
        $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...
353
        // Protection
354 70
        $this->protection = new Worksheet\Protection();
355
        // Default row dimension
356 70
        $this->defaultRowDimension = new Worksheet\RowDimension(null);
357
        // Default column dimension
358 70
        $this->defaultColumnDimension = new Worksheet\ColumnDimension(null);
359 70
        $this->autoFilter = new Worksheet\AutoFilter(null, $this);
360 70
    }
361
362
    /**
363
     * Disconnect all cells from this Worksheet object,
364
     *    typically so that the worksheet object can be unset
365
     */
366 1
    public function disconnectCells()
367
    {
368 1
        if ($this->cellCollection !== null) {
369 1
            $this->cellCollection->unsetWorksheetCells();
370 1
            $this->cellCollection = null;
371
        }
372
        //    detach ourself from the workbook, so that it can then delete this worksheet successfully
373 1
        $this->parent = null;
374 1
    }
375
376
    /**
377
     * Code to execute when this worksheet is unset()
378
     */
379 1
    public function __destruct()
380
    {
381 1
        Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title);
382
383 1
        $this->disconnectCells();
384 1
    }
385
386
    /**
387
     * Return the cache controller for the cell collection
388
     *
389
     * @return CachedObjectStorage_xxx
390
     */
391 62
    public function getCellCacheController()
392
    {
393 62
        return $this->cellCollection;
394
    }
395
396
    /**
397
     * Get array of invalid characters for sheet title
398
     *
399
     * @return array
400
     */
401
    public static function getInvalidCharacters()
402
    {
403
        return self::$invalidCharacters;
404
    }
405
406
    /**
407
     * Check sheet code name for valid Excel syntax
408
     *
409
     * @param string $pValue The string to check
410
     * @throws Exception
411
     * @return string The valid string
412
     */
413 70
    private static function checkSheetCodeName($pValue)
414
    {
415 70
        $CharCount = Shared\StringHelper::countCharacters($pValue);
416 70
        if ($CharCount == 0) {
417
            throw new Exception('Sheet code name cannot be empty.');
418
        }
419
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ] and  first and last characters cannot be a "'"
420 70
        if ((str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) ||
421 70
            (Shared\StringHelper::substring($pValue, -1, 1) == '\'') ||
422 70
            (Shared\StringHelper::substring($pValue, 0, 1) == '\'')) {
423
            throw new Exception('Invalid character found in sheet code name');
424
        }
425
426
        // Maximum 31 characters allowed for sheet title
427 70
        if ($CharCount > 31) {
428
            throw new Exception('Maximum 31 characters allowed in sheet code name.');
429
        }
430
431 70
        return $pValue;
432
    }
433
434
    /**
435
     * Check sheet title for valid Excel syntax
436
     *
437
     * @param string $pValue The string to check
438
     * @throws Exception
439
     * @return string The valid string
440
     */
441 70
    private static function checkSheetTitle($pValue)
442
    {
443
        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ]
444 70
        if (str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) {
445
            throw new Exception('Invalid character found in sheet title');
446
        }
447
448
        // Maximum 31 characters allowed for sheet title
449 70
        if (Shared\StringHelper::countCharacters($pValue) > 31) {
450
            throw new Exception('Maximum 31 characters allowed in sheet title.');
451
        }
452
453 70
        return $pValue;
454
    }
455
456
    /**
457
     * Get collection of cells
458
     *
459
     * @param bool $pSorted Also sort the cell collection?
460
     * @return Cell[]
461
     */
462 60
    public function getCellCollection($pSorted = true)
463
    {
464 60
        if ($pSorted) {
465
            // Re-order cell collection
466 60
            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...
467
        }
468 59
        if ($this->cellCollection !== null) {
469 59
            return $this->cellCollection->getCellList();
470
        }
471
472
        return [];
473
    }
474
475
    /**
476
     * Sort collection of cells
477
     *
478
     * @return Worksheet
479
     */
480 60
    public function sortCellCollection()
481
    {
482 60
        if ($this->cellCollection !== null) {
483 60
            return $this->cellCollection->getSortedCellList();
484
        }
485
486
        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...
487
    }
488
489
    /**
490
     * Get collection of row dimensions
491
     *
492
     * @return Worksheet\RowDimension[]
493
     */
494 59
    public function getRowDimensions()
495
    {
496 59
        return $this->rowDimensions;
497
    }
498
499
    /**
500
     * Get default row dimension
501
     *
502
     * @return Worksheet\RowDimension
503
     */
504 59
    public function getDefaultRowDimension()
505
    {
506 59
        return $this->defaultRowDimension;
507
    }
508
509
    /**
510
     * Get collection of column dimensions
511
     *
512
     * @return Worksheet\ColumnDimension[]
513
     */
514 59
    public function getColumnDimensions()
515
    {
516 59
        return $this->columnDimensions;
517
    }
518
519
    /**
520
     * Get default column dimension
521
     *
522
     * @return Worksheet\ColumnDimension
523
     */
524 58
    public function getDefaultColumnDimension()
525
    {
526 58
        return $this->defaultColumnDimension;
527
    }
528
529
    /**
530
     * Get collection of drawings
531
     *
532
     * @return Worksheet\BaseDrawing[]
533
     */
534 59
    public function getDrawingCollection()
535
    {
536 59
        return $this->drawingCollection;
537
    }
538
539
    /**
540
     * Get collection of charts
541
     *
542
     * @return Chart[]
543
     */
544 14
    public function getChartCollection()
545
    {
546 14
        return $this->chartCollection;
547
    }
548
549
    /**
550
     * Add chart
551
     *
552
     * @param Chart $pChart
553
     * @param int|null $iChartIndex Index where chart should go (0,1,..., or null for last)
554
     * @return Chart
555
     */
556 14
    public function addChart(Chart $pChart = null, $iChartIndex = null)
557
    {
558 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...
559 14
        if (is_null($iChartIndex)) {
560 14
            $this->chartCollection[] = $pChart;
561
        } else {
562
            // Insert the chart at the requested index
563
            array_splice($this->chartCollection, $iChartIndex, 0, [$pChart]);
564
        }
565
566 14
        return $pChart;
567
    }
568
569
    /**
570
     * Return the count of charts on this worksheet
571
     *
572
     * @return int        The number of charts
573
     */
574 14
    public function getChartCount()
575
    {
576 14
        return count($this->chartCollection);
577
    }
578
579
    /**
580
     * Get a chart by its index position
581
     *
582
     * @param string $index Chart index position
583
     * @throws Exception
584
     * @return false|Chart
585
     */
586 13
    public function getChartByIndex($index = null)
587
    {
588 13
        $chartCount = count($this->chartCollection);
589 13
        if ($chartCount == 0) {
590
            return false;
591
        }
592 13
        if (is_null($index)) {
593
            $index = --$chartCount;
594
        }
595 13
        if (!isset($this->chartCollection[$index])) {
596
            return false;
597
        }
598
599 13
        return $this->chartCollection[$index];
600
    }
601
602
    /**
603
     * Return an array of the names of charts on this worksheet
604
     *
605
     * @throws Exception
606
     * @return string[] The names of charts
607
     */
608 1
    public function getChartNames()
609
    {
610 1
        $chartNames = [];
611 1
        foreach ($this->chartCollection as $chart) {
612 1
            $chartNames[] = $chart->getName();
613
        }
614
615 1
        return $chartNames;
616
    }
617
618
    /**
619
     * Get a chart by name
620
     *
621
     * @param string $chartName Chart name
622
     * @throws Exception
623
     * @return false|Chart
624
     */
625 1
    public function getChartByName($chartName = '')
626
    {
627 1
        $chartCount = count($this->chartCollection);
628 1
        if ($chartCount == 0) {
629
            return false;
630
        }
631 1
        foreach ($this->chartCollection as $index => $chart) {
632 1
            if ($chart->getName() == $chartName) {
633 1
                return $this->chartCollection[$index];
634
            }
635
        }
636
637
        return false;
638
    }
639
640
    /**
641
     * Refresh column dimensions
642
     *
643
     * @return Worksheet
644
     */
645 12
    public function refreshColumnDimensions()
646
    {
647 12
        $currentColumnDimensions = $this->getColumnDimensions();
648 12
        $newColumnDimensions = [];
649
650 12
        foreach ($currentColumnDimensions as $objColumnDimension) {
651 12
            $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension;
652
        }
653
654 12
        $this->columnDimensions = $newColumnDimensions;
655
656 12
        return $this;
657
    }
658
659
    /**
660
     * Refresh row dimensions
661
     *
662
     * @return Worksheet
663
     */
664 2
    public function refreshRowDimensions()
665
    {
666 2
        $currentRowDimensions = $this->getRowDimensions();
667 2
        $newRowDimensions = [];
668
669 2
        foreach ($currentRowDimensions as $objRowDimension) {
670 2
            $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension;
671
        }
672
673 2
        $this->rowDimensions = $newRowDimensions;
674
675 2
        return $this;
676
    }
677
678
    /**
679
     * Calculate worksheet dimension
680
     *
681
     * @return string  String containing the dimension of this worksheet
682
     */
683 58
    public function calculateWorksheetDimension()
684
    {
685
        // Return
686 58
        return 'A1' . ':' . $this->getHighestColumn() . $this->getHighestRow();
687
    }
688
689
    /**
690
     * Calculate worksheet data dimension
691
     *
692
     * @return string  String containing the dimension of this worksheet that actually contain data
693
     */
694
    public function calculateWorksheetDataDimension()
695
    {
696
        // Return
697
        return 'A1' . ':' . $this->getHighestDataColumn() . $this->getHighestDataRow();
698
    }
699
700
    /**
701
     * Calculate widths for auto-size columns
702
     *
703
     * @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...
704
     */
705 43
    public function calculateColumnWidths()
706
    {
707
        // initialize $autoSizes array
708 43
        $autoSizes = [];
709 43
        foreach ($this->getColumnDimensions() as $colDimension) {
710 23
            if ($colDimension->getAutoSize()) {
711 23
                $autoSizes[$colDimension->getColumnIndex()] = -1;
712
            }
713
        }
714
715
        // There is only something to do if there are some auto-size columns
716 43
        if (!empty($autoSizes)) {
717
            // build list of cells references that participate in a merge
718 11
            $isMergeCell = [];
719 11
            foreach ($this->getMergeCells() as $cells) {
720 8
                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...
721 8
                    $isMergeCell[$cellReference] = true;
722
                }
723
            }
724
725
            // loop through all cells in the worksheet
726 11
            foreach ($this->getCellCollection(false) as $cellID) {
727 11
                $cell = $this->getCell($cellID, false);
728 11
                if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
729
                    //Determine if cell is in merge range
730 11
                    $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentAddress()]);
731
732
                    //By default merged cells should be ignored
733 11
                    $isMergedButProceed = false;
734
735
                    //The only exception is if it's a merge range value cell of a 'vertical' randge (1 column wide)
736 11
                    if ($isMerged && $cell->isMergeRangeValueCell()) {
737
                        $range = $cell->getMergeRange();
738
                        $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...
739
                        if ($rangeBoundaries[0] == 1) {
740
                            $isMergedButProceed = true;
741
                        }
742
                    }
743
744
                    // Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range
745 11
                    if (!$isMerged || $isMergedButProceed) {
746
                        // Calculated value
747
                        // To formatted string
748 11
                        $cellValue = Style\NumberFormat::toFormattedString(
749 11
                            $cell->getCalculatedValue(),
750 11
                            $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
751
                        );
752
753 11
                        $autoSizes[$this->cellCollection->getCurrentColumn()] = max(
754 11
                            (float) $autoSizes[$this->cellCollection->getCurrentColumn()],
755 11
                            (float) Shared\Font::calculateColumnWidth(
756 11
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
757
                                $cellValue,
758 11
                                $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
759 11
                                $this->getParent()->getDefaultStyle()->getFont()
760
                            )
761
                        );
762
                    }
763
                }
764
            }
765
766
            // adjust column widths
767 11
            foreach ($autoSizes as $columnIndex => $width) {
768 11
                if ($width == -1) {
769
                    $width = $this->getDefaultColumnDimension()->getWidth();
770
                }
771 11
                $this->getColumnDimension($columnIndex)->setWidth($width);
772
            }
773
        }
774
775 43
        return $this;
776
    }
777
778
    /**
779
     * Get parent
780
     *
781
     * @return Spreadsheet
782
     */
783 70
    public function getParent()
784
    {
785 70
        return $this->parent;
786
    }
787
788
    /**
789
     * Re-bind parent
790
     *
791
     * @param Spreadsheet $parent
792
     * @return Worksheet
793
     */
794 1
    public function rebindParent(Spreadsheet $parent)
795
    {
796 1
        if ($this->parent !== null) {
797 1
            $namedRanges = $this->parent->getNamedRanges();
798 1
            foreach ($namedRanges as $namedRange) {
799
                $parent->addNamedRange($namedRange);
800
            }
801
802 1
            $this->parent->removeSheetByIndex(
803 1
                $this->parent->getIndex($this)
804
            );
805
        }
806 1
        $this->parent = $parent;
807
808 1
        return $this;
809
    }
810
811
    /**
812
     * Get title
813
     *
814
     * @return string
815
     */
816 70
    public function getTitle()
817
    {
818 70
        return $this->title;
819
    }
820
821
    /**
822
     * Set title
823
     *
824
     * @param string $pValue String containing the dimension of this worksheet
825
     * @param string $updateFormulaCellReferences boolean Flag indicating whether cell references in formulae should
826
     *            be updated to reflect the new sheet name.
827
     *          This should be left as the default true, unless you are
828
     *          certain that no formula cells on any worksheet contain
829
     *          references to this worksheet
830
     * @return Worksheet
831
     */
832 70
    public function setTitle($pValue = 'Worksheet', $updateFormulaCellReferences = true)
833
    {
834
        // Is this a 'rename' or not?
835 70
        if ($this->getTitle() == $pValue) {
836 4
            return $this;
837
        }
838
839
        // Syntax check
840 70
        self::checkSheetTitle($pValue);
841
842
        // Old title
843 70
        $oldTitle = $this->getTitle();
844
845 70
        if ($this->parent) {
846
            // Is there already such sheet name?
847 62
            if ($this->parent->sheetNameExists($pValue)) {
848
                // Use name, but append with lowest possible integer
849
850 2
                if (Shared\StringHelper::countCharacters($pValue) > 29) {
851
                    $pValue = Shared\StringHelper::substring($pValue, 0, 29);
852
                }
853 2
                $i = 1;
854 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...
855
                    ++$i;
856
                    if ($i == 10) {
857
                        if (Shared\StringHelper::countCharacters($pValue) > 28) {
858
                            $pValue = Shared\StringHelper::substring($pValue, 0, 28);
859
                        }
860
                    } elseif ($i == 100) {
861
                        if (Shared\StringHelper::countCharacters($pValue) > 27) {
862
                            $pValue = Shared\StringHelper::substring($pValue, 0, 27);
863
                        }
864
                    }
865
                }
866
867 2
                $altTitle = $pValue . ' ' . $i;
868
869 2
                return $this->setTitle($altTitle, $updateFormulaCellReferences);
870
            }
871
        }
872
873
        // Set title
874 70
        $this->title = $pValue;
875 70
        $this->dirty = true;
876
877 70
        if ($this->parent && $this->parent->getCalculationEngine()) {
878
            // New title
879 62
            $newTitle = $this->getTitle();
880 62
            $this->parent->getCalculationEngine()
881 62
                ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
882 62
            if ($updateFormulaCellReferences) {
883 23
                ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
884
            }
885
        }
886
887 70
        return $this;
888
    }
889
890
    /**
891
     * Get sheet state
892
     *
893
     * @return string Sheet state (visible, hidden, veryHidden)
894
     */
895 58
    public function getSheetState()
896
    {
897 58
        return $this->sheetState;
898
    }
899
900
    /**
901
     * Set sheet state
902
     *
903
     * @param string $value Sheet state (visible, hidden, veryHidden)
904
     * @return Worksheet
905
     */
906 70
    public function setSheetState($value = self::SHEETSTATE_VISIBLE)
907
    {
908 70
        $this->sheetState = $value;
909
910 70
        return $this;
911
    }
912
913
    /**
914
     * Get page setup
915
     *
916
     * @return Worksheet\PageSetup
917
     */
918 59
    public function getPageSetup()
919
    {
920 59
        return $this->pageSetup;
921
    }
922
923
    /**
924
     * Set page setup
925
     *
926
     * @param Worksheet\PageSetup    $pValue
927
     * @return Worksheet
928
     */
929
    public function setPageSetup(Worksheet\PageSetup $pValue)
930
    {
931
        $this->pageSetup = $pValue;
932
933
        return $this;
934
    }
935
936
    /**
937
     * Get page margins
938
     *
939
     * @return Worksheet\PageMargins
940
     */
941 59
    public function getPageMargins()
942
    {
943 59
        return $this->pageMargins;
944
    }
945
946
    /**
947
     * Set page margins
948
     *
949
     * @param Worksheet\PageMargins    $pValue
950
     * @return Worksheet
951
     */
952
    public function setPageMargins(Worksheet\PageMargins $pValue)
953
    {
954
        $this->pageMargins = $pValue;
955
956
        return $this;
957
    }
958
959
    /**
960
     * Get page header/footer
961
     *
962
     * @return Worksheet\HeaderFooter
963
     */
964 59
    public function getHeaderFooter()
965
    {
966 59
        return $this->headerFooter;
967
    }
968
969
    /**
970
     * Set page header/footer
971
     *
972
     * @param Worksheet\HeaderFooter    $pValue
973
     * @return Worksheet
974
     */
975
    public function setHeaderFooter(Worksheet\HeaderFooter $pValue)
976
    {
977
        $this->headerFooter = $pValue;
978
979
        return $this;
980
    }
981
982
    /**
983
     * Get sheet view
984
     *
985
     * @return Worksheet\SheetView
986
     */
987 58
    public function getSheetView()
988
    {
989 58
        return $this->sheetView;
990
    }
991
992
    /**
993
     * Set sheet view
994
     *
995
     * @param Worksheet\SheetView    $pValue
996
     * @return Worksheet
997
     */
998
    public function setSheetView(Worksheet\SheetView $pValue)
999
    {
1000
        $this->sheetView = $pValue;
1001
1002
        return $this;
1003
    }
1004
1005
    /**
1006
     * Get Protection
1007
     *
1008
     * @return Worksheet\Protection
1009
     */
1010 59
    public function getProtection()
1011
    {
1012 59
        return $this->protection;
1013
    }
1014
1015
    /**
1016
     * Set Protection
1017
     *
1018
     * @param Worksheet\Protection    $pValue
1019
     * @return Worksheet
1020
     */
1021
    public function setProtection(Worksheet\Protection $pValue)
1022
    {
1023
        $this->protection = $pValue;
1024
        $this->dirty = true;
1025
1026
        return $this;
1027
    }
1028
1029
    /**
1030
     * Get highest worksheet column
1031
     *
1032
     * @param   string     $row        Return the data highest column for the specified row,
1033
     *                                     or the highest column of any row if no row number is passed
1034
     * @return string Highest column name
1035
     */
1036 60
    public function getHighestColumn($row = null)
1037
    {
1038 60
        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...
1039 60
            return $this->cachedHighestColumn;
1040
        }
1041
1042
        return $this->getHighestDataColumn($row);
1043
    }
1044
1045
    /**
1046
     * Get highest worksheet column that contains data
1047
     *
1048
     * @param   string     $row        Return the highest data column for the specified row,
1049
     *                                     or the highest data column of any row if no row number is passed
1050
     * @return string Highest column name that contains data
1051
     */
1052 10
    public function getHighestDataColumn($row = null)
1053
    {
1054 10
        return $this->cellCollection->getHighestColumn($row);
1055
    }
1056
1057
    /**
1058
     * Get highest worksheet row
1059
     *
1060
     * @param   string     $column     Return the highest data row for the specified column,
1061
     *                                     or the highest row of any column if no column letter is passed
1062
     * @return int Highest row number
1063
     */
1064 60
    public function getHighestRow($column = null)
1065
    {
1066 60
        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...
1067 60
            return $this->cachedHighestRow;
1068
        }
1069
1070
        return $this->getHighestDataRow($column);
1071
    }
1072
1073
    /**
1074
     * Get highest worksheet row that contains data
1075
     *
1076
     * @param   string     $column     Return the highest data row for the specified column,
1077
     *                                     or the highest data row of any column if no column letter is passed
1078
     * @return string Highest row number that contains data
1079
     */
1080 12
    public function getHighestDataRow($column = null)
1081
    {
1082 12
        return $this->cellCollection->getHighestRow($column);
1083
    }
1084
1085
    /**
1086
     * Get highest worksheet column and highest row that have cell records
1087
     *
1088
     * @return array Highest column name and highest row number
1089
     */
1090
    public function getHighestRowAndColumn()
1091
    {
1092
        return $this->cellCollection->getHighestRowAndColumn();
1093
    }
1094
1095
    /**
1096
     * Set a cell value
1097
     *
1098
     * @param string $pCoordinate Coordinate of the cell
1099
     * @param mixed $pValue Value of the cell
1100
     * @param bool $returnCell   Return the worksheet (false, default) or the cell (true)
1101
     * @return Worksheet|Cell    Depending on the last parameter being specified
1102
     */
1103 36
    public function setCellValue($pCoordinate = 'A1', $pValue = null, $returnCell = false)
1104
    {
1105 36
        $cell = $this->getCell(strtoupper($pCoordinate))->setValue($pValue);
1106
1107 36
        return ($returnCell) ? $cell : $this;
1108
    }
1109
1110
    /**
1111
     * Set a cell value by using numeric cell coordinates
1112
     *
1113
     * @param int $pColumn Numeric column coordinate of the cell (A = 0)
1114
     * @param int $pRow Numeric row coordinate of the cell
1115
     * @param mixed $pValue Value of the cell
1116
     * @param bool $returnCell Return the worksheet (false, default) or the cell (true)
1117
     * @return Worksheet|Cell    Depending on the last parameter being specified
1118
     */
1119
    public function setCellValueByColumnAndRow($pColumn = 0, $pRow = 1, $pValue = null, $returnCell = false)
1120
    {
1121
        $cell = $this->getCellByColumnAndRow($pColumn, $pRow)->setValue($pValue);
1122
1123
        return ($returnCell) ? $cell : $this;
1124
    }
1125
1126
    /**
1127
     * Set a cell value
1128
     *
1129
     * @param string $pCoordinate Coordinate of the cell
1130
     * @param mixed  $pValue Value of the cell
1131
     * @param string $pDataType Explicit data type
1132
     * @param bool $returnCell Return the worksheet (false, default) or the cell (true)
1133
     * @return Worksheet|Cell    Depending on the last parameter being specified
1134
     */
1135
    public function setCellValueExplicit($pCoordinate = 'A1', $pValue = null, $pDataType = Cell\DataType::TYPE_STRING, $returnCell = false)
1136
    {
1137
        // Set value
1138
        $cell = $this->getCell(strtoupper($pCoordinate))->setValueExplicit($pValue, $pDataType);
1139
1140
        return ($returnCell) ? $cell : $this;
1141
    }
1142
1143
    /**
1144
     * Set a cell value by using numeric cell coordinates
1145
     *
1146
     * @param int $pColumn Numeric column coordinate of the cell
1147
     * @param int $pRow Numeric row coordinate of the cell
1148
     * @param mixed $pValue Value of the cell
1149
     * @param string $pDataType Explicit data type
1150
     * @param bool $returnCell Return the worksheet (false, default) or the cell (true)
1151
     * @return Worksheet|Cell    Depending on the last parameter being specified
1152
     */
1153
    public function setCellValueExplicitByColumnAndRow($pColumn = 0, $pRow = 1, $pValue = null, $pDataType = Cell\DataType::TYPE_STRING, $returnCell = false)
1154
    {
1155
        $cell = $this->getCellByColumnAndRow($pColumn, $pRow)->setValueExplicit($pValue, $pDataType);
1156
1157
        return ($returnCell) ? $cell : $this;
1158
    }
1159
1160
    /**
1161
     * Get cell at a specific coordinate
1162
     *
1163
     * @param string $pCoordinate    Coordinate of the cell
1164
     * @param bool $createIfNotExists  Flag indicating whether a new cell should be created if it doesn't
1165
     *                                       already exist, or a null should be returned instead
1166
     * @throws Exception
1167
     * @return null|Cell Cell that was found/created or null
1168
     */
1169 62
    public function getCell($pCoordinate = 'A1', $createIfNotExists = true)
1170
    {
1171
        // Check cell collection
1172 62
        if ($this->cellCollection->isDataSet(strtoupper($pCoordinate))) {
1173 61
            return $this->cellCollection->getCacheData($pCoordinate);
1174
        }
1175
1176
        // Worksheet reference?
1177 61 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...
1178 1
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1179
1180 1
            return $this->parent->getSheetByName($worksheetReference[0])->getCell(strtoupper($worksheetReference[1]), $createIfNotExists);
1181
        }
1182
1183
        // Named range?
1184 61
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1185 61
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1186
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1187
            if ($namedRange !== null) {
1188
                $pCoordinate = $namedRange->getRange();
1189
1190
                return $namedRange->getWorksheet()->getCell($pCoordinate, $createIfNotExists);
1191
            }
1192
        }
1193
1194
        // Uppercase coordinate
1195 61
        $pCoordinate = strtoupper($pCoordinate);
1196
1197 61 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...
1198
            throw new Exception('Cell coordinate can not be a range of cells.');
1199 61
        } elseif (strpos($pCoordinate, '$') !== false) {
1200
            throw new Exception('Cell coordinate must not be absolute.');
1201
        }
1202
1203
        // Create new cell object, if required
1204 61
        return $createIfNotExists ? $this->createNewCell($pCoordinate) : null;
1205
    }
1206
1207
    /**
1208
     * Get cell at a specific coordinate by using numeric cell coordinates
1209
     *
1210
     * @param  string $pColumn Numeric column coordinate of the cell
1211
     * @param string $pRow Numeric row coordinate of the cell
1212
     * @param bool $createIfNotExists  Flag indicating whether a new cell should be created if it doesn't
1213
     *                                       already exist, or a null should be returned instead
1214
     * @return null|Cell Cell that was found/created or null
1215
     */
1216 29
    public function getCellByColumnAndRow($pColumn = 0, $pRow = 1, $createIfNotExists = true)
1217
    {
1218 29
        $columnLetter = Cell::stringFromColumnIndex($pColumn);
1219 29
        $coordinate = $columnLetter . $pRow;
1220
1221 29
        if ($this->cellCollection->isDataSet($coordinate)) {
1222 29
            return $this->cellCollection->getCacheData($coordinate);
1223
        }
1224
1225
        // Create new cell object, if required
1226 17
        return $createIfNotExists ? $this->createNewCell($coordinate) : null;
1227
    }
1228
1229
    /**
1230
     * Create a new cell at the specified coordinate
1231
     *
1232
     * @param string $pCoordinate    Coordinate of the cell
1233
     * @return Cell Cell that was created
1234
     */
1235 62
    private function createNewCell($pCoordinate)
1236
    {
1237 62
        $cell = $this->cellCollection->addCacheData(
1238
            $pCoordinate,
1239 62
            new Cell(null, Cell\DataType::TYPE_NULL, $this)
1240
        );
1241 62
        $this->cellCollectionIsSorted = false;
1242
1243
        // Coordinates
1244 62
        $aCoordinates = Cell::coordinateFromString($pCoordinate);
1245 62
        if (Cell::columnIndexFromString($this->cachedHighestColumn) < Cell::columnIndexFromString($aCoordinates[0])) {
1246 54
            $this->cachedHighestColumn = $aCoordinates[0];
1247
        }
1248 62
        $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...
1249
1250
        // Cell needs appropriate xfIndex from dimensions records
1251
        //    but don't create dimension records if they don't already exist
1252 62
        $rowDimension = $this->getRowDimension($aCoordinates[1], false);
1253 62
        $columnDimension = $this->getColumnDimension($aCoordinates[0], false);
1254
1255 62
        if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) {
1256
            // then there is a row dimension with explicit style, assign it to the cell
1257
            $cell->setXfIndex($rowDimension->getXfIndex());
1258 62
        } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) {
1259
            // then there is a column dimension, assign it to the cell
1260
            $cell->setXfIndex($columnDimension->getXfIndex());
1261
        }
1262
1263 62
        return $cell;
1264
    }
1265
1266
    /**
1267
     * Does the cell at a specific coordinate exist?
1268
     *
1269
     * @param string $pCoordinate  Coordinate of the cell
1270
     * @throws Exception
1271
     * @return bool
1272
     */
1273 37
    public function cellExists($pCoordinate = 'A1')
1274
    {
1275
        // Worksheet reference?
1276 37 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...
1277
            $worksheetReference = self::extractSheetTitle($pCoordinate, true);
1278
1279
            return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1]));
1280
        }
1281
1282
        // Named range?
1283 37
        if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
1284 37
            (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
1285
            $namedRange = NamedRange::resolveRange($pCoordinate, $this);
1286
            if ($namedRange !== null) {
1287
                $pCoordinate = $namedRange->getRange();
1288
                if ($this->getHashCode() != $namedRange->getWorksheet()->getHashCode()) {
1289
                    if (!$namedRange->getLocalOnly()) {
1290
                        return $namedRange->getWorksheet()->cellExists($pCoordinate);
1291
                    } else {
1292
                        throw new Exception('Named range ' . $namedRange->getName() . ' is not accessible from within sheet ' . $this->getTitle());
1293
                    }
1294
                }
1295
            } else {
1296
                return false;
1297
            }
1298
        }
1299
1300
        // Uppercase coordinate
1301 37
        $pCoordinate = strtoupper($pCoordinate);
1302
1303 37
        if (strpos($pCoordinate, ':') !== false || strpos($pCoordinate, ',') !== false) {
1304
            throw new Exception('Cell coordinate can not be a range of cells.');
1305 37
        } elseif (strpos($pCoordinate, '$') !== false) {
1306
            throw new Exception('Cell coordinate must not be absolute.');
1307
        } else {
1308
            // Coordinates
1309 37
            $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...
1310
1311
            // Cell exists?
1312 37
            return $this->cellCollection->isDataSet($pCoordinate);
1313
        }
1314
    }
1315
1316
    /**
1317
     * Cell at a specific coordinate by using numeric cell coordinates exists?
1318
     *
1319
     * @param string $pColumn Numeric column coordinate of the cell
1320
     * @param string $pRow Numeric row coordinate of the cell
1321
     * @return bool
1322
     */
1323 3
    public function cellExistsByColumnAndRow($pColumn = 0, $pRow = 1)
1324
    {
1325 3
        return $this->cellExists(Cell::stringFromColumnIndex($pColumn) . $pRow);
1326
    }
1327
1328
    /**
1329
     * Get row dimension at a specific row
1330
     *
1331
     * @param int $pRow Numeric index of the row
1332
     * @return Worksheet\RowDimension
1333
     */
1334 62
    public function getRowDimension($pRow = 1, $create = true)
1335
    {
1336
        // Found
1337 62
        $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...
1338
1339
        // Get row dimension
1340 62
        if (!isset($this->rowDimensions[$pRow])) {
1341 62
            if (!$create) {
1342 61
                return null;
1343
            }
1344 58
            $this->rowDimensions[$pRow] = new Worksheet\RowDimension($pRow);
1345
1346 58
            $this->cachedHighestRow = max($this->cachedHighestRow, $pRow);
1347
        }
1348
1349 58
        return $this->rowDimensions[$pRow];
1350
    }
1351
1352
    /**
1353
     * Get column dimension at a specific column
1354
     *
1355
     * @param string $pColumn String index of the column
1356
     * @return Worksheet\ColumnDimension
1357
     */
1358 62
    public function getColumnDimension($pColumn = 'A', $create = true)
1359
    {
1360
        // Uppercase coordinate
1361 62
        $pColumn = strtoupper($pColumn);
1362
1363
        // Fetch dimensions
1364 62
        if (!isset($this->columnDimensions[$pColumn])) {
1365 62
            if (!$create) {
1366 61
                return null;
1367
            }
1368 26
            $this->columnDimensions[$pColumn] = new Worksheet\ColumnDimension($pColumn);
1369
1370 26
            if (Cell::columnIndexFromString($this->cachedHighestColumn) < Cell::columnIndexFromString($pColumn)) {
1371 12
                $this->cachedHighestColumn = $pColumn;
1372
            }
1373
        }
1374
1375 26
        return $this->columnDimensions[$pColumn];
1376
    }
1377
1378
    /**
1379
     * Get column dimension at a specific column by using numeric cell coordinates
1380
     *
1381
     * @param int $pColumn Numeric column coordinate of the cell
1382
     * @return Worksheet\ColumnDimension
1383
     */
1384 3
    public function getColumnDimensionByColumn($pColumn = 0)
1385
    {
1386 3
        return $this->getColumnDimension(Cell::stringFromColumnIndex($pColumn));
1387
    }
1388
1389
    /**
1390
     * Get styles
1391
     *
1392
     * @return Style[]
1393
     */
1394
    public function getStyles()
1395
    {
1396
        return $this->styles;
1397
    }
1398
1399
    /**
1400
     * Get style for cell
1401
     *
1402
     * @param string $pCellCoordinate Cell coordinate (or range) to get style for
1403
     * @throws Exception
1404
     * @return Style
1405
     */
1406 29
    public function getStyle($pCellCoordinate = 'A1')
1407
    {
1408
        // set this sheet as active
1409 29
        $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
1410
1411
        // set cell coordinate as active
1412 29
        $this->setSelectedCells(strtoupper($pCellCoordinate));
1413
1414 29
        return $this->parent->getCellXfSupervisor();
1415
    }
1416
1417
    /**
1418
     * Get conditional styles for a cell
1419
     *
1420
     * @param string $pCoordinate
1421
     * @return Style\Conditional[]
1422
     */
1423 2
    public function getConditionalStyles($pCoordinate = 'A1')
1424
    {
1425 2
        $pCoordinate = strtoupper($pCoordinate);
1426 2
        if (!isset($this->conditionalStylesCollection[$pCoordinate])) {
1427 2
            $this->conditionalStylesCollection[$pCoordinate] = [];
1428
        }
1429
1430 2
        return $this->conditionalStylesCollection[$pCoordinate];
1431
    }
1432
1433
    /**
1434
     * Do conditional styles exist for this cell?
1435
     *
1436
     * @param string $pCoordinate
1437
     * @return bool
1438
     */
1439 10
    public function conditionalStylesExists($pCoordinate = 'A1')
1440
    {
1441 10
        if (isset($this->conditionalStylesCollection[strtoupper($pCoordinate)])) {
1442
            return true;
1443
        }
1444
1445 10
        return false;
1446
    }
1447
1448
    /**
1449
     * Removes conditional styles for a cell
1450
     *
1451
     * @param string $pCoordinate
1452
     * @return Worksheet
1453
     */
1454 11
    public function removeConditionalStyles($pCoordinate = 'A1')
1455
    {
1456 11
        unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
1457
1458 11
        return $this;
1459
    }
1460
1461
    /**
1462
     * Get collection of conditional styles
1463
     *
1464
     * @return array
1465
     */
1466 58
    public function getConditionalStylesCollection()
1467
    {
1468 58
        return $this->conditionalStylesCollection;
1469
    }
1470
1471
    /**
1472
     * Set conditional styles
1473
     *
1474
     * @param string $pCoordinate eg: 'A1'
1475
     * @param $pValue Style\Conditional[]
1476
     * @return Worksheet
1477
     */
1478 2
    public function setConditionalStyles($pCoordinate, $pValue)
1479
    {
1480 2
        $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue;
1481
1482 2
        return $this;
1483
    }
1484
1485
    /**
1486
     * Get style for cell by using numeric cell coordinates
1487
     *
1488
     * @param int $pColumn  Numeric column coordinate of the cell
1489
     * @param int $pRow Numeric row coordinate of the cell
1490
     * @param int pColumn2 Numeric column coordinate of the range cell
1491
     * @param int pRow2 Numeric row coordinate of the range cell
1492
     * @return Style
1493
     */
1494
    public function getStyleByColumnAndRow($pColumn = 0, $pRow = 1, $pColumn2 = null, $pRow2 = null)
1495
    {
1496
        if (!is_null($pColumn2) && !is_null($pRow2)) {
1497
            $cellRange = Cell::stringFromColumnIndex($pColumn) . $pRow . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1498
1499
            return $this->getStyle($cellRange);
1500
        }
1501
1502
        return $this->getStyle(Cell::stringFromColumnIndex($pColumn) . $pRow);
1503
    }
1504
1505
    /**
1506
     * Duplicate cell style to a range of cells
1507
     *
1508
     * Please note that this will overwrite existing cell styles for cells in range!
1509
     *
1510
     * @param Style $pCellStyle Cell style to duplicate
1511
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1512
     * @throws Exception
1513
     * @return Worksheet
1514
     */
1515 2
    public function duplicateStyle(Style $pCellStyle = null, $pRange = '')
1516
    {
1517
        // make sure we have a real style and not supervisor
1518 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...
1519
1520
        // Add the style to the workbook if necessary
1521 2
        $workbook = $this->parent;
1522 2
        if ($existingStyle = $this->parent->getCellXfByHashCode($pCellStyle->getHashCode())) {
1523
            // there is already such cell Xf in our collection
1524 1
            $xfIndex = $existingStyle->getIndex();
1525
        } else {
1526
            // we don't have such a cell Xf, need to add
1527 2
            $workbook->addCellXf($pCellStyle);
0 ignored issues
show
Bug introduced by
It seems like $pCellStyle defined by parameter $pCellStyle on line 1515 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...
1528 2
            $xfIndex = $pCellStyle->getIndex();
1529
        }
1530
1531
        // Calculate range outer borders
1532 2
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange . ':' . $pRange);
1533
1534
        // Make sure we can loop upwards on rows and columns
1535 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...
1536
            $tmp = $rangeStart;
1537
            $rangeStart = $rangeEnd;
1538
            $rangeEnd = $tmp;
1539
        }
1540
1541
        // Loop through cells and apply styles
1542 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...
1543 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1544 2
                $this->getCell(Cell::stringFromColumnIndex($col - 1) . $row)->setXfIndex($xfIndex);
1545
            }
1546
        }
1547
1548 2
        return $this;
1549
    }
1550
1551
    /**
1552
     * Duplicate conditional style to a range of cells
1553
     *
1554
     * Please note that this will overwrite existing cell styles for cells in range!
1555
     *
1556
     * @param    Style\Conditional[]    $pCellStyle    Cell style to duplicate
1557
     * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
1558
     * @throws Exception
1559
     * @return Worksheet
1560
     */
1561 2
    public function duplicateConditionalStyle(array $pCellStyle = null, $pRange = '')
1562
    {
1563 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...
1564 2
            if (!($cellStyle instanceof Style\Conditional)) {
1565 2
                throw new Exception('Style is not a conditional style');
1566
            }
1567
        }
1568
1569
        // Calculate range outer borders
1570 2
        list($rangeStart, $rangeEnd) = Cell::rangeBoundaries($pRange . ':' . $pRange);
1571
1572
        // Make sure we can loop upwards on rows and columns
1573 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...
1574
            $tmp = $rangeStart;
1575
            $rangeStart = $rangeEnd;
1576
            $rangeEnd = $tmp;
1577
        }
1578
1579
        // Loop through cells and apply styles
1580 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...
1581 2
            for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
1582 2
                $this->setConditionalStyles(Cell::stringFromColumnIndex($col - 1) . $row, $pCellStyle);
1583
            }
1584
        }
1585
1586 2
        return $this;
1587
    }
1588
1589
    /**
1590
     * Set break on a cell
1591
     *
1592
     * @param string $pCell Cell coordinate (e.g. A1)
1593
     * @param int $pBreak Break type (type of Worksheet::BREAK_*)
1594
     * @throws Exception
1595
     * @return Worksheet
1596
     */
1597 1
    public function setBreak($pCell = 'A1', $pBreak = self::BREAK_NONE)
1598
    {
1599
        // Uppercase coordinate
1600 1
        $pCell = strtoupper($pCell);
1601
1602 1
        if ($pCell != '') {
1603 1
            if ($pBreak == self::BREAK_NONE) {
1604
                if (isset($this->breaks[$pCell])) {
1605
                    unset($this->breaks[$pCell]);
1606
                }
1607
            } else {
1608 1
                $this->breaks[$pCell] = $pBreak;
1609
            }
1610
        } else {
1611
            throw new Exception('No cell coordinate specified.');
1612
        }
1613
1614 1
        return $this;
1615
    }
1616
1617
    /**
1618
     * Set break on a cell by using numeric cell coordinates
1619
     *
1620
     * @param int $pColumn Numeric column coordinate of the cell
1621
     * @param int $pRow Numeric row coordinate of the cell
1622
     * @param  int $pBreak Break type (type of \PhpOffice\PhpSpreadsheet\Worksheet::BREAK_*)
1623
     * @return Worksheet
1624
     */
1625
    public function setBreakByColumnAndRow($pColumn = 0, $pRow = 1, $pBreak = self::BREAK_NONE)
1626
    {
1627
        return $this->setBreak(Cell::stringFromColumnIndex($pColumn) . $pRow, $pBreak);
1628
    }
1629
1630
    /**
1631
     * Get breaks
1632
     *
1633
     * @return array[]
1634
     */
1635 59
    public function getBreaks()
1636
    {
1637 59
        return $this->breaks;
1638
    }
1639
1640
    /**
1641
     * Set merge on a cell range
1642
     *
1643
     * @param string $pRange  Cell range (e.g. A1:E1)
1644
     * @throws Exception
1645
     * @return Worksheet
1646
     */
1647 15
    public function mergeCells($pRange = 'A1:A1')
1648
    {
1649
        // Uppercase coordinate
1650 15
        $pRange = strtoupper($pRange);
1651
1652 15
        if (strpos($pRange, ':') !== false) {
1653 15
            $this->mergeCells[$pRange] = $pRange;
1654
1655
            // make sure cells are created
1656
1657
            // get the cells in the range
1658 15
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
1659
1660
            // create upper left cell if it does not already exist
1661 15
            $upperLeft = $aReferences[0];
1662 15
            if (!$this->cellExists($upperLeft)) {
1663 10
                $this->getCell($upperLeft)->setValueExplicit(null, Cell\DataType::TYPE_NULL);
1664
            }
1665
1666
            // Blank out the rest of the cells in the range (if they exist)
1667 15
            $count = count($aReferences);
1668 15
            for ($i = 1; $i < $count; ++$i) {
1669 15
                if ($this->cellExists($aReferences[$i])) {
1670 4
                    $this->getCell($aReferences[$i])->setValueExplicit(null, Cell\DataType::TYPE_NULL);
1671
                }
1672
            }
1673
        } else {
1674
            throw new Exception('Merge must be set on a range of cells.');
1675
        }
1676
1677 15
        return $this;
1678
    }
1679
1680
    /**
1681
     * Set merge on a cell range by using numeric cell coordinates
1682
     *
1683
     * @param int $pColumn1    Numeric column coordinate of the first cell
1684
     * @param int $pRow1        Numeric row coordinate of the first cell
1685
     * @param int $pColumn2    Numeric column coordinate of the last cell
1686
     * @param int $pRow2        Numeric row coordinate of the last cell
1687
     * @throws    Exception
1688
     * @return Worksheet
1689
     */
1690
    public function mergeCellsByColumnAndRow($pColumn1 = 0, $pRow1 = 1, $pColumn2 = 0, $pRow2 = 1)
1691
    {
1692
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1693
1694
        return $this->mergeCells($cellRange);
1695
    }
1696
1697
    /**
1698
     * Remove merge on a cell range
1699
     *
1700
     * @param    string            $pRange        Cell range (e.g. A1:E1)
1701
     * @throws    Exception
1702
     * @return Worksheet
1703
     */
1704 9
    public function unmergeCells($pRange = 'A1:A1')
1705
    {
1706
        // Uppercase coordinate
1707 9
        $pRange = strtoupper($pRange);
1708
1709 9
        if (strpos($pRange, ':') !== false) {
1710 9
            if (isset($this->mergeCells[$pRange])) {
1711 9
                unset($this->mergeCells[$pRange]);
1712
            } else {
1713 9
                throw new Exception('Cell range ' . $pRange . ' not known as merged.');
1714
            }
1715
        } else {
1716
            throw new Exception('Merge can only be removed from a range of cells.');
1717
        }
1718
1719 9
        return $this;
1720
    }
1721
1722
    /**
1723
     * Remove merge on a cell range by using numeric cell coordinates
1724
     *
1725
     * @param int $pColumn1    Numeric column coordinate of the first cell
1726
     * @param int $pRow1        Numeric row coordinate of the first cell
1727
     * @param int $pColumn2    Numeric column coordinate of the last cell
1728
     * @param int $pRow2        Numeric row coordinate of the last cell
1729
     * @throws    Exception
1730
     * @return Worksheet
1731
     */
1732
    public function unmergeCellsByColumnAndRow($pColumn1 = 0, $pRow1 = 1, $pColumn2 = 0, $pRow2 = 1)
1733
    {
1734
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1735
1736
        return $this->unmergeCells($cellRange);
1737
    }
1738
1739
    /**
1740
     * Get merge cells array.
1741
     *
1742
     * @return array[]
1743
     */
1744 59
    public function getMergeCells()
1745
    {
1746 59
        return $this->mergeCells;
1747
    }
1748
1749
    /**
1750
     * Set merge cells array for the entire sheet. Use instead mergeCells() to merge
1751
     * a single cell range.
1752
     *
1753
     * @param array
1754
     */
1755 12
    public function setMergeCells($pValue = [])
1756
    {
1757 12
        $this->mergeCells = $pValue;
1758
1759 12
        return $this;
1760
    }
1761
1762
    /**
1763
     * Set protection on a cell range
1764
     *
1765
     * @param    string            $pRange                Cell (e.g. A1) or cell range (e.g. A1:E1)
1766
     * @param    string            $pPassword            Password to unlock the protection
1767
     * @param    bool        $pAlreadyHashed    If the password has already been hashed, set this to true
1768
     * @throws    Exception
1769
     * @return Worksheet
1770
     */
1771 9
    public function protectCells($pRange = 'A1', $pPassword = '', $pAlreadyHashed = false)
1772
    {
1773
        // Uppercase coordinate
1774 9
        $pRange = strtoupper($pRange);
1775
1776 9
        if (!$pAlreadyHashed) {
1777 9
            $pPassword = Shared\PasswordHasher::hashPassword($pPassword);
1778
        }
1779 9
        $this->protectedCells[$pRange] = $pPassword;
1780
1781 9
        return $this;
1782
    }
1783
1784
    /**
1785
     * Set protection 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
     * @param string $pPassword            Password to unlock the protection
1792
     * @param    bool $pAlreadyHashed    If the password has already been hashed, set this to true
1793
     * @throws    Exception
1794
     * @return Worksheet
1795
     */
1796
    public function protectCellsByColumnAndRow($pColumn1 = 0, $pRow1 = 1, $pColumn2 = 0, $pRow2 = 1, $pPassword = '', $pAlreadyHashed = false)
1797
    {
1798
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1799
1800
        return $this->protectCells($cellRange, $pPassword, $pAlreadyHashed);
1801
    }
1802
1803
    /**
1804
     * Remove protection on a cell range
1805
     *
1806
     * @param    string            $pRange        Cell (e.g. A1) or cell range (e.g. A1:E1)
1807
     * @throws    Exception
1808
     * @return Worksheet
1809
     */
1810 9
    public function unprotectCells($pRange = 'A1')
1811
    {
1812
        // Uppercase coordinate
1813 9
        $pRange = strtoupper($pRange);
1814
1815 9
        if (isset($this->protectedCells[$pRange])) {
1816 9
            unset($this->protectedCells[$pRange]);
1817
        } else {
1818
            throw new Exception('Cell range ' . $pRange . ' not known as protected.');
1819
        }
1820
1821 9
        return $this;
1822
    }
1823
1824
    /**
1825
     * Remove protection on a cell range by using numeric cell coordinates
1826
     *
1827
     * @param int  $pColumn1            Numeric column coordinate of the first cell
1828
     * @param int  $pRow1                Numeric row coordinate of the first cell
1829
     * @param int  $pColumn2            Numeric column coordinate of the last cell
1830
     * @param int $pRow2                Numeric row coordinate of the last cell
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
     * @throws    Exception
1834
     * @return Worksheet
1835
     */
1836
    public function unprotectCellsByColumnAndRow($pColumn1 = 0, $pRow1 = 1, $pColumn2 = 0, $pRow2 = 1, $pPassword = '', $pAlreadyHashed = false)
1837
    {
1838
        $cellRange = Cell::stringFromColumnIndex($pColumn1) . $pRow1 . ':' . Cell::stringFromColumnIndex($pColumn2) . $pRow2;
1839
1840
        return $this->unprotectCells($cellRange, $pPassword, $pAlreadyHashed);
0 ignored issues
show
Unused Code introduced by
The call to Worksheet::unprotectCells() has too many arguments starting with $pPassword.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

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