Completed
Push — develop ( 1cec98...8d7602 )
by Adrien
24:39
created

ReferenceHelper::updateNamedFormulas()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7.6024

Importance

Changes 0
Metric Value
cc 7
eloc 12
c 0
b 0
f 0
nc 6
nop 3
dl 0
loc 20
ccs 10
cts 13
cp 0.7692
crap 7.6024
rs 8.2222
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6
use PhpOffice\PhpSpreadsheet\Cell\DataType;
7
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
8
9
class ReferenceHelper
10
{
11
    /**    Constants                */
12
    /**    Regular Expressions      */
13
    const REFHELPER_REGEXP_CELLREF = '((\w*|\'[^!]*\')!)?(?<![:a-z\$])(\$?[a-z]{1,3}\$?\d+)(?=[^:!\d\'])';
14
    const REFHELPER_REGEXP_CELLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}\$?\d+):(\$?[a-z]{1,3}\$?\d+)';
15
    const REFHELPER_REGEXP_ROWRANGE = '((\w*|\'[^!]*\')!)?(\$?\d+):(\$?\d+)';
16
    const REFHELPER_REGEXP_COLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}):(\$?[a-z]{1,3})';
17
18
    /**
19
     * Instance of this class.
20
     *
21
     * @var ReferenceHelper
22
     */
23
    private static $instance;
24
25
    /**
26
     * Get an instance of this class.
27
     *
28
     * @return ReferenceHelper
29
     */
30 48
    public static function getInstance()
31
    {
32 48 View Code Duplication
        if (!isset(self::$instance) || (self::$instance === null)) {
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...
33 40
            self::$instance = new self();
34
        }
35
36 48
        return self::$instance;
37
    }
38
39
    /**
40
     * Create a new ReferenceHelper.
41
     */
42 40
    protected function __construct()
43
    {
44 40
    }
45
46
    /**
47
     * Compare two column addresses
48
     * Intended for use as a Callback function for sorting column addresses by column.
49
     *
50
     * @param string $a First column to test (e.g. 'AA')
51
     * @param string $b Second column to test (e.g. 'Z')
52
     *
53
     * @return int
54
     */
55 1
    public static function columnSort($a, $b)
56
    {
57 1
        return strcasecmp(strlen($a) . $a, strlen($b) . $b);
58
    }
59
60
    /**
61
     * Compare two column addresses
62
     * Intended for use as a Callback function for reverse sorting column addresses by column.
63
     *
64
     * @param string $a First column to test (e.g. 'AA')
65
     * @param string $b Second column to test (e.g. 'Z')
66
     *
67
     * @return int
68
     */
69 1
    public static function columnReverseSort($a, $b)
70
    {
71 1
        return 1 - strcasecmp(strlen($a) . $a, strlen($b) . $b);
72
    }
73
74
    /**
75
     * Compare two cell addresses
76
     * Intended for use as a Callback function for sorting cell addresses by column and row.
77
     *
78
     * @param string $a First cell to test (e.g. 'AA1')
79
     * @param string $b Second cell to test (e.g. 'Z1')
80
     *
81
     * @return int
82
     */
83 13 View Code Duplication
    public static function cellSort($a, $b)
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...
84
    {
85 13
        sscanf($a, '%[A-Z]%d', $ac, $ar);
0 ignored issues
show
Bug introduced by
The variable $ar does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
86 13
        sscanf($b, '%[A-Z]%d', $bc, $br);
0 ignored issues
show
Bug introduced by
The variable $br does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
87
88 13
        if ($ar == $br) {
89
            return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
90
        }
91
92 13
        return ($ar < $br) ? -1 : 1;
93
    }
94
95
    /**
96
     * Compare two cell addresses
97
     * Intended for use as a Callback function for sorting cell addresses by column and row.
98
     *
99
     * @param string $a First cell to test (e.g. 'AA1')
100
     * @param string $b Second cell to test (e.g. 'Z1')
101
     *
102
     * @return int
103
     */
104 13 View Code Duplication
    public static function cellReverseSort($a, $b)
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...
105
    {
106 13
        sscanf($a, '%[A-Z]%d', $ac, $ar);
0 ignored issues
show
Bug introduced by
The variable $ar does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
107 13
        sscanf($b, '%[A-Z]%d', $bc, $br);
0 ignored issues
show
Bug introduced by
The variable $br does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
108
109 13
        if ($ar == $br) {
110
            return 1 - strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
111
        }
112
113 13
        return ($ar < $br) ? 1 : -1;
114
    }
115
116
    /**
117
     * Test whether a cell address falls within a defined range of cells.
118
     *
119
     * @param string $cellAddress Address of the cell we're testing
120
     * @param int $beforeRow Number of the row we're inserting/deleting before
121
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
122
     * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
123
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
124
     *
125
     * @return bool
126
     */
