Completed
Push — develop ( 682b1b...fe73b2 )
by Adrien
20:00
created

LookupRef::cellAddress()   C

Complexity

Conditions 15
Paths 25

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 240

Importance

Changes 0
Metric Value
cc 15
eloc 24
nc 25
nop 5
dl 0
loc 38
rs 5.0504
c 0
b 0
f 0
ccs 0
cts 24
cp 0
crap 240

How to fix   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\Calculation;
4
5
/**
6
 * Copyright (c) 2006 - 2016 PhpSpreadsheet.
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
 *
22
 * @category    PhpSpreadsheet
23
 *
24
 * @copyright   Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
25
 * @license     http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
26
 */
27
class LookupRef
28
{
29
    /**
30
     * CELL_ADDRESS.
31
     *
32
     * Creates a cell address as text, given specified row and column numbers.
33
     *
34
     * Excel Function:
35
     *        =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText])
36
     *
37
     * @param row Row number to use in the cell reference
38
     * @param column Column number to use in the cell reference
39
     * @param relativity Flag indicating the type of reference to return
40
     *                                1 or omitted    Absolute
41
     *                                2                Absolute row; relative column
42
     *                                3                Relative row; absolute column
43
     *                                4                Relative
44
     * @param referenceStyle A logical value that specifies the A1 or R1C1 reference style.
45
     *                                TRUE or omitted        CELL_ADDRESS returns an A1-style reference
46
     *                                FALSE                CELL_ADDRESS returns an R1C1-style reference
47
     * @param sheetText Optional Name of worksheet to use
48
     * @param mixed $row
49
     * @param mixed $column
50
     * @param mixed $relativity
51
     * @param mixed $referenceStyle
52
     * @param mixed $sheetText
53
     *
54
     * @return string
55
     */
56
    public static function cellAddress($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '')
57
    {
58
        $row = Functions::flattenSingleValue($row);
59
        $column = Functions::flattenSingleValue($column);
60
        $relativity = Functions::flattenSingleValue($relativity);
61
        $sheetText = Functions::flattenSingleValue($sheetText);
62
63
        if (($row < 1) || ($column < 1)) {
64
            return Functions::VALUE();
65
        }
66
67
        if ($sheetText > '') {
68
            if (strpos($sheetText, ' ') !== false) {
69
                $sheetText = "'" . $sheetText . "'";
70
            }
71
            $sheetText .= '!';
72
        }
73
        if ((!is_bool($referenceStyle)) || $referenceStyle) {
74
            $rowRelative = $columnRelative = '$';
75
            $column = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($column - 1);
76
            if (($relativity == 2) || ($relativity == 4)) {
77
                $columnRelative = '';
78
            }
79
            if (($relativity == 3) || ($relativity == 4)) {
80
                $rowRelative = '';
81
            }
82
83
            return $sheetText . $columnRelative . $column . $rowRelative . $row;
84
        }
85
        if (($relativity == 2) || ($relativity == 4)) {
86
            $column = '[' . $column . ']';
87
        }
88
        if (($relativity == 3) || ($relativity == 4)) {
89
            $row = '[' . $row . ']';
90
        }
91
92
        return $sheetText . 'R' . $row . 'C' . $column;
93
    }
94
95
    /**
96
     * COLUMN.
97
     *
98
     * Returns the column number of the given cell reference
99
     * If the cell reference is a range of cells, COLUMN returns the column numbers of each column in the reference as a horizontal array.
100
     * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
101
     *        reference of the cell in which the COLUMN function appears; otherwise this function returns 0.
102
     *
103
     * Excel Function:
104
     *        =COLUMN([cellAddress])
105
     *
106
     * @param cellAddress A reference to a range of cells for which you want the column numbers
107
     * @param null|mixed $cellAddress
108
     *
109
     * @return int or array of integer
110
     */
111
    public static function COLUMN($cellAddress = null)
112
    {
113
        if (is_null($cellAddress) || trim($cellAddress) === '') {
114
            return 0;
115
        }
116
117
        if (is_array($cellAddress)) {
118
            foreach ($cellAddress as $columnKey => $value) {
119
                $columnKey = preg_replace('/[^a-z]/i', '', $columnKey);
120
121
                return (int) \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($columnKey);
122
            }
123 View Code Duplication
        } else {
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...
124
            if (strpos($cellAddress, '!') !== false) {
125
                list($sheet, $cellAddress) = explode('!', $cellAddress);
0 ignored issues
show
Unused Code introduced by
The assignment to $sheet is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
126
            }
127
            if (strpos($cellAddress, ':') !== false) {
128
                list($startAddress, $endAddress) = explode(':', $cellAddress);
129
                $startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
130
                $endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
131
                $returnValue = [];
132
                do {
133
                    $returnValue[] = (int) \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($startAddress);
134
                } while ($startAddress++ != $endAddress);
135
136
                return $returnValue;
137
            }
138
            $cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress);
139
140
            return (int) \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($cellAddress);
141
        }
142
    }
