Failed Conditions
Push — develop ( eb5856...11b055 )
by Adrien
31:22
created

ReferenceHelper::insertNewBefore()   F

Complexity

Conditions 53
Paths > 20000

Size

Total Lines 245
Code Lines 134

Duplication

Lines 54
Ratio 22.04 %

Code Coverage

Tests 90
CRAP Score 134.834

Importance

Changes 0
Metric Value
cc 53
eloc 134
c 0
b 0
f 0
nc 110592
nop 4
dl 54
loc 245
rs 2
ccs 90
cts 130
cp 0.6923
crap 134.834

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 49
    public static function getInstance()
31
    {
32 49 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 49
        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);
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable $ar seems to be never defined.
Loading history...
86 13
        sscanf($b, '%[A-Z]%d', $bc, $br);
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable $br seems to be never defined.
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);
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable $ar seems to be never defined.
Loading history...
107 13
        sscanf($b, '%[A-Z]%d', $bc, $br);
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable $br seems to be never defined.
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);
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
                $newReference = $this->updateCellReference($key, /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
170
                if ($key != $newReference) {
171
                    $pSheet->setBreak($newReference, $value)
0 ignored issues
show
Bug introduced by
$value of type array is incompatible with the type integer expected by parameter $pBreak of PhpOffice\PhpSpreadsheet...t\Worksheet::setBreak(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

171
                    $pSheet->setBreak($newReference, /** @scrutinizer ignore-type */ $value)
Loading history...
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);
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

197
                $newReference = $this->updateCellReference($key, /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
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
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...
Unused Code introduced by
The parameter $beforeColumnIndex is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

215
    protected function adjustHyperlinks($pSheet, $pBefore, /** @scrutinizer ignore-unused */ $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)

This check looks for 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. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

215
    protected function adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, /** @scrutinizer ignore-unused */ $beforeRow, $pNumRows)

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

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);
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

222
            $newReference = $this->updateCellReference($key, /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
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
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...
Unused Code introduced by
The parameter $beforeRow is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

240
    protected function adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, /** @scrutinizer ignore-unused */ $beforeRow, $pNumRows)

This check looks for 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 $beforeColumnIndex is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

240
    protected function adjustDataValidations($pSheet, $pBefore, /** @scrutinizer ignore-unused */ $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)

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

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);
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

247
            $newReference = $this->updateCellReference($key, /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
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. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

265
    protected function adjustMergeCells($pSheet, $pBefore, /** @scrutinizer ignore-unused */ $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)

This check looks for 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. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

265
    protected function adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, /** @scrutinizer ignore-unused */ $beforeRow, $pNumRows)

This check looks for 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);
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

270
            $newReference = $this->updateCellReference($key, /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
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 $beforeRow is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

286
    protected function adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, /** @scrutinizer ignore-unused */ $beforeRow, $pNumRows)

This check looks for 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 $beforeColumnIndex is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

286
    protected function adjustProtectedCells($pSheet, $pBefore, /** @scrutinizer ignore-unused */ $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)

This check looks for 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);
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

292
            $newReference = $this->updateCellReference($key, /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
293 12
            if ($key != $newReference) {
294 12
                $pSheet->protectCells($newReference, $value, true);
0 ignored issues
show
Bug introduced by
$value of type array is incompatible with the type string expected by parameter $pPassword of PhpOffice\PhpSpreadsheet...rksheet::protectCells(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

294
                $pSheet->protectCells($newReference, /** @scrutinizer ignore-type */ $value, true);
Loading history...
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. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

310
    protected function adjustColumnDimensions($pSheet, $pBefore, /** @scrutinizer ignore-unused */ $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)

This check looks for 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. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

310
    protected function adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, /** @scrutinizer ignore-unused */ $beforeRow, $pNumRows)

This check looks for 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);
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

315
                $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
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. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

335
    protected function adjustRowDimensions($pSheet, $pBefore, /** @scrutinizer ignore-unused */ $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)

This check looks for 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);
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

340
                $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
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()));
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...dateFormulaReferences(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

435
                            ->setValue($this->updateFormulaReferences($cell->getValue(), /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle()));
Loading history...
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
Comprehensibility Best Practice introduced by
The variable $row seems to be never defined.
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));
0 ignored issues
show
Bug introduced by
$pBefore of type string is incompatible with the type integer expected by parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

575
            $pSheet->setAutoFilter($this->updateCellReference($autoFilterRange, /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows));
Loading history...
576
        }
577
578
        // Update worksheet: freeze pane