127 13
    private static function cellAddressInDeleteRange($cellAddress, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)
128
    {
129 13
        list($cellColumn, $cellRow) = Coordinate::coordinateFromString($cellAddress);
130 13
        $cellColumnIndex = Coordinate::columnIndexFromString($cellColumn);
131
        //    Is cell within the range of rows/columns if we're deleting
132 13
        if ($pNumRows < 0 &&
133 13
            ($cellRow >= ($beforeRow + $pNumRows)) &&
134 13
            ($cellRow < $beforeRow)) {
135
            return true;
136 13
        } elseif ($pNumCols < 0 &&
137 13
            ($cellColumnIndex >= ($beforeColumnIndex + $pNumCols)) &&
138 13
            ($cellColumnIndex < $beforeColumnIndex)) {
139
            return true;
140
        }
141
142 13
        return false;
143
    }
144
145
    /**
146
     * Update page breaks when inserting/deleting rows/columns.
147
     *
148
     * @param Worksheet $pSheet The worksheet that we're editing
149
     * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
150
     * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
151
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
152
     * @param int $beforeRow Number of the row we're inserting/deleting before
153
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
154
     */
155 15
    protected function adjustPageBreaks(Worksheet $pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
156
    {
157 15
        $aBreaks = $pSheet->getBreaks();
158 15
        ($pNumCols > 0 || $pNumRows > 0) ?
159 15
            uksort($aBreaks, ['self', 'cellReverseSort']) : uksort($aBreaks, ['self', 'cellSort']);
160
161 15
        foreach ($aBreaks as $key => $value) {
162
            if (self::cellAddressInDeleteRange($key, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)) {
163
                //    If we're deleting, then clear any defined breaks that are within the range
164
                //        of rows/columns that we're deleting
165
                $pSheet->setBreak($key, Worksheet::BREAK_NONE);
166
            } else {
167
                //    Otherwise update any affected breaks by inserting a new break at the appropriate point
168
                //        and removing the old affected break
169
                $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
170
                if ($key != $newReference) {
171
                    $pSheet->setBreak($newReference, $value)
172
                        ->setBreak($key, Worksheet::BREAK_NONE);
173
                }
174
            }
175
        }
176 15
    }
177
178
    /**
179
     * Update cell comments when inserting/deleting rows/columns.
180
     *
181
     * @param Worksheet $pSheet The worksheet that we're editing
182
     * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
183
     * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
184
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
185
     * @param int $beforeRow Number of the row we're inserting/deleting before
186
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
187
     */
188 15
    protected function adjustComments($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
189
    {
190 15
        $aComments = $pSheet->getComments();
191 15
        $aNewComments = []; // the new array of all comments
192
193 15
        foreach ($aComments as $key => &$value) {
194
            // Any comments inside a deleted range will be ignored
195 13
            if (!self::cellAddressInDeleteRange($key, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)) {
196
                // Otherwise build a new array of comments indexed by the adjusted cell reference
197 13
                $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
198 13
                $aNewComments[$newReference] = $value;
199
            }
200
        }
201
        //    Replace the comments array with the new set of comments
202 15
        $pSheet->setComments($aNewComments);
203 15
    }
204
205
    /**
206
     * Update hyperlinks when inserting/deleting rows/columns.
207
     *
208
     * @param Worksheet $pSheet The worksheet that we're editing
209
     * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
210
     * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
211
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
212
     * @param int $beforeRow Number of the row we're inserting/deleting before
213
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
214
     */
215 15 View Code Duplication
    protected function adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
0 ignored issues
show
Unused Code introduced by
The parameter $beforeColumnIndex is not used and could be removed.

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

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

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

Loading history...
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...
216
    {
217 15
        $aHyperlinkCollection = $pSheet->getHyperlinkCollection();
218 15
        ($pNumCols > 0 || $pNumRows > 0) ?
219 15
            uksort($aHyperlinkCollection, ['self', 'cellReverseSort']) : uksort($aHyperlinkCollection, ['self', 'cellSort']);
220
221 15
        foreach ($aHyperlinkCollection as $key => $value) {
222 13
            $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
223 13
            if ($key != $newReference) {
224 13
                $pSheet->setHyperlink($newReference, $value);
225 13
                $pSheet->setHyperlink($key, null);
226
            }
227
        }
228 15
    }
229
230
    /**
231
     * Update data validations when inserting/deleting rows/columns.
232
     *
233
     * @param Worksheet $pSheet The worksheet that we're editing
234
     * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
235
     * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
236
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
237
     * @param int $beforeRow Number of the row we're inserting/deleting before
238
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
239
     */
240 15 View Code Duplication
    protected function adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
0 ignored issues
show
Unused Code introduced by
The parameter $beforeColumnIndex is not used and could be removed.

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

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

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

Loading history...
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...
241
    {
242 15
        $aDataValidationCollection = $pSheet->getDataValidationCollection();
243 15
        ($pNumCols > 0 || $pNumRows > 0) ?
244 15
            uksort($aDataValidationCollection, ['self', 'cellReverseSort']) : uksort($aDataValidationCollection, ['self', 'cellSort']);
245
246 15
        foreach ($aDataValidationCollection as $key => $value) {
247
            $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
248
            if ($key != $newReference) {
249
                $pSheet->setDataValidation($newReference, $value);
250
                $pSheet->setDataValidation($key, null);
251
            }
252
        }
253 15
    }
254
255
    /**
256
     * Update merged cells when inserting/deleting rows/columns.
257
     *
258
     * @param Worksheet $pSheet The worksheet that we're editing
259
     * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
260
     * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
261
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
262
     * @param int $beforeRow Number of the row we're inserting/deleting before
263
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
264
     */
265 15
    protected function adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
0 ignored issues
show
Unused Code introduced by
The parameter $beforeColumnIndex is not used and could be removed.

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

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

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

Loading history...
266
    {
267 15
        $aMergeCells = $pSheet->getMergeCells();
268 15
        $aNewMergeCells = []; // the new array of all merge cells
269 15
        foreach ($aMergeCells as $key => &$value) {
270 14
            $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
271 14
            $aNewMergeCells[$newReference] = $newReference;
272
        }
273 15
        $pSheet->setMergeCells($aNewMergeCells); // replace the merge cells array
274 15
    }
275
276
    /**
277
     * Update protected cells when inserting/deleting rows/columns.
278
     *
279
     * @param Worksheet $pSheet The worksheet that we're editing
280
     * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
281
     * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
282
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
283
     * @param int $beforeRow Number of the row we're inserting/deleting before
284
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
285
     */
286 15
    protected function adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
0 ignored issues
show
Unused Code introduced by
The parameter $beforeColumnIndex is not used and could be removed.

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

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

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

Loading history...
287
    {
288 15
        $aProtectedCells = $pSheet->getProtectedCells();
289 15
        ($pNumCols > 0 || $pNumRows > 0) ?
290 15
            uksort($aProtectedCells, ['self', 'cellReverseSort']) : uksort($aProtectedCells, ['self', 'cellSort']);
291 15
        foreach ($aProtectedCells as $key => $value) {
292 12
            $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
293 12
            if ($key != $newReference) {
294 12
                $pSheet->protectCells($newReference, $value, true);
295 12
                $pSheet->unprotectCells($key);
296
            }
297
        }
298 15
    }
299
300
    /**
301
     * Update column dimensions when inserting/deleting rows/columns.
302
     *
303
     * @param Worksheet $pSheet The worksheet that we're editing
304
     * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
305
     * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
306
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
307
     * @param int $beforeRow Number of the row we're inserting/deleting before
308
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
309
     */
310 15
    protected function adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
0 ignored issues
show
Unused Code introduced by
The parameter $beforeColumnIndex is not used and could be removed.

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

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

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

Loading history...
311
    {
312 15
        $aColumnDimensions = array_reverse($pSheet->getColumnDimensions(), true);
313 15
        if (!empty($aColumnDimensions)) {
314 15 View Code Duplication
            foreach ($aColumnDimensions as $objColumnDimension) {
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...
315 15
                $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $pBefore, $pNumCols, $pNumRows);
316 15
                list($newReference) = Coordinate::coordinateFromString($newReference);
317 15
                if ($objColumnDimension->getColumnIndex() != $newReference) {
318 15
                    $objColumnDimension->setColumnIndex($newReference);
319
                }
320
            }
321 15
            $pSheet->refreshColumnDimensions();
322
        }
323 15
    }
324
325
    /**
326
     * Update row dimensions when inserting/deleting rows/columns.
327
     *
328
     * @param Worksheet $pSheet The worksheet that we're editing
329
     * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
330
     * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
331
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
332
     * @param int $beforeRow Number of the row we're inserting/deleting before
333
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
334
     */
335 15
    protected function adjustRowDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
0 ignored issues
show
Unused Code introduced by
The parameter $beforeColumnIndex is not used and could be removed.

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

Loading history...
336
    {
337 15
        $aRowDimensions = array_reverse($pSheet->getRowDimensions(), true);
338 15
        if (!empty($aRowDimensions)) {
339 2 View Code Duplication
            foreach ($aRowDimensions as $objRowDimension) {
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...
340 2
                $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $pBefore, $pNumCols, $pNumRows);
341 2
                list(, $newReference) = Coordinate::coordinateFromString($newReference);
342 2
                if ($objRowDimension->getRowIndex() != $newReference) {
343 2
                    $objRowDimension->setRowIndex($newReference);
344
                }
345
            }
346 2
            $pSheet->refreshRowDimensions();
347
348 2
            $copyDimension = $pSheet->getRowDimension($beforeRow - 1);
349 2
            for ($i = $beforeRow; $i <= $beforeRow - 1 + $pNumRows; ++$i) {
350 1
                $newDimension = $pSheet->getRowDimension($i);
351 1
                $newDimension->setRowHeight($copyDimension->getRowHeight());
352 1
                $newDimension->setVisible($copyDimension->getVisible());
353 1
                $newDimension->setOutlineLevel($copyDimension->getOutlineLevel());
354 1
                $newDimension->setCollapsed($copyDimension->getCollapsed());
355
            }
356
        }
357 15
    }