143
144
    /**
145
     * COLUMNS.
146
     *
147
     * Returns the number of columns in an array or reference.
148
     *
149
     * Excel Function:
150
     *        =COLUMNS(cellAddress)
151
     *
152
     * @param cellAddress An array or array formula, or a reference to a range of cells for which you want the number of columns
153
     * @param null|mixed $cellAddress
154
     *
155
     * @return int The number of columns in cellAddress
156
     */
157 View Code Duplication
    public static function COLUMNS($cellAddress = null)
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...
158
    {
159
        if (is_null($cellAddress) || $cellAddress === '') {
160
            return 1;
161
        } elseif (!is_array($cellAddress)) {
162
            return Functions::VALUE();
163
        }
164
165
        reset($cellAddress);
166
        $isMatrix = (is_numeric(key($cellAddress)));
167
        list($columns, $rows) = \PhpOffice\PhpSpreadsheet\Calculation::_getMatrixDimensions($cellAddress);
0 ignored issues
show
Bug introduced by
The method _getMatrixDimensions() does not exist on PhpOffice\PhpSpreadsheet\Calculation. Did you maybe mean getMatrixDimensions()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
168
169
        if ($isMatrix) {
170
            return $rows;
171
        }
172
173
        return $columns;
174
    }
175
176
    /**
177
     * ROW.
178
     *
179
     * Returns the row number of the given cell reference
180
     * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference as a vertical array.
181
     * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
182
     *        reference of the cell in which the ROW function appears; otherwise this function returns 0.
183
     *
184
     * Excel Function:
185
     *        =ROW([cellAddress])
186
     *
187
     * @param cellAddress A reference to a range of cells for which you want the row numbers
188
     * @param null|mixed $cellAddress
189
     *
190
     * @return int or array of integer
191
     */
192
    public static function ROW($cellAddress = null)
193
    {
194
        if (is_null($cellAddress) || trim($cellAddress) === '') {
195
            return 0;
196
        }
197
198
        if (is_array($cellAddress)) {
199
            foreach ($cellAddress as $columnKey => $rowValue) {
200
                foreach ($rowValue as $rowKey => $cellValue) {
201
                    return (int) preg_replace('/[^0-9]/i', '', $rowKey);
202
                }
203
            }
204 View Code Duplication
        } else {
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...
205
            if (strpos($cellAddress, '!') !== false) {
206
                list($sheet, $cellAddress) = explode('!', $cellAddress);
0 ignored issues
show
Unused Code introduced by
The assignment to $sheet is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
207
            }
208
            if (strpos($cellAddress, ':') !== false) {
209
                list($startAddress, $endAddress) = explode(':', $cellAddress);
210
                $startAddress = preg_replace('/[^0-9]/', '', $startAddress);
211
                $endAddress = preg_replace('/[^0-9]/', '', $endAddress);
212
                $returnValue = [];
213
                do {
214
                    $returnValue[][] = (int) $startAddress;
215
                } while ($startAddress++ != $endAddress);
216
217
                return $returnValue;
218
            }
219
            list($cellAddress) = explode(':', $cellAddress);
220
221
            return (int) preg_replace('/[^0-9]/', '', $cellAddress);
222
        }
223
    }
224
225
    /**
226
     * ROWS.
227
     *
228
     * Returns the number of rows in an array or reference.
229
     *
230
     * Excel Function:
231
     *        =ROWS(cellAddress)
232
     *
233
     * @param cellAddress An array or array formula, or a reference to a range of cells for which you want the number of rows
234
     * @param null|mixed $cellAddress
235
     *
236
     * @return int The number of rows in cellAddress
237
     */
238 View Code Duplication
    public static function ROWS($cellAddress = null)
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...
239
    {
240
        if (is_null($cellAddress) || $cellAddress === '') {
241
            return 1;
242
        } elseif (!is_array($cellAddress)) {
243
            return Functions::VALUE();
244
        }
245
246
        reset($cellAddress);
247
        $isMatrix = (is_numeric(key($cellAddress)));
248
        list($columns, $rows) = \PhpOffice\PhpSpreadsheet\Calculation::_getMatrixDimensions($cellAddress);
0 ignored issues
show
Bug introduced by
The method _getMatrixDimensions() does not exist on PhpOffice\PhpSpreadsheet\Calculation. Did you maybe mean getMatrixDimensions()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
249
250
        if ($isMatrix) {
251
            return $columns;
252
        }
253
254
        return $rows;
255
    }
