Completed
Push — develop ( e1f81f...539a89 )
by Adrien
16:11
created

LookupRef::ROW()   D

Complexity

Conditions 9
Paths 8

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 22
nc 8
nop 1
dl 0
loc 31
rs 4.909
c 0
b 0
f 0
1
<?php
2
3
namespace 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
 * @copyright   Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
24
 * @license     http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
25
 * @version     ##VERSION##, ##DATE##
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
     * @return    string
49
     */
50
    public static function cellAddress($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '')
51
    {
52
        $row        = Functions::flattenSingleValue($row);
53
        $column     = Functions::flattenSingleValue($column);
54
        $relativity = Functions::flattenSingleValue($relativity);
55
        $sheetText  = Functions::flattenSingleValue($sheetText);
56
57
        if (($row < 1) || ($column < 1)) {
58
            return Functions::VALUE();
59
        }
60
61
        if ($sheetText > '') {
62
            if (strpos($sheetText, ' ') !== false) {
63
                $sheetText = "'".$sheetText."'";
64
            }
65
            $sheetText .='!';
66
        }
67
        if ((!is_bool($referenceStyle)) || $referenceStyle) {
68
            $rowRelative = $columnRelative = '$';
69
            $column = \PhpSpreadsheet\Cell::stringFromColumnIndex($column-1);
70
            if (($relativity == 2) || ($relativity == 4)) {
71
                $columnRelative = '';
72
            }
73
            if (($relativity == 3) || ($relativity == 4)) {
74
                $rowRelative = '';
75
            }
76
            return $sheetText.$columnRelative.$column.$rowRelative.$row;
77
        } else {
78
            if (($relativity == 2) || ($relativity == 4)) {
79
                $column = '['.$column.']';
80
            }
81
            if (($relativity == 3) || ($relativity == 4)) {
82
                $row = '['.$row.']';
83
            }
84
            return $sheetText.'R'.$row.'C'.$column;
85
        }
86
    }
87
88
89
    /**
90
     * COLUMN
91
     *
92
     * Returns the column number of the given cell reference
93
     * If the cell reference is a range of cells, COLUMN returns the column numbers of each column in the reference as a horizontal array.
94
     * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
95
     *        reference of the cell in which the COLUMN function appears; otherwise this function returns 0.
96
     *
97
     * Excel Function:
98
     *        =COLUMN([cellAddress])
99
     *
100
     * @param    cellAddress        A reference to a range of cells for which you want the column numbers
101
     * @return    integer or array of integer
102
     */
103
    public static function COLUMN($cellAddress = null)
104
    {
105
        if (is_null($cellAddress) || trim($cellAddress) === '') {
106
            return 0;
107
        }
108
109
        if (is_array($cellAddress)) {
110
            foreach ($cellAddress as $columnKey => $value) {
111
                $columnKey = preg_replace('/[^a-z]/i', '', $columnKey);
112
                return (integer) \PhpSpreadsheet\Cell::columnIndexFromString($columnKey);
113
            }
114
        } else {
115
            if (strpos($cellAddress, '!') !== false) {
116
                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...
117
            }
118
            if (strpos($cellAddress, ':') !== false) {
119
                list($startAddress, $endAddress) = explode(':', $cellAddress);
120
                $startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
121
                $endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
122
                $returnValue = array();
123
                do {
124
                    $returnValue[] = (integer) \PhpSpreadsheet\Cell::columnIndexFromString($startAddress);
125
                } while ($startAddress++ != $endAddress);
126
                return $returnValue;
127
            } else {
128
                $cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress);
129
                return (integer) \PhpSpreadsheet\Cell::columnIndexFromString($cellAddress);
130
            }
131
        }
132
    }
133
134
135
    /**
136
     * COLUMNS
137
     *
138
     * Returns the number of columns in an array or reference.
139
     *
140
     * Excel Function:
141
     *        =COLUMNS(cellAddress)
142
     *
143
     * @param    cellAddress        An array or array formula, or a reference to a range of cells for which you want the number of columns
144
     * @return    integer            The number of columns in cellAddress
145
     */