358
359
    /**
360
     * Insert a new column or row, updating all possible related data.
361
     *
362
     * @param string $pBefore Insert before this cell address (e.g. 'A1')
363
     * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
364
     * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
365
     * @param Worksheet $pSheet The worksheet that we're editing
366
     *
367
     * @throws Exception
368
     */
369 15
    public function insertNewBefore($pBefore, $pNumCols, $pNumRows, Worksheet $pSheet)
370
    {
371 15
        $remove = ($pNumCols < 0 || $pNumRows < 0);
372 15
        $allCoordinates = $pSheet->getCoordinates();
373
374
        // Get coordinate of $pBefore
375 15
        list($beforeColumn, $beforeRow) = Coordinate::coordinateFromString($pBefore);
376 15
        $beforeColumnIndex = Coordinate::columnIndexFromString($beforeColumn);
377
378
        // Clear cells if we are removing columns or rows
379 15
        $highestColumn = $pSheet->getHighestColumn();
380 15
        $highestRow = $pSheet->getHighestRow();
381
382
        // 1. Clear column strips if we are removing columns
383 15 View Code Duplication
        if ($pNumCols < 0 && $beforeColumnIndex - 2 + $pNumCols > 0) {
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...
384 12
            for ($i = 1; $i <= $highestRow - 1; ++$i) {
385 12
                for ($j = $beforeColumnIndex - 1 + $pNumCols; $j <= $beforeColumnIndex - 2; ++$j) {
386 12
                    $coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
387 12
                    $pSheet->removeConditionalStyles($coordinate);
388 12
                    if ($pSheet->cellExists($coordinate)) {
389 12
                        $pSheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
390 12
                        $pSheet->getCell($coordinate)->setXfIndex(0);
391
                    }
392
                }
393
            }
394
        }
395
396
        // 2. Clear row strips if we are removing rows
397 15 View Code Duplication
        if ($pNumRows < 0 && $beforeRow - 1 + $pNumRows > 0) {
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...
398 14
            for ($i = $beforeColumnIndex - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) {
399 14
                for ($j = $beforeRow + $pNumRows; $j <= $beforeRow - 1; ++$j) {
400 14
                    $coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j;
401 14
                    $pSheet->removeConditionalStyles($coordinate);
402 14
                    if ($pSheet->cellExists($coordinate)) {
403 13
                        $pSheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
404 13
                        $pSheet->getCell($coordinate)->setXfIndex(0);
405
                    }
406
                }
407
            }
408
        }
409
410
        // Loop through cells, bottom-up, and change cell coordinate
411 15
        if ($remove) {
412
            // It's faster to reverse and pop than to use unshift, especially with large cell collections
413 15
            $allCoordinates = array_reverse($allCoordinates);
414
        }
415 15
        while ($coordinate = array_pop($allCoordinates)) {
416 15
            $cell = $pSheet->getCell($coordinate);
417 15
            $cellIndex = Coordinate::columnIndexFromString($cell->getColumn());
418
419 15
            if ($cellIndex - 1 + $pNumCols < 0) {
420 12
                continue;
421
            }
422
423
            // New coordinate
424 15
            $newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $pNumCols) . ($cell->getRow() + $pNumRows);
425
426
            // Should the cell be updated? Move value and cellXf index from one cell to another.
427 15
            if (($cellIndex >= $beforeColumnIndex) && ($cell->getRow() >= $beforeRow)) {
428
                // Update cell styles
429 15
                $pSheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex());
430
431
                // Insert this cell at its new location
432 15
                if ($cell->getDataType() == DataType::TYPE_FORMULA) {
433
                    // Formula should be adjusted
434 13
                    $pSheet->getCell($newCoordinate)
435 13
                            ->setValue($this->updateFormulaReferences($cell->getValue(), $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle()));
436
                } else {
437
                    // Formula should not be adjusted
438 15
                    $pSheet->getCell($newCoordinate)->setValue($cell->getValue());
439
                }
440
441
                // Clear the original cell
442 15
                $pSheet->getCellCollection()->delete($coordinate);
443
            } else {
444
                /*    We don't need to update styles for rows/columns before our insertion position,
445
                        but we do still need to adjust any formulae    in those cells                    */
446 15
                if ($cell->getDataType() == DataType::TYPE_FORMULA) {
447
                    // Formula should be adjusted
448 13
                    $cell->setValue($this->updateFormulaReferences($cell->getValue(), $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle()));
449
                }
450
            }