256
257
    /**
258
     * HYPERLINK.
259
     *
260
     * Excel Function:
261
     *        =HYPERLINK(linkURL,displayName)
262
     *
263
     * @category Logical Functions
264
     *
265
     * @param string $linkURL Value to check, is also the value returned when no error
266
     * @param string $displayName Value to return when testValue is an error condition
267
     * @param \PhpOffice\PhpSpreadsheet\Cell $pCell The cell to set the hyperlink in
268
     *
269
     * @return mixed The value of $displayName (or $linkURL if $displayName was blank)
270
     */
271 1
    public static function HYPERLINK($linkURL = '', $displayName = null, \PhpOffice\PhpSpreadsheet\Cell $pCell = null)
272
    {
273 1
        $linkURL = (is_null($linkURL)) ? '' : Functions::flattenSingleValue($linkURL);
274 1
        $displayName = (is_null($displayName)) ? '' : Functions::flattenSingleValue($displayName);
275
276 1
        if ((!is_object($pCell)) || (trim($linkURL) == '')) {
277
            return Functions::REF();
278
        }
279
280 1
        if ((is_object($displayName)) || trim($displayName) == '') {
281
            $displayName = $linkURL;
282
        }
283
284 1
        $pCell->getHyperlink()->setUrl($linkURL);
285 1
        $pCell->getHyperlink()->setTooltip($displayName);
286
287 1
        return $displayName;
288
    }
289
290
    /**
291
     * INDIRECT.
292
     *
293
     * Returns the reference specified by a text string.
294
     * References are immediately evaluated to display their contents.
295
     *
296
     * Excel Function:
297
     *        =INDIRECT(cellAddress)
298
     *
299
     * NOTE - INDIRECT() does not yet support the optional a1 parameter introduced in Excel 2010
300
     *
301
     * @param cellAddress $cellAddress The cell address of the current cell (containing this formula)
302
     * @param \PhpOffice\PhpSpreadsheet\Cell $pCell The current cell (containing this formula)
303
     *
304
     * @return mixed The cells referenced by cellAddress
305
     *
306
     * @todo    Support for the optional a1 parameter introduced in Excel 2010
307
     */
308
    public static function INDIRECT($cellAddress = null, \PhpOffice\PhpSpreadsheet\Cell $pCell = null)
309
    {
310
        $cellAddress = Functions::flattenSingleValue($cellAddress);
311
        if (is_null($cellAddress) || $cellAddress === '') {
312
            return Functions::REF();
313
        }
314
315
        $cellAddress1 = $cellAddress;
316
        $cellAddress2 = null;
317
        if (strpos($cellAddress, ':') !== false) {
318
            list($cellAddress1, $cellAddress2) = explode(':', $cellAddress);
319
        }
320
321
        if ((!preg_match('/^' . \PhpOffice\PhpSpreadsheet\Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) ||
322
            ((!is_null($cellAddress2)) && (!preg_match('/^' . \PhpOffice\PhpSpreadsheet\Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches)))) {
323
            if (!preg_match('/^' . \PhpOffice\PhpSpreadsheet\Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $cellAddress1, $matches)) {
324
                return Functions::REF();
325
            }
326
327 View Code Duplication
            if (strpos($cellAddress, '!') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
328
                list($sheetName, $cellAddress) = explode('!', $cellAddress);
329
                $sheetName = trim($sheetName, "'");
330
                $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
0 ignored issues
show
Bug introduced by
It seems like $pCell is not always an object, but can also be of type null. Maybe add an additional type check?

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

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
331
            } else {
332
                $pSheet = $pCell->getWorksheet();
333
            }
334
335
            return \PhpOffice\PhpSpreadsheet\Calculation::getInstance()->extractNamedRange($cellAddress, $pSheet, false);
336
        }
337
338 View Code Duplication
        if (strpos($cellAddress, '!') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
339
            list($sheetName, $cellAddress) = explode('!', $cellAddress);
340
            $sheetName = trim($sheetName, "'");
341
            $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
342
        } else {
343
            $pSheet = $pCell->getWorksheet();
344
        }
345
346
        return \PhpOffice\PhpSpreadsheet\Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
347
    }
348
349
    /**
350
     * OFFSET.
351
     *
352
     * Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
353
     * The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and
354
     * the number of columns to be returned.
355
     *
356
     * Excel Function:
357
     *        =OFFSET(cellAddress, rows, cols, [height], [width])
358
     *
359
     * @param cellAddress The reference from which you want to base the offset. Reference must refer to a cell or
360
     *                                range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value.
361
     * @param rows The number of rows, up or down, that you want the upper-left cell to refer to.
362
     *                                Using 5 as the rows argument specifies that the upper-left cell in the reference is
363
     *                                five rows below reference. Rows can be positive (which means below the starting reference)
364
     *                                or negative (which means above the starting reference).
365
     * @param cols The number of columns, to the left or right, that you want the upper-left cell of the result
366
     *                                to refer to. Using 5 as the cols argument specifies that the upper-left cell in the
367
     *                                reference is five columns to the right of reference. Cols can be positive (which means
368
     *                                to the right of the starting reference) or negative (which means to the left of the
369
     *                                starting reference).
370
     * @param height The height, in number of rows, that you want the returned reference to be. Height must be a positive number.
371
     * @param width The width, in number of columns, that you want the returned reference to be. Width must be a positive number.
372
     * @param null|mixed $cellAddress
373
     * @param mixed $rows
374
     * @param mixed $columns
375
     * @param null|mixed $height
376
     * @param null|mixed $width
377
     * @param \PhpOffice\PhpSpreadsheet\Cell $pCell
378
     *
379
     * @return string A reference to a cell or range of cells
380
     */
381
    public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, \PhpOffice\PhpSpreadsheet\Cell $pCell = null)