146 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...
147
    {
148
        if (is_null($cellAddress) || $cellAddress === '') {
149
            return 1;
150
        } elseif (!is_array($cellAddress)) {
151
            return Functions::VALUE();
152
        }
153
154
        reset($cellAddress);
155
        $isMatrix = (is_numeric(key($cellAddress)));
156
        list($columns, $rows) = \PhpSpreadsheet\Calculation::_getMatrixDimensions($cellAddress);
0 ignored issues
show
Bug introduced by
The method _getMatrixDimensions() does not exist on 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...
157
158
        if ($isMatrix) {
159
            return $rows;
160
        } else {
161
            return $columns;
162
        }
163
    }
164
165
166
    /**
167
     * ROW
168
     *
169
     * Returns the row number of the given cell reference
170
     * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference as a vertical array.
171
     * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
172
     *        reference of the cell in which the ROW function appears; otherwise this function returns 0.
173
     *
174
     * Excel Function:
175
     *        =ROW([cellAddress])
176
     *
177
     * @param    cellAddress        A reference to a range of cells for which you want the row numbers
178
     * @return    integer or array of integer
179
     */
180
    public static function ROW($cellAddress = null)
181
    {
182
        if (is_null($cellAddress) || trim($cellAddress) === '') {
183
            return 0;
184
        }
185
186
        if (is_array($cellAddress)) {
187
            foreach ($cellAddress as $columnKey => $rowValue) {
188
                foreach ($rowValue as $rowKey => $cellValue) {
189
                    return (integer) preg_replace('/[^0-9]/i', '', $rowKey);
190
                }
191
            }
192
        } else {
193
            if (strpos($cellAddress, '!') !== false) {
194
                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...
195
            }
196
            if (strpos($cellAddress, ':') !== false) {
197
                list($startAddress, $endAddress) = explode(':', $cellAddress);
198
                $startAddress = preg_replace('/[^0-9]/', '', $startAddress);
199
                $endAddress = preg_replace('/[^0-9]/', '', $endAddress);
200
                $returnValue = array();
201
                do {
202
                    $returnValue[][] = (integer) $startAddress;
203
                } while ($startAddress++ != $endAddress);
204
                return $returnValue;
205
            } else {
206
                list($cellAddress) = explode(':', $cellAddress);
207
                return (integer) preg_replace('/[^0-9]/', '', $cellAddress);
208
            }
209
        }
210
    }
211
212
213
    /**
214
     * ROWS
215
     *
216
     * Returns the number of rows in an array or reference.
217
     *
218
     * Excel Function:
219
     *        =ROWS(cellAddress)
220
     *
221
     * @param    cellAddress        An array or array formula, or a reference to a range of cells for which you want the number of rows
222
     * @return    integer            The number of rows in cellAddress
223
     */
224 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...
225
    {
226
        if (is_null($cellAddress) || $cellAddress === '') {
227
            return 1;
228
        } elseif (!is_array($cellAddress)) {
229
            return Functions::VALUE();
230
        }
231
232
        reset($cellAddress);
233
        $isMatrix = (is_numeric(key($cellAddress)));
234
        list($columns, $rows) = \PhpSpreadsheet\Calculation::_getMatrixDimensions($cellAddress);
0 ignored issues
show
Bug introduced by
The method _getMatrixDimensions() does not exist on 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...
235
236
        if ($isMatrix) {
237
            return $columns;
238
        } else {
239
            return $rows;
240
        }
241
    }
242
243
244
    /**
245
     * HYPERLINK
246
     *
247
     * Excel Function:
248
     *        =HYPERLINK(linkURL,displayName)
249
     *
250
     * @access    public
251
     * @category Logical Functions
252
     * @param    string            $linkURL        Value to check, is also the value returned when no error
253
     * @param    string            $displayName    Value to return when testValue is an error condition
254
     * @param    \PhpSpreadsheet\Cell    $pCell            The cell to set the hyperlink in
255
     * @return   mixed    The value of $displayName (or $linkURL if $displayName was blank)
256
     */