451
        }
452
453
        // Duplicate styles for the newly inserted cells
454 15
        $highestColumn = $pSheet->getHighestColumn();
455 15
        $highestRow = $pSheet->getHighestRow();
456
457 15
        if ($pNumCols > 0 && $beforeColumnIndex - 2 > 0) {
458 12
            for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
459
                // Style
460 12
                $coordinate = Coordinate::stringFromColumnIndex($beforeColumnIndex - 1) . $i;
461 12 View Code Duplication
                if ($pSheet->cellExists($coordinate)) {
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...
462 12
                    $xfIndex = $pSheet->getCell($coordinate)->getXfIndex();
463 12
                    $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ?
464 12
                        $pSheet->getConditionalStyles($coordinate) : false;
465 12
                    for ($j = $beforeColumnIndex; $j <= $beforeColumnIndex - 1 + $pNumCols; ++$j) {
466 12
                        $pSheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
467 12
                        if ($conditionalStyles) {
468
                            $cloned = [];
469
                            foreach ($conditionalStyles as $conditionalStyle) {
470
                                $cloned[] = clone $conditionalStyle;
471
                            }
472
                            $pSheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned);
473
                        }
474
                    }
475
                }
476
            }
477
        }
478
479 15
        if ($pNumRows > 0 && $beforeRow - 1 > 0) {
480 13
            for ($i = $beforeColumnIndex; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) {
481
                // Style
482 13
                $coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
483 13 View Code Duplication
                if ($pSheet->cellExists($coordinate)) {
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...
484 13
                    $xfIndex = $pSheet->getCell($coordinate)->getXfIndex();
485 13
                    $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ?
486 13
                        $pSheet->getConditionalStyles($coordinate) : false;
487 13
                    for ($j = $beforeRow; $j <= $beforeRow - 1 + $pNumRows; ++$j) {
488 13
                        $pSheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
489 13
                        if ($conditionalStyles) {
490
                            $cloned = [];
491
                            foreach ($conditionalStyles as $conditionalStyle) {
492
                                $cloned[] = clone $conditionalStyle;
493
                            }
494
                            $pSheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned);
495
                        }
496
                    }
497
                }
498
            }