382
    {
383
        $rows = Functions::flattenSingleValue($rows);
384
        $columns = Functions::flattenSingleValue($columns);
385
        $height = Functions::flattenSingleValue($height);
386
        $width = Functions::flattenSingleValue($width);
387
        if ($cellAddress == null) {
388
            return 0;
389
        }
390
391
        if (!is_object($pCell)) {
392
            return Functions::REF();
393
        }
394
395
        $sheetName = null;
396
        if (strpos($cellAddress, '!')) {
397
            list($sheetName, $cellAddress) = explode('!', $cellAddress);
398
            $sheetName = trim($sheetName, "'");
399
        }
400
        if (strpos($cellAddress, ':')) {
401
            list($startCell, $endCell) = explode(':', $cellAddress);
402
        } else {
403
            $startCell = $endCell = $cellAddress;
404
        }
405
        list($startCellColumn, $startCellRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($startCell);
406
        list($endCellColumn, $endCellRow) = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($endCell);
407
408
        $startCellRow += $rows;
409
        $startCellColumn = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($startCellColumn) - 1;
410
        $startCellColumn += $columns;
411
412
        if (($startCellRow <= 0) || ($startCellColumn < 0)) {
413
            return Functions::REF();
414
        }
415
        $endCellColumn = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($endCellColumn) - 1;
416
        if (($width != null) && (!is_object($width))) {
417
            $endCellColumn = $startCellColumn + $width - 1;
418
        } else {
419
            $endCellColumn += $columns;
420
        }
421
        $startCellColumn = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($startCellColumn);
422
423
        if (($height != null) && (!is_object($height))) {
424
            $endCellRow = $startCellRow + $height - 1;
425
        } else {
426
            $endCellRow += $rows;
427
        }
428
429
        if (($endCellRow <= 0) || ($endCellColumn < 0)) {
430
            return Functions::REF();
431
        }
432
        $endCellColumn = \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex($endCellColumn);
433
434
        $cellAddress = $startCellColumn . $startCellRow;
435
        if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) {
436
            $cellAddress .= ':' . $endCellColumn . $endCellRow;
437
        }
438
439
        if ($sheetName !== null) {
440
            $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
441
        } else {
442
            $pSheet = $pCell->getWorksheet();
443
        }
444
445
        return \PhpOffice\PhpSpreadsheet\Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
446
    }
447
448
    /**
449
     * CHOOSE.
450
     *
451
     * Uses lookup_value to return a value from the list of value arguments.
452
     * Use CHOOSE to select one of up to 254 values based on the lookup_value.
453
     *
454
     * Excel Function:
455
     *        =CHOOSE(index_num, value1, [value2], ...)
456
     *
457
     * @param index_num Specifies which value argument is selected.
458
     *                            Index_num must be a number between 1 and 254, or a formula or reference to a cell containing a number
459
     *                                between 1 and 254.
460
     * @param value1... Value1 is required, subsequent values are optional.
461
     *                            Between 1 to 254 value arguments from which CHOOSE selects a value or an action to perform based on
462
     *                                index_num. The arguments can be numbers, cell references, defined names, formulas, functions, or
463
     *                                text.
464
     *
465
     * @return mixed The selected value
466
     */