257
    public static function HYPERLINK($linkURL = '', $displayName = null, \PhpSpreadsheet\Cell $pCell = null)
0 ignored issues
show
Unused Code introduced by
The parameter $pCell is not used and could be removed.

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

Loading history...
258
    {
259
        $args = func_get_args();
260
        $pCell = array_pop($args);
261
262
        $linkURL     = (is_null($linkURL))     ? '' : Functions::flattenSingleValue($linkURL);
263
        $displayName = (is_null($displayName)) ? '' : Functions::flattenSingleValue($displayName);
264
265
        if ((!is_object($pCell)) || (trim($linkURL) == '')) {
266
            return Functions::REF();
267
        }
268
269
        if ((is_object($displayName)) || trim($displayName) == '') {
270
            $displayName = $linkURL;
271
        }
272
273
        $pCell->getHyperlink()->setUrl($linkURL);
274
        $pCell->getHyperlink()->setTooltip($displayName);
275
276
        return $displayName;
277
    }
278
279
280
    /**
281
     * INDIRECT
282
     *
283
     * Returns the reference specified by a text string.
284
     * References are immediately evaluated to display their contents.
285
     *
286
     * Excel Function:
287
     *        =INDIRECT(cellAddress)
288
     *
289
     * NOTE - INDIRECT() does not yet support the optional a1 parameter introduced in Excel 2010
290
     *
291
     * @param    cellAddress        $cellAddress    The cell address of the current cell (containing this formula)
292
     * @param    \PhpSpreadsheet\Cell    $pCell            The current cell (containing this formula)
293
     * @return   mixed            The cells referenced by cellAddress
294
     *
295
     * @todo    Support for the optional a1 parameter introduced in Excel 2010
296
     *
297
     */
298
    public static function INDIRECT($cellAddress = null, \PhpSpreadsheet\Cell $pCell = null)
299
    {
300
        $cellAddress    = Functions::flattenSingleValue($cellAddress);
301
        if (is_null($cellAddress) || $cellAddress === '') {
302
            return Functions::REF();
303
        }
304
305
        $cellAddress1 = $cellAddress;
306
        $cellAddress2 = null;
307
        if (strpos($cellAddress, ':') !== false) {
308
            list($cellAddress1, $cellAddress2) = explode(':', $cellAddress);
309
        }
310
311
        if ((!preg_match('/^'.\PhpSpreadsheet\Calculation::CALCULATION_REGEXP_CELLREF.'$/i', $cellAddress1, $matches)) ||
312
            ((!is_null($cellAddress2)) && (!preg_match('/^'.\PhpSpreadsheet\Calculation::CALCULATION_REGEXP_CELLREF.'$/i', $cellAddress2, $matches)))) {
313
            if (!preg_match('/^'.\PhpSpreadsheet\Calculation::CALCULATION_REGEXP_NAMEDRANGE.'$/i', $cellAddress1, $matches)) {
314
                return Functions::REF();
315
            }
316
317 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...
318
                list($sheetName, $cellAddress) = explode('!', $cellAddress);
319
                $sheetName = trim($sheetName, "'");
320
                $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...
321
            } else {
322
                $pSheet = $pCell->getWorksheet();
323
            }
324
325
            return \PhpSpreadsheet\Calculation::getInstance()->extractNamedRange($cellAddress, $pSheet, false);
326
        }
327
328 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...
329
            list($sheetName, $cellAddress) = explode('!', $cellAddress);
330
            $sheetName = trim($sheetName, "'");
331
            $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
332
        } else {
333
            $pSheet = $pCell->getWorksheet();
334
        }
335
336
        return \PhpSpreadsheet\Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
337
    }