499
        }
500
501
        // Update worksheet: column dimensions
502 15
        $this->adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
503
504
        // Update worksheet: row dimensions
505 15
        $this->adjustRowDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
506
507
        //    Update worksheet: page breaks
508 15
        $this->adjustPageBreaks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
509
510
        //    Update worksheet: comments
511 15
        $this->adjustComments($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
512
513
        // Update worksheet: hyperlinks
514 15
        $this->adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
515
516
        // Update worksheet: data validations
517 15
        $this->adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
518
519
        // Update worksheet: merge cells
520 15
        $this->adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
521
522
        // Update worksheet: protected cells
523 15
        $this->adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
524
525
        // Update worksheet: autofilter
526 15
        $autoFilter = $pSheet->getAutoFilter();
527 15
        $autoFilterRange = $autoFilter->getRange();
528 15
        if (!empty($autoFilterRange)) {
529
            if ($pNumCols != 0) {
530
                $autoFilterColumns = $autoFilter->getColumns();
531
                if (count($autoFilterColumns) > 0) {
532
                    sscanf($pBefore, '%[A-Z]%d', $column, $row);
0 ignored issues
show
Bug introduced by
The variable $row does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
533
                    $columnIndex = Coordinate::columnIndexFromString($column);
534
                    list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($autoFilterRange);
535
                    if ($columnIndex <= $rangeEnd[0]) {
536
                        if ($pNumCols < 0) {
537
                            //    If we're actually deleting any columns that fall within the autofilter range,
538
                            //        then we delete any rules for those columns
539
                            $deleteColumn = $columnIndex + $pNumCols - 1;
540
                            $deleteCount = abs($pNumCols);
541
                            for ($i = 1; $i <= $deleteCount; ++$i) {
542
                                if (isset($autoFilterColumns[Coordinate::stringFromColumnIndex($deleteColumn + 1)])) {
543
                                    $autoFilter->clearColumn(Coordinate::stringFromColumnIndex($deleteColumn + 1));
544
                                }
545
                                ++$deleteColumn;
546
                            }
547
                        }
548
                        $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
549
550
                        //    Shuffle columns in autofilter range
551
                        if ($pNumCols > 0) {
552
                            $startColRef = $startCol;
553
                            $endColRef = $rangeEnd[0];
554
                            $toColRef = $rangeEnd[0] + $pNumCols;
555
556
                            do {
557
                                $autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
558
                                --$endColRef;
559
                                --$toColRef;
560
                            } while ($startColRef <= $endColRef);
561
                        } else {
562
                            //    For delete, we shuffle from beginning to end to avoid overwriting
563
                            $startColID = Coordinate::stringFromColumnIndex($startCol);
564
                            $toColID = Coordinate::stringFromColumnIndex($startCol + $pNumCols);
565
                            $endColID = Coordinate::stringFromColumnIndex($rangeEnd[0] + 1);
566
                            do {
567
                                $autoFilter->shiftColumn($startColID, $toColID);
568
                                ++$startColID;
569
                                ++$toColID;
570
                            } while ($startColID != $endColID);
571
                        }
572
                    }
573
                }
574
            }
575
            $pSheet->setAutoFilter($this->updateCellReference($autoFilterRange, $pBefore, $pNumCols, $pNumRows));
576
        }
577
578
        // Update worksheet: freeze pane
579 15
        if ($pSheet->getFreezePane() != '') {
580 1
            $pSheet->freezePane($this->updateCellReference($pSheet->getFreezePane(), $pBefore, $pNumCols, $pNumRows));
581
        }
582
583
        // Page setup
584 15
        if ($pSheet->getPageSetup()->isPrintAreaSet()) {
585
            $pSheet->getPageSetup()->setPrintArea($this->updateCellReference($pSheet->getPageSetup()->getPrintArea(), $pBefore, $pNumCols, $pNumRows));
586
        }
587
588
        // Update worksheet: drawings
589 15
        $aDrawings = $pSheet->getDrawingCollection();
590 15
        foreach ($aDrawings as $objDrawing) {
591 13
            $newReference = $this->updateCellReference($objDrawing->getCoordinates(), $pBefore, $pNumCols, $pNumRows);
592 13
            if ($objDrawing->getCoordinates() != $newReference) {
593 13
                $objDrawing->setCoordinates($newReference);
594
            }
595
        }
596
597
        // Update workbook: named ranges
598 15
        if (count($pSheet->getParent()->getNamedRanges()) > 0) {
599 1
            foreach ($pSheet->getParent()->getNamedRanges() as $namedRange) {
600 1
                if ($namedRange->getWorksheet()->getHashCode() == $pSheet->getHashCode()) {
601 1
                    $namedRange->setRange($this->updateCellReference($namedRange->getRange(), $pBefore, $pNumCols, $pNumRows));
602
                }
603
            }
604
        }
605
606
        // Garbage collect
607 15
        $pSheet->garbageCollect();
608 15
    }