467
    public static function CHOOSE(...$chooseArgs)
468
    {
469
        $chosenEntry = Functions::flattenArray(array_shift($chooseArgs));
470
        $entryCount = count($chooseArgs) - 1;
471
472
        if (is_array($chosenEntry)) {
473
            $chosenEntry = array_shift($chosenEntry);
474
        }
475
        if ((is_numeric($chosenEntry)) && (!is_bool($chosenEntry))) {
476
            --$chosenEntry;
477
        } else {
478
            return Functions::VALUE();
479
        }
480
        $chosenEntry = floor($chosenEntry);
481
        if (($chosenEntry < 0) || ($chosenEntry > $entryCount)) {
482
            return Functions::VALUE();
483
        }
484
485
        if (is_array($chooseArgs[$chosenEntry])) {
486
            return Functions::flattenArray($chooseArgs[$chosenEntry]);
487
        }
488
489
        return $chooseArgs[$chosenEntry];
490
    }
491
492
    /**
493
     * MATCH.
494
     *
495
     * The MATCH function searches for a specified item in a range of cells
496
     *
497
     * Excel Function:
498
     *        =MATCH(lookup_value, lookup_array, [match_type])
499
     *
500
     * @param lookup_value The value that you want to match in lookup_array
501
     * @param lookup_array The range of cells being searched
502
     * @param match_type The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below. If match_type is 1 or -1, the list has to be ordered.
503
     * @param mixed $lookup_value
504
     * @param mixed $lookup_array
505
     * @param mixed $match_type
506
     *
507
     * @return int The relative position of the found item
508
     */
509
    public static function MATCH($lookup_value, $lookup_array, $match_type = 1)
510
    {
511
        $lookup_array = Functions::flattenArray($lookup_array);
512
        $lookup_value = Functions::flattenSingleValue($lookup_value);
513
        $match_type = (is_null($match_type)) ? 1 : (int) Functions::flattenSingleValue($match_type);
514
        //    MATCH is not case sensitive
515
        $lookup_value = strtolower($lookup_value);
516
517
        //    lookup_value type has to be number, text, or logical values
518 View Code Duplication
        if ((!is_numeric($lookup_value)) && (!is_string($lookup_value)) && (!is_bool($lookup_value))) {
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...
519
            return Functions::NA();
520
        }
521
522
        //    match_type is 0, 1 or -1
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% 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...
523
        if (($match_type !== 0) && ($match_type !== -1) && ($match_type !== 1)) {
524
            return Functions::NA();
525
        }
526
527
        //    lookup_array should not be empty
528
        $lookupArraySize = count($lookup_array);
529
        if ($lookupArraySize <= 0) {
530
            return Functions::NA();
531
        }
532
533
        //    lookup_array should contain only number, text, or logical values, or empty (null) cells
534
        foreach ($lookup_array as $i => $lookupArrayValue) {
535
            //    check the type of the value
536 View Code Duplication
            if ((!is_numeric($lookupArrayValue)) && (!is_string($lookupArrayValue)) &&
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...
537
                (!is_bool($lookupArrayValue)) && (!is_null($lookupArrayValue))) {
538
                return Functions::NA();
539
            }
540
            //    convert strings to lowercase for case-insensitive testing
541
            if (is_string($lookupArrayValue)) {
542
                $lookup_array[$i] = strtolower($lookupArrayValue);
543
            }
544
            if ((is_null($lookupArrayValue)) && (($match_type == 1) || ($match_type == -1))) {
545
                $lookup_array = array_slice($lookup_array, 0, $i - 1);
546
            }
547
        }
548
549
        // if match_type is 1 or -1, the list has to be ordered
550
        if ($match_type == 1) {
551
            asort($lookup_array);
552
            $keySet = array_keys($lookup_array);
553
        } elseif ($match_type == -1) {
554
            arsort($lookup_array);
555
            $keySet = array_keys($lookup_array);
556
        }
557
558
        // **
559
        // find the match
560
        // **
561
        foreach ($lookup_array as $i => $lookupArrayValue) {
562
            if (($match_type == 0) && ($lookupArrayValue == $lookup_value)) {
563
                //    exact match
564
                return ++$i;
565
            } elseif (($match_type == -1) && ($lookupArrayValue <= $lookup_value)) {
566
                $i = array_search($i, $keySet);
0 ignored issues
show
Bug introduced by
The variable $keySet does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
567
                // if match_type is -1 <=> find the smallest value that is greater than or equal to lookup_value
568
                if ($i < 1) {
569
                    // 1st cell was already smaller than the lookup_value
570
                    break;
571
                }
572
                    // the previous cell was the match
573
                    return $keySet[$i - 1] + 1;
574
            } elseif (($match_type == 1) && ($lookupArrayValue >= $lookup_value)) {
575
                $i = array_search($i, $keySet);
576
                // if match_type is 1 <=> find the largest value that is less than or equal to lookup_value
577
                if ($i < 1) {
578
                    // 1st cell was already bigger than the lookup_value
579
                    break;
580
                }
581
                    // the previous cell was the match
582
                    return $keySet[$i - 1] + 1;
583
            }
584
        }
585
586
        //    unsuccessful in finding a match, return #N/A error value
587
        return Functions::NA();
588
    }