338
339
340
    /**
341
     * OFFSET
342
     *
343
     * Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
344
     * The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and
345
     * the number of columns to be returned.
346
     *
347
     * Excel Function:
348
     *        =OFFSET(cellAddress, rows, cols, [height], [width])
349
     *
350
     * @param    cellAddress        The reference from which you want to base the offset. Reference must refer to a cell or
351
     *                                range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value.
352
     * @param    rows            The number of rows, up or down, that you want the upper-left cell to refer to.
353
     *                                Using 5 as the rows argument specifies that the upper-left cell in the reference is
354
     *                                five rows below reference. Rows can be positive (which means below the starting reference)
355
     *                                or negative (which means above the starting reference).
356
     * @param    cols            The number of columns, to the left or right, that you want the upper-left cell of the result
357
     *                                to refer to. Using 5 as the cols argument specifies that the upper-left cell in the
358
     *                                reference is five columns to the right of reference. Cols can be positive (which means
359
     *                                to the right of the starting reference) or negative (which means to the left of the
360
     *                                starting reference).
361
     * @param    height            The height, in number of rows, that you want the returned reference to be. Height must be a positive number.
362
     * @param    width            The width, in number of columns, that you want the returned reference to be. Width must be a positive number.
363
     * @return    string            A reference to a cell or range of cells
364
     */
365
    public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null)
366
    {
367
        $rows    = Functions::flattenSingleValue($rows);
368
        $columns = Functions::flattenSingleValue($columns);
369
        $height  = Functions::flattenSingleValue($height);
370
        $width   = Functions::flattenSingleValue($width);
371
        if ($cellAddress == null) {
372
            return 0;
373
        }
374
375
        $args = func_get_args();
376
        $pCell = array_pop($args);
377
        if (!is_object($pCell)) {
378
            return Functions::REF();
379
        }
380
381
        $sheetName = null;
382
        if (strpos($cellAddress, "!")) {
383
            list($sheetName, $cellAddress) = explode("!", $cellAddress);
384
            $sheetName = trim($sheetName, "'");
385
        }
386
        if (strpos($cellAddress, ":")) {
387
            list($startCell, $endCell) = explode(":", $cellAddress);
388
        } else {
389
            $startCell = $endCell = $cellAddress;
390
        }
391
        list($startCellColumn, $startCellRow) = \PhpSpreadsheet\Cell::coordinateFromString($startCell);
392
        list($endCellColumn, $endCellRow) = \PhpSpreadsheet\Cell::coordinateFromString($endCell);
393
394
        $startCellRow += $rows;
395
        $startCellColumn = \PhpSpreadsheet\Cell::columnIndexFromString($startCellColumn) - 1;
396
        $startCellColumn += $columns;
397
398
        if (($startCellRow <= 0) || ($startCellColumn < 0)) {
399
            return Functions::REF();
400
        }
401
        $endCellColumn = \PhpSpreadsheet\Cell::columnIndexFromString($endCellColumn) - 1;
402
        if (($width != null) && (!is_object($width))) {
403
            $endCellColumn = $startCellColumn + $width - 1;
404
        } else {
405
            $endCellColumn += $columns;
406
        }
407
        $startCellColumn = \PhpSpreadsheet\Cell::stringFromColumnIndex($startCellColumn);
408
409
        if (($height != null) && (!is_object($height))) {
410
            $endCellRow = $startCellRow + $height - 1;
411
        } else {
412
            $endCellRow += $rows;
413
        }
414
415
        if (($endCellRow <= 0) || ($endCellColumn < 0)) {
416
            return Functions::REF();
417
        }
418
        $endCellColumn = \PhpSpreadsheet\Cell::stringFromColumnIndex($endCellColumn);
419
420
        $cellAddress = $startCellColumn.$startCellRow;
421
        if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) {
422
            $cellAddress .= ':'.$endCellColumn.$endCellRow;
423
        }
424
425
        if ($sheetName !== null) {
426
            $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
427
        } else {
428
            $pSheet = $pCell->getWorksheet();
429
        }
430
431
        return \PhpSpreadsheet\Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
432
    }