609
610
    /**
611
     * Update references within formulas.
612
     *
613
     * @param string $pFormula Formula to update
614
     * @param int $pBefore Insert before this one
615
     * @param int $pNumCols Number of columns to insert
616
     * @param int $pNumRows Number of rows to insert
617
     * @param string $sheetName Worksheet name/title
618
     *
619
     * @throws Exception
620
     *
621
     * @return string Updated formula
622
     */
623 16
    public function updateFormulaReferences($pFormula = '', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0, $sheetName = '')
624
    {
625
        //    Update cell references in the formula
626 16
        $formulaBlocks = explode('"', $pFormula);
627 16
        $i = false;
628 16
        foreach ($formulaBlocks as &$formulaBlock) {
629
            //    Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode)
630 16
            if ($i = !$i) {
631 16
                $adjustCount = 0;
632 16
                $newCellTokens = $cellTokens = [];
633
                //    Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5)
0 ignored issues
show
Unused Code Comprehensibility introduced by
41% 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...
634 16
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
635 16
                if ($matchCount > 0) {
636
                    foreach ($matches as $match) {
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> 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...
637
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
638
                        $fromString .= $match[3] . ':' . $match[4];
639
                        $modified3 = substr($this->updateCellReference('$A' . $match[3], $pBefore, $pNumCols, $pNumRows), 2);
640
                        $modified4 = substr($this->updateCellReference('$A' . $match[4], $pBefore, $pNumCols, $pNumRows), 2);
641
642 View Code Duplication
                        if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
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...
643
                            if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
644
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
645
                                $toString .= $modified3 . ':' . $modified4;
646
                                //    Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
647
                                $column = 100000;
648
                                $row = 10000000 + trim($match[3], '$');
649
                                $cellIndex = $column . $row;
650
651
                                $newCellTokens[$cellIndex] = preg_quote($toString);
652
                                $cellTokens[$cellIndex] = '/(?<!\d\$\!)' . preg_quote($fromString) . '(?!\d)/i';
653
                                ++$adjustCount;
654
                            }
655
                        }
656
                    }
657
                }
658
                //    Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E)
659 16
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
660 16
                if ($matchCount > 0) {
661
                    foreach ($matches as $match) {
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> 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...
662
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
663
                        $fromString .= $match[3] . ':' . $match[4];
664
                        $modified3 = substr($this->updateCellReference($match[3] . '$1', $pBefore, $pNumCols, $pNumRows), 0, -2);
665
                        $modified4 = substr($this->updateCellReference($match[4] . '$1', $pBefore, $pNumCols, $pNumRows), 0, -2);
666
667 View Code Duplication
                        if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
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...
668
                            if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
669
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
670
                                $toString .= $modified3 . ':' . $modified4;
671
                                //    Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
672
                                $column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000;
673
                                $row = 10000000;
674
                                $cellIndex = $column . $row;
675
676
                                $newCellTokens[$cellIndex] = preg_quote($toString);
677
                                $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString) . '(?![A-Z])/i';
678
                                ++$adjustCount;
679
                            }
680
                        }