589
590
    /**
591
     * INDEX.
592
     *
593
     * Uses an index to choose a value from a reference or array
594
     *
595
     * Excel Function:
596
     *        =INDEX(range_array, row_num, [column_num])
597
     *
598
     * @param range_array A range of cells or an array constant
599
     * @param row_num The row in array from which to return a value. If row_num is omitted, column_num is required.
600
     * @param column_num The column in array from which to return a value. If column_num is omitted, row_num is required.
601
     * @param mixed $arrayValues
602
     * @param mixed $rowNum
603
     * @param mixed $columnNum
604
     *
605
     * @return mixed the value of a specified cell or array of cells
606
     */
607
    public static function INDEX($arrayValues, $rowNum = 0, $columnNum = 0)
608
    {
609
        if (($rowNum < 0) || ($columnNum < 0)) {
610
            return Functions::VALUE();
611
        }
612
613
        if (!is_array($arrayValues)) {
614
            return Functions::REF();
615
        }
616
617
        $rowKeys = array_keys($arrayValues);
618
        $columnKeys = @array_keys($arrayValues[$rowKeys[0]]);
619
620
        if ($columnNum > count($columnKeys)) {
621
            return Functions::VALUE();
622
        } elseif ($columnNum == 0) {
623
            if ($rowNum == 0) {
624
                return $arrayValues;
625
            }
626
            $rowNum = $rowKeys[--$rowNum];
627
            $returnArray = [];
628
            foreach ($arrayValues as $arrayColumn) {
629
                if (is_array($arrayColumn)) {
630
                    if (isset($arrayColumn[$rowNum])) {
631
                        $returnArray[] = $arrayColumn[$rowNum];
632
                    } else {
633
                        return $arrayValues[$rowNum];
634
                    }
635
                } else {
636
                    return $arrayValues[$rowNum];
637
                }
638
            }
639
640
            return $returnArray;
641
        }
642
        $columnNum = $columnKeys[--$columnNum];
643
        if ($rowNum > count($rowKeys)) {
644
            return Functions::VALUE();
645
        } elseif ($rowNum == 0) {
646
            return $arrayValues[$columnNum];
647
        }
648
        $rowNum = $rowKeys[--$rowNum];
649
650
        return $arrayValues[$rowNum][$columnNum];
651
    }
652
653
    /**
654
     * TRANSPOSE.
655
     *
656
     * @param array $matrixData A matrix of values
657
     *
658
     * @return array
659
     *
660
     * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix
661
     */
662
    public static function TRANSPOSE($matrixData)
663
    {
664
        $returnMatrix = [];
665
        if (!is_array($matrixData)) {
666
            $matrixData = [[$matrixData]];
667
        }
668
669
        $column = 0;
670
        foreach ($matrixData as $matrixRow) {
671
            $row = 0;
672
            foreach ($matrixRow as $matrixCell) {
673
                $returnMatrix[$row][$column] = $matrixCell;
674
                ++$row;
675
            }
676
            ++$column;
677
        }
678
679
        return $returnMatrix;
680
    }
681
682 2
    private static function vlookupSort($a, $b)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
683
    {
684 2
        reset($a);
685 2
        $firstColumn = key($a);
686 2
        if (($aLower = strtolower($a[$firstColumn])) == ($bLower = strtolower($b[$firstColumn]))) {
687
            return 0;
688
        }
689
690 2
        return ($aLower < $bLower) ? -1 : 1;
691
    }