433
434
435
    /**
436
     * CHOOSE
437
     *
438
     * Uses lookup_value to return a value from the list of value arguments.
439
     * Use CHOOSE to select one of up to 254 values based on the lookup_value.
440
     *
441
     * Excel Function:
442
     *        =CHOOSE(index_num, value1, [value2], ...)
443
     *
444
     * @param    index_num        Specifies which value argument is selected.
445
     *                            Index_num must be a number between 1 and 254, or a formula or reference to a cell containing a number
446
     *                                between 1 and 254.
447
     * @param    value1...        Value1 is required, subsequent values are optional.
448
     *                            Between 1 to 254 value arguments from which CHOOSE selects a value or an action to perform based on
449
     *                                index_num. The arguments can be numbers, cell references, defined names, formulas, functions, or
450
     *                                text.
451
     * @return    mixed            The selected value
452
     */
453
    public static function CHOOSE()
454
    {
455
        $chooseArgs = func_get_args();
456
        $chosenEntry = Functions::flattenArray(array_shift($chooseArgs));
457
        $entryCount = count($chooseArgs) - 1;
458
459
        if (is_array($chosenEntry)) {
460
            $chosenEntry = array_shift($chosenEntry);
461
        }
462
        if ((is_numeric($chosenEntry)) && (!is_bool($chosenEntry))) {
463
            --$chosenEntry;
464
        } else {
465
            return Functions::VALUE();
466
        }
467
        $chosenEntry = floor($chosenEntry);
468
        if (($chosenEntry < 0) || ($chosenEntry > $entryCount)) {
469
            return Functions::VALUE();
470
        }
471
472
        if (is_array($chooseArgs[$chosenEntry])) {
473
            return Functions::flattenArray($chooseArgs[$chosenEntry]);
474
        } else {
475
            return $chooseArgs[$chosenEntry];
476
        }
477
    }
478
479
480
    /**
481
     * MATCH
482
     *
483
     * The MATCH function searches for a specified item in a range of cells
484
     *
485
     * Excel Function:
486
     *        =MATCH(lookup_value, lookup_array, [match_type])
487
     *
488
     * @param    lookup_value    The value that you want to match in lookup_array
489
     * @param    lookup_array    The range of cells being searched
490
     * @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.
491
     * @return    integer            The relative position of the found item
492
     */
493
    public static function MATCH($lookup_value, $lookup_array, $match_type = 1)
494
    {
495
        $lookup_array = Functions::flattenArray($lookup_array);
496
        $lookup_value = Functions::flattenSingleValue($lookup_value);
497
        $match_type    = (is_null($match_type)) ? 1 : (int) Functions::flattenSingleValue($match_type);
498
        //    MATCH is not case sensitive
499
        $lookup_value = strtolower($lookup_value);
500
501
        //    lookup_value type has to be number, text, or logical values
502 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...
503
            return Functions::NA();
504
        }
505
506
        //    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...
507
        if (($match_type !== 0) && ($match_type !== -1) && ($match_type !== 1)) {
508
            return Functions::NA();
509
        }
510
511
        //    lookup_array should not be empty
512
        $lookupArraySize = count($lookup_array);
513
        if ($lookupArraySize <= 0) {
514
            return Functions::NA();
515
        }
516
517
        //    lookup_array should contain only number, text, or logical values, or empty (null) cells
518
        foreach ($lookup_array as $i => $lookupArrayValue) {
519
            //    check the type of the value
520 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...
521
                (!is_bool($lookupArrayValue)) && (!is_null($lookupArrayValue))) {
522
                return Functions::NA();
523
            }
524
            //    convert strings to lowercase for case-insensitive testing
525
            if (is_string($lookupArrayValue)) {
526
                $lookup_array[$i] = strtolower($lookupArrayValue);
527
            }
528
            if ((is_null($lookupArrayValue)) && (($match_type == 1) || ($match_type == -1))) {
529
                $lookup_array = array_slice($lookup_array, 0, $i-1);
530
            }
531
        }
532
533
        // if match_type is 1 or -1, the list has to be ordered