681
                    }
682
                }
683
                //    Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5)
684 16
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
685 16
                if ($matchCount > 0) {
686 14
                    foreach ($matches as $match) {
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> 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...
687 14
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
688 14
                        $fromString .= $match[3] . ':' . $match[4];
689 14
                        $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows);
690 14
                        $modified4 = $this->updateCellReference($match[4], $pBefore, $pNumCols, $pNumRows);
691
692 14
                        if ($match[3] . $match[4] !== $modified3 . $modified4) {
693 14 View Code Duplication
                            if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
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...
694 14
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
695 14
                                $toString .= $modified3 . ':' . $modified4;
696 14
                                list($column, $row) = Coordinate::coordinateFromString($match[3]);
697
                                //    Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
698 14
                                $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
699 14
                                $row = trim($row, '$') + 10000000;
700 14
                                $cellIndex = $column . $row;
701
702 14
                                $newCellTokens[$cellIndex] = preg_quote($toString);
703 14
                                $cellTokens[$cellIndex] = '/(?<![A-Z]\$\!)' . preg_quote($fromString) . '(?!\d)/i';
704 14
                                ++$adjustCount;
705
                            }
706
                        }
707
                    }
708
                }
709
                //    Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5)
710 16
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
711
712 16
                if ($matchCount > 0) {
713 16
                    foreach ($matches as $match) {
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> 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...
714 16
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
715 16
                        $fromString .= $match[3];
716
717 16
                        $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows);
718 16 View Code Duplication
                        if ($match[3] !== $modified3) {
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...
719 16
                            if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
720 16
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
721 16
                                $toString .= $modified3;
722 16
                                list($column, $row) = Coordinate::coordinateFromString($match[3]);
723
                                //    Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
724 16
                                $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
725 16
                                $row = trim($row, '$') + 10000000;
726 16
                                $cellIndex = $row . $column;
727
728 16
                                $newCellTokens[$cellIndex] = preg_quote($toString);
729 16
                                $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString) . '(?!\d)/i';
730 16
                                ++$adjustCount;
731
                            }
732
                        }
733
                    }
734
                }
735 16
                if ($adjustCount > 0) {
736 16
                    if ($pNumCols > 0 || $pNumRows > 0) {
737 16
                        krsort($cellTokens);
738 16
                        krsort($newCellTokens);
739
                    } else {
740 13
                        ksort($cellTokens);
741 13
                        ksort($newCellTokens);
742
                    }   //  Update cell references in the formula
743 16
                    $formulaBlock = str_replace('\\', '', preg_replace($cellTokens, $newCellTokens, $formulaBlock));
744
                }
745
            }
746
        }
747 16
        unset($formulaBlock);
748
749
        //    Then rebuild the formula string
750 16
        return implode('"', $formulaBlocks);
751
    }
752
753
    /**
754
     * Update cell reference.
755
     *
756
     * @param string $pCellRange Cell range
757
     * @param int $pBefore Insert before this one
758
     * @param int $pNumCols Number of columns to increment
759
     * @param int $pNumRows Number of rows to increment
760
     *
761
     * @throws Exception
762
     *
763
     * @return string Updated cell range
764
     */
