Completed
Push — develop ( 4e0344...49775b )
by Adrien
27:07
created

ReferenceHelper::adjustProtectedCells()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 12
nop 6
dl 0
loc 10
ccs 9
cts 9
cp 1
crap 5
rs 8.8571
c 0
b 0
f 0
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 59
    public static function getInstance()
31
    {
32 59
        if (!isset(self::$instance) || (self::$instance === null)) {
33 40
            self::$instance = new self();
34
        }
35
36 59
        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
    public static function cellSort($a, $b)
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
    public static function cellReverseSort($a, $b)
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);
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);
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
    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. ( 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);
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
    protected function adjustDataValidations($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

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);
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);
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);
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
            foreach ($aColumnDimensions as $objColumnDimension) {
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. ( 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
            foreach ($aRowDimensions as $objRowDimension) {
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
        if ($pNumCols < 0 && $beforeColumnIndex - 2 + $pNumCols > 0) {
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
        if ($pNumRows < 0 && $beforeRow - 1 + $pNumRows > 0) {
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
                if ($pSheet->cellExists($coordinate)) {
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) {
0 ignored issues
show
introduced by
The condition $conditionalStyles can never be true.
Loading history...
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
                if ($pSheet->cellExists($coordinate)) {
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) {
0 ignored issues
show
introduced by
The condition $conditionalStyles can never be true.
Loading history...
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));
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);
646
                        $modified4 = substr($this->updateCellReference('$A' . $match[4], $pBefore, $pNumCols, $pNumRows), 2);
647
648
                        if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
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
                        if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
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
                            if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
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
                        if ($match[3] !== $modified3) {
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 string $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 (!Coordinate::coordinateIsRange($pCellRange)) {
778
            // Single cell
779 18
            return $this->updateSingleCellReference($pCellRange, $pBefore, $pNumCols, $pNumRows);
780 14
        } elseif (Coordinate::coordinateIsRange($pCellRange)) {
781
            // Range
782 14
            return $this->updateCellRange($pCellRange, $pBefore, $pNumCols, $pNumRows);
783
        }
784
785
        // Return original
786
        return $pCellRange;
787
    }
788
789
    /**
790
     * Update named formulas (i.e. containing worksheet references / named ranges).
791
     *
792
     * @param Spreadsheet $spreadsheet Object to update
793
     * @param string $oldName Old name (name to replace)
794
     * @param string $newName New name
795
     */
796 38
    public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $newName = '')
797
    {
798 38
        if ($oldName == '') {
799
            return;
800
        }
801
802 38
        foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
803 38
            foreach ($sheet->getCoordinates(false) as $coordinate) {
804 29
                $cell = $sheet->getCell($coordinate);
805 29
                if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) {
806 20
                    $formula = $cell->getValue();
807 20
                    if (strpos($formula, $oldName) !== false) {
808
                        $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
809
                        $formula = str_replace($oldName . '!', $newName . '!', $formula);
810 38
                        $cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
811
                    }
812
                }
813
            }
814
        }
815 38
    }
816
817
    /**
818
     * Update cell range.
819
     *
820
     * @param string $pCellRange Cell range    (e.g. 'B2:D4', 'B:C' or '2:3')
821
     * @param string $pBefore Insert before this one
822
     * @param int $pNumCols Number of columns to increment
823
     * @param int $pNumRows Number of rows to increment
824
     *
825
     * @throws Exception
826
     *
827
     * @return string Updated cell range
828
     */
829 14
    private function updateCellRange($pCellRange = 'A1:A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
830
    {
831 14
        if (!Coordinate::coordinateIsRange($pCellRange)) {
832
            throw new Exception('Only cell ranges may be passed to this method.');
833
        }
834
835
        // Update range
836 14
        $range = Coordinate::splitRange($pCellRange);
837 14
        $ic = count($range);
838 14
        for ($i = 0; $i < $ic; ++$i) {
839 14
            $jc = count($range[$i]);
840 14
            for ($j = 0; $j < $jc; ++$j) {
841 14
                if (ctype_alpha($range[$i][$j])) {
842
                    $r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', $pBefore, $pNumCols, $pNumRows));
843
                    $range[$i][$j] = $r[0];
844 14
                } elseif (ctype_digit($range[$i][$j])) {
845
                    $r = Coordinate::coordinateFromString($this->updateSingleCellReference('A' . $range[$i][$j], $pBefore, $pNumCols, $pNumRows));
846
                    $range[$i][$j] = $r[1];
847
                } else {
848 14
                    $range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $pBefore, $pNumCols, $pNumRows);
849
                }
850
            }
851
        }
852
853
        // Recreate range string
854 14
        return Coordinate::buildRange($range);
855
    }
856
857
    /**
858
     * Update single cell reference.
859
     *
860
     * @param string $pCellReference Single cell reference
861
     * @param string $pBefore Insert before this one
862
     * @param int $pNumCols Number of columns to increment
863
     * @param int $pNumRows Number of rows to increment
864
     *
865
     * @throws Exception
866
     *
867
     * @return string Updated cell reference
868
     */
869 18
    private function updateSingleCellReference($pCellReference = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
870
    {
871 18
        if (Coordinate::coordinateIsRange($pCellReference)) {
872
            throw new Exception('Only single cell references may be passed to this method.');
873
        }
874
875
        // Get coordinate of $pBefore
876 18
        list($beforeColumn, $beforeRow) = Coordinate::coordinateFromString($pBefore);
877
878
        // Get coordinate of $pCellReference
879 18
        list($newColumn, $newRow) = Coordinate::coordinateFromString($pCellReference);
880
881
        // Verify which parts should be updated
882 18
        $updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn)));
883 18
        $updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow);
884
885
        // Create new column reference
886 18
        if ($updateColumn) {
887 18
            $newColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($newColumn) + $pNumCols);
888
        }
889
890
        // Create new row reference
891 18
        if ($updateRow) {
892 17
            $newRow = $newRow + $pNumRows;
893
        }
894
895
        // Return new reference
896 18
        return $newColumn . $newRow;
897
    }
898
899
    /**
900
     * __clone implementation. Cloning should not be allowed in a Singleton!
901
     *
902
     * @throws Exception
903
     */
904
    final public function __clone()
905
    {
906
        throw new Exception('Cloning a Singleton is not allowed!');
907
    }
908
}
909