534
        if ($match_type == 1) {
535
            asort($lookup_array);
536
            $keySet = array_keys($lookup_array);
537
        } elseif ($match_type == -1) {
538
            arsort($lookup_array);
539
            $keySet = array_keys($lookup_array);
540
        }
541
542
        // **
543
        // find the match
544
        // **
545
        foreach ($lookup_array as $i => $lookupArrayValue) {
546
            if (($match_type == 0) && ($lookupArrayValue == $lookup_value)) {
547
                //    exact match
548
                return ++$i;
549
            } elseif (($match_type == -1) && ($lookupArrayValue <= $lookup_value)) {
550
                $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...
551
                // if match_type is -1 <=> find the smallest value that is greater than or equal to lookup_value
552
                if ($i < 1) {
553
                    // 1st cell was already smaller than the lookup_value
554
                    break;
555
                } else {
556
                    // the previous cell was the match
557
                    return $keySet[$i-1]+1;
558
                }
559
            } elseif (($match_type == 1) && ($lookupArrayValue >= $lookup_value)) {
560
                $i = array_search($i, $keySet);
561
                // if match_type is 1 <=> find the largest value that is less than or equal to lookup_value
562
                if ($i < 1) {
563
                    // 1st cell was already bigger than the lookup_value
564
                    break;
565
                } else {
566
                    // the previous cell was the match
567
                    return $keySet[$i-1]+1;
568
                }
569
            }
570
        }
571
572
        //    unsuccessful in finding a match, return #N/A error value
573
        return Functions::NA();
574
    }
575
576
577
    /**
578
     * INDEX
579
     *
580
     * Uses an index to choose a value from a reference or array
581
     *
582
     * Excel Function:
583
     *        =INDEX(range_array, row_num, [column_num])
584
     *
585
     * @param    range_array        A range of cells or an array constant
586
     * @param    row_num            The row in array from which to return a value. If row_num is omitted, column_num is required.
587
     * @param    column_num        The column in array from which to return a value. If column_num is omitted, row_num is required.
588
     * @return    mixed            the value of a specified cell or array of cells
589
     */
590
    public static function INDEX($arrayValues, $rowNum = 0, $columnNum = 0)
591
    {
592
        if (($rowNum < 0) || ($columnNum < 0)) {
593
            return Functions::VALUE();
594
        }
595
596
        if (!is_array($arrayValues)) {
597
            return Functions::REF();
598
        }
599
600
        $rowKeys = array_keys($arrayValues);
601
        $columnKeys = @array_keys($arrayValues[$rowKeys[0]]);
602
603
        if ($columnNum > count($columnKeys)) {
604
            return Functions::VALUE();
605
        } elseif ($columnNum == 0) {
606
            if ($rowNum == 0) {
607
                return $arrayValues;
608
            }
609
            $rowNum = $rowKeys[--$rowNum];
610
            $returnArray = array();
611
            foreach ($arrayValues as $arrayColumn) {
612
                if (is_array($arrayColumn)) {
613
                    if (isset($arrayColumn[$rowNum])) {
614
                        $returnArray[] = $arrayColumn[$rowNum];
615
                    } else {
616
                        return $arrayValues[$rowNum];
617
                    }
618
                } else {
619
                    return $arrayValues[$rowNum];
620
                }
621
            }
622
            return $returnArray;
623
        }
624
        $columnNum = $columnKeys[--$columnNum];
625
        if ($rowNum > count($rowKeys)) {
626
            return Functions::VALUE();
627
        } elseif ($rowNum == 0) {
628
            return $arrayValues[$columnNum];
629
        }
630
        $rowNum = $rowKeys[--$rowNum];
631
632
        return $arrayValues[$rowNum][$columnNum];
633
    }
634
635
636
    /**
637
     * TRANSPOSE
638
     *
639
     * @param    array    $matrixData    A matrix of values
640
     * @return    array
641
     *
642
     * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix.
643
     */
644
    public static function TRANSPOSE($matrixData)