579 15
        if ($pSheet->getFreezePane()) {
580 1
            $splitCell = $pSheet->getFreezePane();
581 1
            $topLeftCell = $pSheet->getTopLeftCell();
582
583 1
            $splitCell = $this->updateCellReference($splitCell, $pBefore, $pNumCols, $pNumRows);
584 1
            $topLeftCell = $this->updateCellReference($topLeftCell, $pBefore, $pNumCols, $pNumRows);
585
586 1
            $pSheet->freezePane($splitCell, $topLeftCell);
587
        }
588
589
        // Page setup
590 15
        if ($pSheet->getPageSetup()->isPrintAreaSet()) {
591
            $pSheet->getPageSetup()->setPrintArea($this->updateCellReference($pSheet->getPageSetup()->getPrintArea(), $pBefore, $pNumCols, $pNumRows));
592
        }
593
594
        // Update worksheet: drawings
595 15
        $aDrawings = $pSheet->getDrawingCollection();
596 15
        foreach ($aDrawings as $objDrawing) {
597 13
            $newReference = $this->updateCellReference($objDrawing->getCoordinates(), $pBefore, $pNumCols, $pNumRows);
598 13
            if ($objDrawing->getCoordinates() != $newReference) {
599 13
                $objDrawing->setCoordinates($newReference);
600
            }
601
        }
602
603
        // Update workbook: named ranges
604 15
        if (count($pSheet->getParent()->getNamedRanges()) > 0) {
605 1
            foreach ($pSheet->getParent()->getNamedRanges() as $namedRange) {
606 1
                if ($namedRange->getWorksheet()->getHashCode() == $pSheet->getHashCode()) {
607 1
                    $namedRange->setRange($this->updateCellReference($namedRange->getRange(), $pBefore, $pNumCols, $pNumRows));
608
                }
609
            }
610
        }
611
612
        // Garbage collect
613 15
        $pSheet->garbageCollect();
614 15
    }
615
616
    /**
617
     * Update references within formulas.
618
     *
619
     * @param string $pFormula Formula to update
620
     * @param int $pBefore Insert before this one
621
     * @param int $pNumCols Number of columns to insert
622
     * @param int $pNumRows Number of rows to insert
623
     * @param string $sheetName Worksheet name/title
624
     *
625
     * @throws Exception
626
     *
627
     * @return string Updated formula
628
     */
629 16
    public function updateFormulaReferences($pFormula = '', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0, $sheetName = '')
630
    {
631
        //    Update cell references in the formula
632 16
        $formulaBlocks = explode('"', $pFormula);
633 16
        $i = false;
634 16
        foreach ($formulaBlocks as &$formulaBlock) {
635
            //    Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode)
636 16
            if ($i = !$i) {
637 16
                $adjustCount = 0;
638 16
                $newCellTokens = $cellTokens = [];
639
                //    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...
640 16
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
641 16
                if ($matchCount > 0) {
642
                    foreach ($matches as $match) {
643
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
644
                        $fromString .= $match[3] . ':' . $match[4];
645
                        $modified3 = substr($this->updateCellReference('$A' . $match[3], $pBefore, $pNumCols, $pNumRows), 2);
1 ignored issue
show
Bug introduced by
It seems like $pBefore can also be of type string; however, parameter $pBefore of PhpOffice\PhpSpreadsheet...::updateCellReference() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

645
                        $modified3 = substr($this->updateCellReference('$A' . $match[3], /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows), 2);
Loading history...
646
                        $modified4 = substr($this->updateCellReference('$A' . $match[4], $pBefore, $pNumCols, $pNumRows), 2);
647
648 View Code Duplication
                        if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
2 ignored issues
show
Bug introduced by
Are you sure $modified4 of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

648
                        if ($match[3] . ':' . $match[4] !== $modified3 . ':' . /** @scrutinizer ignore-type */ $modified4) {
Loading history...
Bug introduced by
Are you sure $modified3 of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

648
                        if ($match[3] . ':' . $match[4] !== /** @scrutinizer ignore-type */ $modified3 . ':' . $modified4) {
Loading history...
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...
649
                            if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
650
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
651
                                $toString .= $modified3 . ':' . $modified4;
652
                                //    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
653
                                $column = 100000;
654
                                $row = 10000000 + trim($match[3], '$');
655
                                $cellIndex = $column . $row;
656
657
                                $newCellTokens[$cellIndex] = preg_quote($toString);
658
                                $cellTokens[$cellIndex] = '/(?<!\d\$\!)' . preg_quote($fromString) . '(?!\d)/i';
659
                                ++$adjustCount;
660
                            }
661
                        }
662
                    }
663
                }
664
                //    Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E)
665 16
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
666 16
                if ($matchCount > 0) {
667
                    foreach ($matches as $match) {
668
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
669
                        $fromString .= $match[3] . ':' . $match[4];
670
                        $modified3 = substr($this->updateCellReference($match[3] . '$1', $pBefore, $pNumCols, $pNumRows), 0, -2);
671
                        $modified4 = substr($this->updateCellReference($match[4] . '$1', $pBefore, $pNumCols, $pNumRows), 0, -2);
672
673 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...
674
                            if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
675
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
676
                                $toString .= $modified3 . ':' . $modified4;
677
                                //    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
678
                                $column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000;
679
                                $row = 10000000;
680
                                $cellIndex = $column . $row;
681
682
                                $newCellTokens[$cellIndex] = preg_quote($toString);
683
                                $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString) . '(?![A-Z])/i';
684
                                ++$adjustCount;
685
                            }
686
                        }
687
                    }
688
                }