765 18
    public function updateCellReference($pCellRange = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
766
    {
767
        // Is it in another worksheet? Will not have to update anything.
768 18
        if (strpos($pCellRange, '!') !== false) {
769
            return $pCellRange;
770
            // Is it a range or a single cell?
771 18
        } elseif (strpos($pCellRange, ':') === false && strpos($pCellRange, ',') === false) {
772
            // Single cell
773 18
            return $this->updateSingleCellReference($pCellRange, $pBefore, $pNumCols, $pNumRows);
774 14
        } elseif (strpos($pCellRange, ':') !== false || strpos($pCellRange, ',') !== false) {
775
            // Range
776 14
            return $this->updateCellRange($pCellRange, $pBefore, $pNumCols, $pNumRows);
777
        }
778
        // Return original
779
        return $pCellRange;
780
    }
781
782
    /**
783
     * Update named formulas (i.e. containing worksheet references / named ranges).
784
     *
785
     * @param Spreadsheet $spreadsheet Object to update
786
     * @param string $oldName Old name (name to replace)
787
     * @param string $newName New name
788
     */
789 31
    public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $newName = '')
790
    {
791 31
        if ($oldName == '') {
792
            return;
793
        }
794
795 31
        foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
796 31
            foreach ($sheet->getCoordinates(false) as $coordinate) {
797 29
                $cell = $sheet->getCell($coordinate);
798 29
                if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) {
799 20
                    $formula = $cell->getValue();
800 20
                    if (strpos($formula, $oldName) !== false) {
801
                        $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
802
                        $formula = str_replace($oldName . '!', $newName . '!', $formula);
803 31
                        $cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
804
                    }
805
                }
806
            }
807
        }
808 31
    }
809
810
    /**
811
     * Update cell range.
812
     *
813
     * @param string $pCellRange Cell range    (e.g. 'B2:D4', 'B:C' or '2:3')
814
     * @param int $pBefore Insert before this one
815
     * @param int $pNumCols Number of columns to increment
816
     * @param int $pNumRows Number of rows to increment
817
     *
818
     * @throws Exception
819
     *
820
     * @return string Updated cell range
821
     */
822 14
    private function updateCellRange($pCellRange = 'A1:A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
823
    {
824 14
        if (strpos($pCellRange, ':') !== false || strpos($pCellRange, ',') !== false) {
825
            // Update range
826 14
            $range = Coordinate::splitRange($pCellRange);
827 14
            $ic = count($range);
828 14
            for ($i = 0; $i < $ic; ++$i) {
829 14
                $jc = count($range[$i]);
830 14
                for ($j = 0; $j < $jc; ++$j) {
831 14
                    if (ctype_alpha($range[$i][$j])) {
832
                        $r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', $pBefore, $pNumCols, $pNumRows));
833
                        $range[$i][$j] = $r[0];
834 14
                    } elseif (ctype_digit($range[$i][$j])) {
835
                        $r = Coordinate::coordinateFromString($this->updateSingleCellReference('A' . $range[$i][$j], $pBefore, $pNumCols, $pNumRows));
836
                        $range[$i][$j] = $r[1];
837
                    } else {
838 14
                        $range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $pBefore, $pNumCols, $pNumRows);
839
                    }
840
                }
841
            }
842
843
            // Recreate range string
844 14
            return Coordinate::buildRange($range);
845
        }
846
847
        throw new Exception('Only cell ranges may be passed to this method.');
848
    }
849
850
    /**
851
     * Update single cell reference.
852
     *
853
     * @param string $pCellReference Single cell reference
854
     * @param int $pBefore Insert before this one
855
     * @param int $pNumCols Number of columns to increment
856
     * @param int $pNumRows Number of rows to increment
857
     *
858
     * @throws Exception
859
     *
860
     * @return string Updated cell reference
861
     */
862 18
    private function updateSingleCellReference($pCellReference = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
863
    {
864 18
        if (strpos($pCellReference, ':') === false && strpos($pCellReference, ',') === false) {
865
            // Get coordinate of $pBefore
866 18
            list($beforeColumn, $beforeRow) = Coordinate::coordinateFromString($pBefore);
867
868
            // Get coordinate of $pCellReference
869 18
            list($newColumn, $newRow) = Coordinate::coordinateFromString($pCellReference);
870
871
            // Verify which parts should be updated
872 18
            $updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn)));
873 18
            $updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow);
874
875
            // Create new column reference
876 18
            if ($updateColumn) {
877 18
                $newColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($newColumn) + $pNumCols);
878
            }
879
880
            // Create new row reference
881 18
            if ($updateRow) {
882 17
                $newRow = $newRow + $pNumRows;
883
            }
884
885
            // Return new reference
886 18
            return $newColumn . $newRow;
887
        }
888
889
        throw new Exception('Only single cell references may be passed to this method.');
890
    }
891
892
    /**
893
     * __clone implementation. Cloning should not be allowed in a Singleton!
894
     *
895
     * @throws Exception
896
     */
897
    final public function __clone()
898
    {
899
        throw new Exception('Cloning a Singleton is not allowed!');
900
    }
901
}
902