645
    {
646
        $returnMatrix = array();
647
        if (!is_array($matrixData)) {
648
            $matrixData = array(array($matrixData));
649
        }
650
651
        $column = 0;
652
        foreach ($matrixData as $matrixRow) {
653
            $row = 0;
654
            foreach ($matrixRow as $matrixCell) {
655
                $returnMatrix[$row][$column] = $matrixCell;
656
                ++$row;
657
            }
658
            ++$column;
659
        }
660
        return $returnMatrix;
661
    }
662
663
664
    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...
665
    {
666
        reset($a);
667
        $firstColumn = key($a);
668
        if (($aLower = strtolower($a[$firstColumn])) == ($bLower = strtolower($b[$firstColumn]))) {
669
            return 0;
670
        }
671
        return ($aLower < $bLower) ? -1 : 1;
672
    }
673
674
675
    /**
676
     * VLOOKUP
677
     * 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.
678
     * @param    lookup_value    The value that you want to match in lookup_array
679
     * @param    lookup_array    The range of cells being searched
680
     * @param    index_number    The column number in table_array from which the matching value must be returned. The first column is 1.
681
     * @param    not_exact_match    Determines if you are looking for an exact match based on lookup_value.
682
     * @return    mixed            The value of the found cell
683
     */
684
    public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
685
    {
686
        $lookup_value    = Functions::flattenSingleValue($lookup_value);
687
        $index_number    = Functions::flattenSingleValue($index_number);
688
        $not_exact_match = Functions::flattenSingleValue($not_exact_match);
689
690
        // index_number must be greater than or equal to 1
691
        if ($index_number < 1) {
692
            return Functions::VALUE();
693
        }
694
695
        // index_number must be less than or equal to the number of columns in lookup_array
696
        if ((!is_array($lookup_array)) || (empty($lookup_array))) {
697
            return Functions::REF();
698
        } else {
699
            $f = array_keys($lookup_array);
700
            $firstRow = array_pop($f);
701
            if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array[$firstRow]))) {
702
                return Functions::REF();
703
            } else {
704
                $columnKeys = array_keys($lookup_array[$firstRow]);
705
                $returnColumn = $columnKeys[--$index_number];
706
                $firstColumn = array_shift($columnKeys);
707
            }
708
        }
709
710
        if (!$not_exact_match) {
711
            uasort($lookup_array, array('self', 'vlookupSort'));
712
        }
713
714
        $rowNumber = $rowValue = false;
715
        foreach ($lookup_array as $rowKey => $rowData) {
716
            if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) ||
717
                (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && (strtolower($rowData[$firstColumn]) > strtolower($lookup_value)))) {
718
                break;
719
            }
720
            // remember the last key, but only if datatypes match
721
            if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) ||
722
                (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) {
723
                $rowNumber = $rowKey;
724
                $rowValue = $rowData[$firstColumn];
725
            }
726
        }
727
728 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...
729
            if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
730
                //    if an exact match is required, we have what we need to return an appropriate response
731
                return Functions::NA();
732
            } else {
733
                //    otherwise return the appropriate value
734
                return $lookup_array[$rowNumber][$returnColumn];
735
            }
736
        }
737
738
        return Functions::NA();
739
    }
740
741
742
    /**
743
     * HLOOKUP
744
     * 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.
745
     * @param    lookup_value    The value that you want to match in lookup_array
746
     * @param    lookup_array    The range of cells being searched
747
     * @param    index_number    The row number in table_array from which the matching value must be returned. The first row is 1.
748
     * @param    not_exact_match Determines if you are looking for an exact match based on lookup_value.
749
     * @return   mixed           The value of the found cell
750
     */
751
    public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