689
                //    Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5)
690 16
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
691 16
                if ($matchCount > 0) {
692 14
                    foreach ($matches as $match) {
693 14
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
694 14
                        $fromString .= $match[3] . ':' . $match[4];
695 14
                        $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows);
696 14
                        $modified4 = $this->updateCellReference($match[4], $pBefore, $pNumCols, $pNumRows);
697
698 14
                        if ($match[3] . $match[4] !== $modified3 . $modified4) {
699 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...
700 14
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
701 14
                                $toString .= $modified3 . ':' . $modified4;
702 14
                                list($column, $row) = Coordinate::coordinateFromString($match[3]);
703
                                //    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
704 14
                                $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
705 14
                                $row = trim($row, '$') + 10000000;
706 14
                                $cellIndex = $column . $row;
707
708 14
                                $newCellTokens[$cellIndex] = preg_quote($toString);
709 14
                                $cellTokens[$cellIndex] = '/(?<![A-Z]\$\!)' . preg_quote($fromString) . '(?!\d)/i';
710 14
                                ++$adjustCount;
711
                            }
712
                        }
713
                    }
714
                }
715
                //    Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5)
716 16
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
717
718 16
                if ($matchCount > 0) {
719 16
                    foreach ($matches as $match) {
720 16
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
721 16
                        $fromString .= $match[3];
722
723 16
                        $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows);
724 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...
725 16
                            if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
726 16
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
727 16
                                $toString .= $modified3;
728 16
                                list($column, $row) = Coordinate::coordinateFromString($match[3]);
729
                                //    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
730 16
                                $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
731 16
                                $row = trim($row, '$') + 10000000;
732 16
                                $cellIndex = $row . $column;
733
734 16
                                $newCellTokens[$cellIndex] = preg_quote($toString);
735 16
                                $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString) . '(?!\d)/i';
736 16
                                ++$adjustCount;
737
                            }
738
                        }
739
                    }
740
                }
741 16
                if ($adjustCount > 0) {
742 16
                    if ($pNumCols > 0 || $pNumRows > 0) {
743 16
                        krsort($cellTokens);
744 16
                        krsort($newCellTokens);
745
                    } else {
746 13
                        ksort($cellTokens);
747 13
                        ksort($newCellTokens);
748
                    }   //  Update cell references in the formula
749 16
                    $formulaBlock = str_replace('\\', '', preg_replace($cellTokens, $newCellTokens, $formulaBlock));
750
                }
751
            }
752
        }
753 16
        unset($formulaBlock);
754
755
        //    Then rebuild the formula string
756 16
        return implode('"', $formulaBlocks);
757
    }
758
759
    /**
760
     * Update cell reference.
761
     *
762
     * @param string $pCellRange Cell range
763
     * @param int $pBefore Insert before this one
764
     * @param int $pNumCols Number of columns to increment
765
     * @param int $pNumRows Number of rows to increment
766
     *
767
     * @throws Exception
768
     *
769
     * @return string Updated cell range
770
     */
771 18
    public function updateCellReference($pCellRange = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
772
    {
773
        // Is it in another worksheet? Will not have to update anything.
774 18
        if (strpos($pCellRange, '!') !== false) {
775
            return $pCellRange;
776
            // Is it a range or a single cell?
777 18
        } elseif (strpos($pCellRange, ':') === false && strpos($pCellRange, ',') === false) {
778
            // Single cell
779 18
            return $this->updateSingleCellReference($pCellRange, $pBefore, $pNumCols, $pNumRows);
1 ignored issue
show
Bug introduced by
It seems like $pBefore can also be of type string; however, parameter $pBefore of PhpOffice\PhpSpreadsheet...teSingleCellReference() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

779
            return $this->updateSingleCellReference($pCellRange, /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
780 14
        } elseif (strpos($pCellRange, ':') !== false || strpos($pCellRange, ',') !== false) {
781
            // Range
782 14
            return $this->updateCellRange($pCellRange, $pBefore, $pNumCols, $pNumRows);
1 ignored issue
show
Bug introduced by
It seems like $pBefore can also be of type string; however, parameter $pBefore of PhpOffice\PhpSpreadsheet...lper::updateCellRange() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

782
            return $this->updateCellRange($pCellRange, /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows);
Loading history...
783
        }
784
        // Return original
785
        return $pCellRange;
786
    }
787
788
    /**
789
     * Update named formulas (i.e. containing worksheet references / named ranges).
790
     *
791
     * @param Spreadsheet $spreadsheet Object to update
792
     * @param string $oldName Old name (name to replace)
793
     * @param string $newName New name
794
     */
795 31
    public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $newName = '')
796
    {
797 31
        if ($oldName == '') {
798
            return;
799
        }
800
801 31
        foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
802 31
            foreach ($sheet->getCoordinates(false) as $coordinate) {
803 29
                $cell = $sheet->getCell($coordinate);
804 29
                if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) {
805 20
                    $formula = $cell->getValue();
806 20
                    if (strpos($formula, $oldName) !== false) {
807
                        $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
808
                        $formula = str_replace($oldName . '!', $newName . '!', $formula);
809 31
                        $cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
810
                    }
811
                }
812
            }
813
        }