692
693
    /**
694
     * VLOOKUP
695
     * The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value in the same row based on the index_number.
696
     *
697
     * @param lookup_value The value that you want to match in lookup_array
698
     * @param lookup_array The range of cells being searched
699
     * @param index_number The column number in table_array from which the matching value must be returned. The first column is 1.
700
     * @param not_exact_match determines if you are looking for an exact match based on lookup_value
701
     * @param mixed $lookup_value
702
     * @param mixed $lookup_array
703
     * @param mixed $index_number
704
     * @param mixed $not_exact_match
705
     *
706
     * @return mixed The value of the found cell
707
     */
708 5
    public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
709
    {
710 5
        $lookup_value = Functions::flattenSingleValue($lookup_value);
711 5
        $index_number = Functions::flattenSingleValue($index_number);
712 5
        $not_exact_match = Functions::flattenSingleValue($not_exact_match);
713
714
        // index_number must be greater than or equal to 1
715 5
        if ($index_number < 1) {
716
            return Functions::VALUE();
717
        }
718
719
        // index_number must be less than or equal to the number of columns in lookup_array
720 5
        if ((!is_array($lookup_array)) || (empty($lookup_array))) {
721
            return Functions::REF();
722
        }
723 5
        $f = array_keys($lookup_array);
724 5
        $firstRow = array_pop($f);
725 5 View Code Duplication
        if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array[$firstRow]))) {
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...
726
            return Functions::REF();
727
        }
728 5
        $columnKeys = array_keys($lookup_array[$firstRow]);
729 5
        $returnColumn = $columnKeys[--$index_number];
730 5
        $firstColumn = array_shift($columnKeys);
731
732 5
        if (!$not_exact_match) {
733 2
            uasort($lookup_array, ['self', 'vlookupSort']);
734
        }
735
736 5
        $rowNumber = $rowValue = false;
737 5
        foreach ($lookup_array as $rowKey => $rowData) {
738 5
            if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) ||
739 5
                (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && (strtolower($rowData[$firstColumn]) > strtolower($lookup_value)))) {
740 4
                break;
741
            }
742
            // remember the last key, but only if datatypes match
743 5
            if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) ||
744 5
                (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) {
745 4
                $rowNumber = $rowKey;
746 5
                $rowValue = $rowData[$firstColumn];
747
            }
748
        }
749
750 5 View Code Duplication
        if ($rowNumber !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
751 4
            if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
752
                //    if an exact match is required, we have what we need to return an appropriate response
753 2
                return Functions::NA();
754
            }
755
                //    otherwise return the appropriate value
756 2
                return $lookup_array[$rowNumber][$returnColumn];
757
        }
758
759 1
        return Functions::NA();
760
    }
761
762
    /**
763
     * HLOOKUP
764
     * The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value in the same column based on the index_number.
765
     *
766
     * @param lookup_value The value that you want to match in lookup_array
767
     * @param lookup_array The range of cells being searched
768
     * @param index_number The row number in table_array from which the matching value must be returned. The first row is 1.
769
     * @param not_exact_match determines if you are looking for an exact match based on lookup_value
770
     * @param mixed $lookup_value
771
     * @param mixed $lookup_array
772
     * @param mixed $index_number
773
     * @param mixed $not_exact_match
774
     *
775
     * @return mixed The value of the found cell
776
     */
777 9
    public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
778
    {
779 9
        $lookup_value = Functions::flattenSingleValue($lookup_value);
780 9
        $index_number = Functions::flattenSingleValue($index_number);
781 9
        $not_exact_match = Functions::flattenSingleValue($not_exact_match);
782
783
        // index_number must be greater than or equal to 1
784 9
        if ($index_number < 1) {
785
            return Functions::VALUE();
786
        }
787
788
        // index_number must be less than or equal to the number of columns in lookup_array
789 9
        if ((!is_array($lookup_array)) || (empty($lookup_array))) {
790
            return Functions::REF();
791
        }
792 9
        $f = array_keys($lookup_array);
793 9
        $firstRow = array_pop($f);
794 9 View Code Duplication
        if ((!is_array($lookup_array[$firstRow])) || ($index_number - 1 > count($lookup_array[$firstRow]))) {
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...
795
            return Functions::REF();
796
        }
797 9
        $columnKeys = array_keys($lookup_array[$firstRow]);
0 ignored issues
show
Unused Code introduced by
$columnKeys is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
798 9
        $firstkey = $f[0] - 1;
799 9
        $returnColumn = $firstkey + $index_number;
800 9
        $firstColumn = array_shift($f);
801
802 9
        if (!$not_exact_match) {
803 4
            $firstRowH = asort($lookup_array[$firstColumn]);
0 ignored issues
show
Unused Code introduced by
$firstRowH is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
804
        }
805 9
        $rowNumber = $rowValue = false;
806 9
        foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) {
807 9
            if ((is_numeric($lookup_value) && is_numeric($rowData) && ($rowData > $lookup_value)) ||
808 9
                (!is_numeric($lookup_value) && !is_numeric($rowData) && (strtolower($rowData) > strtolower($lookup_value)))) {
809 7
                break;
810
            }
811 9
            $rowNumber = $rowKey;
812 9
            $rowValue = $rowData;
813
        }