752
    {
753
        $lookup_value   = Functions::flattenSingleValue($lookup_value);
754
        $index_number   = Functions::flattenSingleValue($index_number);
755
        $not_exact_match    = Functions::flattenSingleValue($not_exact_match);
756
757
        // index_number must be greater than or equal to 1
758
        if ($index_number < 1) {
759
            return Functions::VALUE();
760
        }
761
762
        // index_number must be less than or equal to the number of columns in lookup_array
763
        if ((!is_array($lookup_array)) || (empty($lookup_array))) {
764
            return Functions::REF();
765
        } else {
766
            $f = array_keys($lookup_array);
767
            $firstRow = array_pop($f);
768
            if ((!is_array($lookup_array[$firstRow])) || ($index_number - 1 > count($lookup_array[$firstRow]))) {
769
                return Functions::REF();
770
            } else {
771
                $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...
772
                                $firstkey = $f[0] - 1;
773
                $returnColumn = $firstkey + $index_number;
774
                $firstColumn = array_shift($f);
775
            }
776
        }
777
778
        if (!$not_exact_match) {
779
            $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...
780
        }
781
        $rowNumber = $rowValue = false;
782
        foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) {
783
            if ((is_numeric($lookup_value) && is_numeric($rowData) && ($rowData > $lookup_value)) ||
784
                (!is_numeric($lookup_value) && !is_numeric($rowData) && (strtolower($rowData) > strtolower($lookup_value)))) {
785
                break;
786
            }
787
            $rowNumber = $rowKey;
788
            $rowValue = $rowData;
789
        }
790
791 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...
792
            if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
793
                //  if an exact match is required, we have what we need to return an appropriate response
794
                return Functions::NA();
795
            } else {
796
                //  otherwise return the appropriate value
797
                return $lookup_array[$returnColumn][$rowNumber];
798
            }
799
        }
800
801
        return Functions::NA();
802
    }
803
804
805
    /**
806
     * LOOKUP
807
     * The LOOKUP function searches for value either from a one-row or one-column range or from an array.
808
     * @param    lookup_value    The value that you want to match in lookup_array
809
     * @param    lookup_vector    The range of cells being searched
810
     * @param    result_vector    The column from which the matching value must be returned
811
     * @return    mixed            The value of the found cell
812
     */
813
    public static function LOOKUP($lookup_value, $lookup_vector, $result_vector = null)
814
    {
815
        $lookup_value = Functions::flattenSingleValue($lookup_value);
816
817
        if (!is_array($lookup_vector)) {
818
            return Functions::NA();
819
        }
820
        $lookupRows = count($lookup_vector);
821
        $l = array_keys($lookup_vector);
822
        $l = array_shift($l);
823
        $lookupColumns = count($lookup_vector[$l]);
824 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...
825
            $lookup_vector = self::TRANSPOSE($lookup_vector);
826
            $lookupRows = count($lookup_vector);
827
            $l = array_keys($lookup_vector);
828
            $lookupColumns = count($lookup_vector[array_shift($l)]);
829
        }
830
831
        if (is_null($result_vector)) {
832
            $result_vector = $lookup_vector;
833
        }
834
        $resultRows = count($result_vector);
835
        $l = array_keys($result_vector);
836
        $l = array_shift($l);
837
        $resultColumns = count($result_vector[$l]);
838 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...
839
            $result_vector = self::TRANSPOSE($result_vector);
840
            $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...
841
            $r = array_keys($result_vector);
842
            $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...
843
        }
844
845
        if ($lookupRows == 2) {
846
            $result_vector = array_pop($lookup_vector);
847
            $lookup_vector = array_shift($lookup_vector);
848
        }
849
        if ($lookupColumns != 2) {
850
            foreach ($lookup_vector as &$value) {
851
                if (is_array($value)) {
852
                    $k = array_keys($value);
853
                    $key1 = $key2 = array_shift($k);
854
                    $key2++;
855
                    $dataValue1 = $value[$key1];
856
                } else {
857
                    $key1 = 0;
858
                    $key2 = 1;
859
                    $dataValue1 = $value;
860
                }
861
                $dataValue2 = array_shift($result_vector);
862
                if (is_array($dataValue2)) {
863
                    $dataValue2 = array_shift($dataValue2);
864
                }
865
                $value = array($key1 => $dataValue1, $key2 => $dataValue2);
866
            }
867
            unset($value);
868
        }
869
870
        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<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...
871
    }
872
}
873