814 31
    }
815
816
    /**
817
     * Update cell range.
818
     *
819
     * @param string $pCellRange Cell range    (e.g. 'B2:D4', 'B:C' or '2:3')
820
     * @param int $pBefore Insert before this one
821
     * @param int $pNumCols Number of columns to increment
822
     * @param int $pNumRows Number of rows to increment
823
     *
824
     * @throws Exception
825
     *
826
     * @return string Updated cell range
827
     */
828 14
    private function updateCellRange($pCellRange = 'A1:A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
829
    {
830 14
        if (strpos($pCellRange, ':') !== false || strpos($pCellRange, ',') !== false) {
831
            // Update range
832 14
            $range = Coordinate::splitRange($pCellRange);
833 14
            $ic = count($range);
834 14
            for ($i = 0; $i < $ic; ++$i) {
835 14
                $jc = count($range[$i]);
836 14
                for ($j = 0; $j < $jc; ++$j) {
837 14
                    if (ctype_alpha($range[$i][$j])) {
838
                        $r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', $pBefore, $pNumCols, $pNumRows));
1 ignored issue
show
Bug introduced by
It seems like $pBefore can also be of type string; however, parameter $pBefore of PhpOffice\PhpSpreadsheet...teSingleCellReference() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

838
                        $r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', /** @scrutinizer ignore-type */ $pBefore, $pNumCols, $pNumRows));
Loading history...
839
                        $range[$i][$j] = $r[0];
840 14
                    } elseif (ctype_digit($range[$i][$j])) {
841
                        $r = Coordinate::coordinateFromString($this->updateSingleCellReference('A' . $range[$i][$j], $pBefore, $pNumCols, $pNumRows));
842
                        $range[$i][$j] = $r[1];
843
                    } else {
844 14
                        $range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $pBefore, $pNumCols, $pNumRows);
845
                    }
846
                }
847
            }
848
849
            // Recreate range string
850 14
            return Coordinate::buildRange($range);
851
        }
852
853
        throw new Exception('Only cell ranges may be passed to this method.');
854
    }
855
856
    /**
857
     * Update single cell reference.
858
     *
859
     * @param string $pCellReference Single cell reference
860
     * @param int $pBefore Insert before this one
861
     * @param int $pNumCols Number of columns to increment
862
     * @param int $pNumRows Number of rows to increment
863
     *
864
     * @throws Exception
865
     *
866
     * @return string Updated cell reference
867
     */
868 18
    private function updateSingleCellReference($pCellReference = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
869
    {
870 18
        if (strpos($pCellReference, ':') === false && strpos($pCellReference, ',') === false) {
871
            // Get coordinate of $pBefore
872 18
            list($beforeColumn, $beforeRow) = Coordinate::coordinateFromString($pBefore);
873
874
            // Get coordinate of $pCellReference
875 18
            list($newColumn, $newRow) = Coordinate::coordinateFromString($pCellReference);
876
877
            // Verify which parts should be updated
878 18
            $updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn)));
879 18
            $updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow);
880
881
            // Create new column reference
882 18
            if ($updateColumn) {
883 18
                $newColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($newColumn) + $pNumCols);
884
            }
885
886
            // Create new row reference
887 18
            if ($updateRow) {
888 17
                $newRow = $newRow + $pNumRows;
889
            }
890
891
            // Return new reference
892 18
            return $newColumn . $newRow;
893
        }
894
895
        throw new Exception('Only single cell references may be passed to this method.');
896
    }
897
898
    /**
899
     * __clone implementation. Cloning should not be allowed in a Singleton!
900
     *
901
     * @throws Exception
902
     */
903
    final public function __clone()
904
    {
905
        throw new Exception('Cloning a Singleton is not allowed!');
906
    }
907
}
908