814
815 9 View Code Duplication
        if ($rowNumber !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
816 9
            if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
817
                //  if an exact match is required, we have what we need to return an appropriate response
818 1
                return Functions::NA();
819
            }
820
                //  otherwise return the appropriate value
821 8
                return $lookup_array[$returnColumn][$rowNumber];
822
        }
823
824
        return Functions::NA();
825
    }
826
827
    /**
828
     * LOOKUP
829
     * The LOOKUP function searches for value either from a one-row or one-column range or from an array.
830
     *
831
     * @param lookup_value The value that you want to match in lookup_array
832
     * @param lookup_vector The range of cells being searched
833
     * @param result_vector The column from which the matching value must be returned
834
     * @param mixed $lookup_value
835
     * @param mixed $lookup_vector
836
     * @param null|mixed $result_vector
837
     *
838
     * @return mixed The value of the found cell
839
     */
840
    public static function LOOKUP($lookup_value, $lookup_vector, $result_vector = null)
841
    {
842
        $lookup_value = Functions::flattenSingleValue($lookup_value);
843
844
        if (!is_array($lookup_vector)) {
845
            return Functions::NA();
846
        }
847
        $lookupRows = count($lookup_vector);
848
        $l = array_keys($lookup_vector);
849
        $l = array_shift($l);
850
        $lookupColumns = count($lookup_vector[$l]);
851 View Code Duplication
        if ((($lookupRows == 1) && ($lookupColumns > 1)) || (($lookupRows == 2) && ($lookupColumns != 2))) {
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...
852
            $lookup_vector = self::TRANSPOSE($lookup_vector);
853
            $lookupRows = count($lookup_vector);
854
            $l = array_keys($lookup_vector);
855
            $lookupColumns = count($lookup_vector[array_shift($l)]);
856
        }
857
858
        if (is_null($result_vector)) {
859
            $result_vector = $lookup_vector;
860
        }
861
        $resultRows = count($result_vector);
862
        $l = array_keys($result_vector);
863
        $l = array_shift($l);
864
        $resultColumns = count($result_vector[$l]);
865 View Code Duplication
        if ((($resultRows == 1) && ($resultColumns > 1)) || (($resultRows == 2) && ($resultColumns != 2))) {
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...
866
            $result_vector = self::TRANSPOSE($result_vector);
867
            $resultRows = count($result_vector);
0 ignored issues
show
Unused Code introduced by
$resultRows is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
868
            $r = array_keys($result_vector);
869
            $resultColumns = count($result_vector[array_shift($r)]);
0 ignored issues
show
Unused Code introduced by
$resultColumns is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
870
        }
871
872
        if ($lookupRows == 2) {
873
            $result_vector = array_pop($lookup_vector);
874
            $lookup_vector = array_shift($lookup_vector);
875
        }
876
        if ($lookupColumns != 2) {
877
            foreach ($lookup_vector as &$value) {
878
                if (is_array($value)) {
879
                    $k = array_keys($value);
880
                    $key1 = $key2 = array_shift($k);
881
                    ++$key2;
882
                    $dataValue1 = $value[$key1];
883
                } else {
884
                    $key1 = 0;
885
                    $key2 = 1;
886
                    $dataValue1 = $value;
887
                }
888
                $dataValue2 = array_shift($result_vector);
889
                if (is_array($dataValue2)) {
890
                    $dataValue2 = array_shift($dataValue2);
891
                }
892
                $value = [$key1 => $dataValue1, $key2 => $dataValue2];
893
            }
894
            unset($value);
895
        }
896
897
        return self::VLOOKUP($lookup_value, $lookup_vector, 2);
0 ignored issues
show
Documentation introduced by
2 is of type integer, but the function expects a object<PhpOffice\PhpSpreadsheet\Calculation\The>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
898
    }
899
}
900