Completed
Push — develop ( 2922a1...cfa1fe )
by Adrien
24:40
created

Calculation::_translateFormulaToLocale()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 24
Code Lines 14

Duplication

Lines 24
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 4
nop 1
dl 24
loc 24
rs 6.7272
c 0
b 0
f 0
ccs 0
cts 14
cp 0
crap 56
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5 1
if (!defined('CALCULATION_REGEXP_CELLREF')) {
6
    //    Test for support of \P (multibyte options) in PCRE
7 1
    if (defined('PREG_BAD_UTF8_ERROR')) {
8
        //    Cell reference (cell or range of cells, with or without a sheet reference)
9 1
        define('CALCULATION_REGEXP_CELLREF', '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d{1,7})');
10
        //    Named Range of cells
11 1
        define('CALCULATION_REGEXP_NAMEDRANGE', '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_A-Z][_A-Z0-9\.]*)');
12
    } else {
13
        //    Cell reference (cell or range of cells, with or without a sheet reference)
14
        define('CALCULATION_REGEXP_CELLREF', '(((\w*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d+)');
15
        //    Named Range of cells
16
        define('CALCULATION_REGEXP_NAMEDRANGE', '(((\w*)|(\'.*\')|(\".*\"))!)?([_A-Z][_A-Z0-9\.]*)');
17
    }
18
}
19
20
/**
21
 * Copyright (c) 2006 - 2016 PhpSpreadsheet
22
 *
23
 * This library is free software; you can redistribute it and/or
24
 * modify it under the terms of the GNU Lesser General Public
25
 * License as published by the Free Software Foundation; either
26
 * version 2.1 of the License, or (at your option) any later version.
27
 *
28
 * This library is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
31
 * Lesser General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Lesser General Public
34
 * License along with this library; if not, write to the Free Software
35
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
36
 *
37
 * @category   PhpSpreadsheet
38
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
39
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
40
 * @version    ##VERSION##, ##DATE##
41
 */
42
class Calculation
43
{
44
    /** Constants                */
45
/** Regular Expressions        */
46
    //    Numeric operand
47
    const CALCULATION_REGEXP_NUMBER = '[-+]?\d*\.?\d+(e[-+]?\d+)?';
48
    //    String operand
49
    const CALCULATION_REGEXP_STRING = '"(?:[^"]|"")*"';
50
    //    Opening bracket
51
    const CALCULATION_REGEXP_OPENBRACE = '\(';
52
    //    Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
53
    const CALCULATION_REGEXP_FUNCTION = '@?([A-Z][A-Z0-9\.]*)[\s]*\(';
54
    //    Cell reference (cell or range of cells, with or without a sheet reference)
55
    const CALCULATION_REGEXP_CELLREF = CALCULATION_REGEXP_CELLREF;
56
    //    Named Range of cells
57
    const CALCULATION_REGEXP_NAMEDRANGE = CALCULATION_REGEXP_NAMEDRANGE;
58
    //    Error
59
    const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?';
60
61
    /** constants */
62
    const RETURN_ARRAY_AS_ERROR = 'error';
63
    const RETURN_ARRAY_AS_VALUE = 'value';
64
    const RETURN_ARRAY_AS_ARRAY = 'array';
65
66
    private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
67
68
    /**
69
     * Instance of this class
70
     *
71
     * @var \PhpOffice\PhpSpreadsheet\Calculation
72
     */
73
    private static $instance;
74
75
    /**
76
     * Instance of the spreadsheet this Calculation Engine is using
77
     *
78
     * @var PhpSpreadsheet
79
     */
80
    private $spreadsheet;
81
82
    /**
83
     * List of instances of the calculation engine that we've instantiated for individual spreadsheets
84
     *
85
     * @var \PhpOffice\PhpSpreadsheet\Calculation[]
86
     */
87
    private static $spreadsheetSets;
0 ignored issues
show
Unused Code introduced by
The property $spreadsheetSets is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
88
89
    /**
90
     * Calculation cache
91
     *
92
     * @var array
93
     */
94
    private $calculationCache = [];
95
96
    /**
97
     * Calculation cache enabled
98
     *
99
     * @var bool
100
     */
101
    private $calculationCacheEnabled = true;
102
103
    /**
104
     * List of operators that can be used within formulae
105
     * The true/false value indicates whether it is a binary operator or a unary operator
106
     *
107
     * @var array
108
     */
109
    private static $operators = [
110
        '+' => true, '-' => true, '*' => true, '/' => true,
111
        '^' => true, '&' => true, '%' => false, '~' => false,
112
        '>' => true, '<' => true, '=' => true, '>=' => true,
113
        '<=' => true, '<>' => true, '|' => true, ':' => true,
114
    ];
115
116
    /**
117
     * List of binary operators (those that expect two operands)
118
     *
119
     * @var array
120
     */
121
    private static $binaryOperators = [
122
        '+' => true, '-' => true, '*' => true, '/' => true,
123
        '^' => true, '&' => true, '>' => true, '<' => true,
124
        '=' => true, '>=' => true, '<=' => true, '<>' => true,
125
        '|' => true, ':' => true,
126
    ];
127
128
    /**
129
     * The debug log generated by the calculation engine
130
     *
131
     * @var CalcEngine\Logger
132
     */
133
    private $debugLog;
0 ignored issues
show
Unused Code introduced by
The property $debugLog is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
134
135
    /**
136
     * Flag to determine how formula errors should be handled
137
     *        If true, then a user error will be triggered
138
     *        If false, then an exception will be thrown
139
     *
140
     * @var bool
141
     */
142
    public $suppressFormulaErrors = false;
143
144
    /**
145
     * Error message for any error that was raised/thrown by the calculation engine
146
     *
147
     * @var string
148
     */
149
    public $formulaError = null;
150
151
    /**
152
     * An array of the nested cell references accessed by the calculation engine, used for the debug log
153
     *
154
     * @var array of string
155
     */
156
    private $cyclicReferenceStack;
157
158
    private $cellStack = [];
159
160
    /**
161
     * Current iteration counter for cyclic formulae
162
     * If the value is 0 (or less) then cyclic formulae will throw an exception,
163
     *    otherwise they will iterate to the limit defined here before returning a result
164
     *
165
     * @var int
166
     */
167
    private $cyclicFormulaCounter = 1;
168
169
    private $cyclicFormulaCell = '';
170
171
    /**
172
     * Number of iterations for cyclic formulae
173
     *
174
     * @var int
175
     */
176
    public $cyclicFormulaCount = 1;
177
178
    /**
179
     * Epsilon Precision used for comparisons in calculations
180
     *
181
     * @var float
182
     */
183
    private $delta = 0.1e-12;
184
185
    /**
186
     * The current locale setting
187
     *
188
     * @var string
189
     */
190
    private static $localeLanguage = 'en_us'; //    US English    (default locale)
191
192
    /**
193
     * List of available locale settings
194
     * Note that this is read for the locale subdirectory only when requested
195
     *
196
     * @var string[]
197
     */
198
    private static $validLocaleLanguages = [
199
        'en', //    English        (default language)
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
200
    ];
201
202
    /**
203
     * Locale-specific argument separator for function arguments
204
     *
205
     * @var string
206
     */
207
    private static $localeArgumentSeparator = ',';
208
    private static $localeFunctions = [];
209
210
    /**
211
     * Locale-specific translations for Excel constants (True, False and Null)
212
     *
213
     * @var string[]
214
     */
215
    public static $localeBoolean = [
216
        'TRUE' => 'TRUE',
217
        'FALSE' => 'FALSE',
218
        'NULL' => 'NULL',
219
    ];
220
221
    /**
222
     * Excel constant string translations to their PHP equivalents
223
     * Constant conversion from text name/value to actual (datatyped) value
224
     *
225
     * @var string[]
226
     */
227
    private static $excelConstants = [
228
        'TRUE' => true,
229
        'FALSE' => false,
230
        'NULL' => null,
231
    ];
232
233
    // PhpSpreadsheet functions
234
    private static $phpSpreadsheetFunctions = [
235
        'ABS' => [
236
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
237
            'functionCall' => 'abs',
238
            'argumentCount' => '1',
239
        ],
240
        'ACCRINT' => [
241
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
242
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::ACCRINT',
243
            'argumentCount' => '4-7',
244
        ],
245
        'ACCRINTM' => [
246
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
247
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::ACCRINTM',
248
            'argumentCount' => '3-5',
249
        ],
250
        'ACOS' => [
251
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
252
            'functionCall' => 'acos',
253
            'argumentCount' => '1',
254
        ],
255
        'ACOSH' => [
256
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
257
            'functionCall' => 'acosh',
258
            'argumentCount' => '1',
259
        ],
260
        'ADDRESS' => [
261
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
262
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::cellAddress',
263
            'argumentCount' => '2-5',
264
        ],
265
        'AMORDEGRC' => [
266
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
267
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::AMORDEGRC',
268
            'argumentCount' => '6,7',
269
        ],
270
        'AMORLINC' => [
271
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
272
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::AMORLINC',
273
            'argumentCount' => '6,7',
274
        ],
275
        'AND' => [
276
            'category' => Calculation\Categories::CATEGORY_LOGICAL,
277
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Logical::logicalAnd',
278
            'argumentCount' => '1+',
279
        ],
280
        'AREAS' => [
281
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
282
            'functionCall' => 'Calculation\Categories::DUMMY',
283
            'argumentCount' => '1',
284
        ],
285
        'ASC' => [
286
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
287
            'functionCall' => 'Calculation\Categories::DUMMY',
288
            'argumentCount' => '1',
289
        ],
290
        'ASIN' => [
291
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
292
            'functionCall' => 'asin',
293
            'argumentCount' => '1',
294
        ],
295
        'ASINH' => [
296
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
297
            'functionCall' => 'asinh',
298
            'argumentCount' => '1',
299
        ],
300
        'ATAN' => [
301
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
302
            'functionCall' => 'atan',
303
            'argumentCount' => '1',
304
        ],
305
        'ATAN2' => [
306
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
307
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::ATAN2',
308
            'argumentCount' => '2',
309
        ],
310
        'ATANH' => [
311
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
312
            'functionCall' => 'atanh',
313
            'argumentCount' => '1',
314
        ],
315
        'AVEDEV' => [
316
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
317
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::AVEDEV',
318
            'argumentCount' => '1+',
319
        ],
320
        'AVERAGE' => [
321
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
322
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::AVERAGE',
323
            'argumentCount' => '1+',
324
        ],
325
        'AVERAGEA' => [
326
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
327
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::AVERAGEA',
328
            'argumentCount' => '1+',
329
        ],
330
        'AVERAGEIF' => [
331
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
332
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::AVERAGEIF',
333
            'argumentCount' => '2,3',
334
        ],
335
        'AVERAGEIFS' => [
336
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
337
            'functionCall' => 'Calculation\Categories::DUMMY',
338
            'argumentCount' => '3+',
339
        ],
340
        'BAHTTEXT' => [
341
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
342
            'functionCall' => 'Calculation\Categories::DUMMY',
343
            'argumentCount' => '1',
344
        ],
345
        'BESSELI' => [
346
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
347
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::BESSELI',
348
            'argumentCount' => '2',
349
        ],
350
        'BESSELJ' => [
351
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
352
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::BESSELJ',
353
            'argumentCount' => '2',
354
        ],
355
        'BESSELK' => [
356
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
357
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::BESSELK',
358
            'argumentCount' => '2',
359
        ],
360
        'BESSELY' => [
361
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
362
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::BESSELY',
363
            'argumentCount' => '2',
364
        ],
365
        'BETADIST' => [
366
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
367
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::BETADIST',
368
            'argumentCount' => '3-5',
369
        ],
370
        'BETAINV' => [
371
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
372
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::BETAINV',
373
            'argumentCount' => '3-5',
374
        ],
375
        'BIN2DEC' => [
376
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
377
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::BINTODEC',
378
            'argumentCount' => '1',
379
        ],
380
        'BIN2HEX' => [
381
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
382
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::BINTOHEX',
383
            'argumentCount' => '1,2',
384
        ],
385
        'BIN2OCT' => [
386
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
387
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::BINTOOCT',
388
            'argumentCount' => '1,2',
389
        ],
390
        'BINOMDIST' => [
391
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
392
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::BINOMDIST',
393
            'argumentCount' => '4',
394
        ],
395
        'CEILING' => [
396
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
397
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::CEILING',
398
            'argumentCount' => '2',
399
        ],
400
        'CELL' => [
401
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
402
            'functionCall' => 'Calculation\Categories::DUMMY',
403
            'argumentCount' => '1,2',
404
        ],
405
        'CHAR' => [
406
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
407
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::CHARACTER',
408
            'argumentCount' => '1',
409
        ],
410
        'CHIDIST' => [
411
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
412
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::CHIDIST',
413
            'argumentCount' => '2',
414
        ],
415
        'CHIINV' => [
416
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
417
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::CHIINV',
418
            'argumentCount' => '2',
419
        ],
420
        'CHITEST' => [
421
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
422
            'functionCall' => 'Calculation\Categories::DUMMY',
423
            'argumentCount' => '2',
424
        ],
425
        'CHOOSE' => [
426
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
427
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::CHOOSE',
428
            'argumentCount' => '2+',
429
        ],
430
        'CLEAN' => [
431
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
432
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::TRIMNONPRINTABLE',
433
            'argumentCount' => '1',
434
        ],
435
        'CODE' => [
436
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
437
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::ASCIICODE',
438
            'argumentCount' => '1',
439
        ],
440
        'COLUMN' => [
441
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
442
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::COLUMN',
443
            'argumentCount' => '-1',
444
            'passByReference' => [true],
445
        ],
446
        'COLUMNS' => [
447
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
448
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::COLUMNS',
449
            'argumentCount' => '1',
450
        ],
451
        'COMBIN' => [
452
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
453
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::COMBIN',
454
            'argumentCount' => '2',
455
        ],
456
        'COMPLEX' => [
457
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
458
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::COMPLEX',
459
            'argumentCount' => '2,3',
460
        ],
461
        'CONCATENATE' => [
462
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
463
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::CONCATENATE',
464
            'argumentCount' => '1+',
465
        ],
466
        'CONFIDENCE' => [
467
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
468
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::CONFIDENCE',
469
            'argumentCount' => '3',
470
        ],
471
        'CONVERT' => [
472
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
473
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::CONVERTUOM',
474
            'argumentCount' => '3',
475
        ],
476
        'CORREL' => [
477
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
478
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::CORREL',
479
            'argumentCount' => '2',
480
        ],
481
        'COS' => [
482
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
483
            'functionCall' => 'cos',
484
            'argumentCount' => '1',
485
        ],
486
        'COSH' => [
487
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
488
            'functionCall' => 'cosh',
489
            'argumentCount' => '1',
490
        ],
491
        'COUNT' => [
492
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
493
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::COUNT',
494
            'argumentCount' => '1+',
495
        ],
496
        'COUNTA' => [
497
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
498
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::COUNTA',
499
            'argumentCount' => '1+',
500
        ],
501
        'COUNTBLANK' => [
502
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
503
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::COUNTBLANK',
504
            'argumentCount' => '1',
505
        ],
506
        'COUNTIF' => [
507
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
508
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::COUNTIF',
509
            'argumentCount' => '2',
510
        ],
511
        'COUNTIFS' => [
512
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
513
            'functionCall' => 'Calculation\Categories::DUMMY',
514
            'argumentCount' => '2',
515
        ],
516
        'COUPDAYBS' => [
517
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
518
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::COUPDAYBS',
519
            'argumentCount' => '3,4',
520
        ],
521
        'COUPDAYS' => [
522
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
523
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::COUPDAYS',
524
            'argumentCount' => '3,4',
525
        ],
526
        'COUPDAYSNC' => [
527
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
528
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::COUPDAYSNC',
529
            'argumentCount' => '3,4',
530
        ],
531
        'COUPNCD' => [
532
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
533
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::COUPNCD',
534
            'argumentCount' => '3,4',
535
        ],
536
        'COUPNUM' => [
537
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
538
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::COUPNUM',
539
            'argumentCount' => '3,4',
540
        ],
541
        'COUPPCD' => [
542
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
543
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::COUPPCD',
544
            'argumentCount' => '3,4',
545
        ],
546
        'COVAR' => [
547
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
548
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::COVAR',
549
            'argumentCount' => '2',
550
        ],
551
        'CRITBINOM' => [
552
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
553
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::CRITBINOM',
554
            'argumentCount' => '3',
555
        ],
556
        'CUBEKPIMEMBER' => [
557
            'category' => Calculation\Categories::CATEGORY_CUBE,
558
            'functionCall' => 'Calculation\Categories::DUMMY',
559
            'argumentCount' => '?',
560
        ],
561
        'CUBEMEMBER' => [
562
            'category' => Calculation\Categories::CATEGORY_CUBE,
563
            'functionCall' => 'Calculation\Categories::DUMMY',
564
            'argumentCount' => '?',
565
        ],
566
        'CUBEMEMBERPROPERTY' => [
567
            'category' => Calculation\Categories::CATEGORY_CUBE,
568
            'functionCall' => 'Calculation\Categories::DUMMY',
569
            'argumentCount' => '?',
570
        ],
571
        'CUBERANKEDMEMBER' => [
572
            'category' => Calculation\Categories::CATEGORY_CUBE,
573
            'functionCall' => 'Calculation\Categories::DUMMY',
574
            'argumentCount' => '?',
575
        ],
576
        'CUBESET' => [
577
            'category' => Calculation\Categories::CATEGORY_CUBE,
578
            'functionCall' => 'Calculation\Categories::DUMMY',
579
            'argumentCount' => '?',
580
        ],
581
        'CUBESETCOUNT' => [
582
            'category' => Calculation\Categories::CATEGORY_CUBE,
583
            'functionCall' => 'Calculation\Categories::DUMMY',
584
            'argumentCount' => '?',
585
        ],
586
        'CUBEVALUE' => [
587
            'category' => Calculation\Categories::CATEGORY_CUBE,
588
            'functionCall' => 'Calculation\Categories::DUMMY',
589
            'argumentCount' => '?',
590
        ],
591
        'CUMIPMT' => [
592
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
593
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::CUMIPMT',
594
            'argumentCount' => '6',
595
        ],
596
        'CUMPRINC' => [
597
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
598
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::CUMPRINC',
599
            'argumentCount' => '6',
600
        ],
601
        'DATE' => [
602
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
603
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::DATE',
604
            'argumentCount' => '3',
605
        ],
606
        'DATEDIF' => [
607
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
608
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::DATEDIF',
609
            'argumentCount' => '2,3',
610
        ],
611
        'DATEVALUE' => [
612
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
613
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::DATEVALUE',
614
            'argumentCount' => '1',
615
        ],
616
        'DAVERAGE' => [
617
            'category' => Calculation\Categories::CATEGORY_DATABASE,
618
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DAVERAGE',
619
            'argumentCount' => '3',
620
        ],
621
        'DAY' => [
622
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
623
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::DAYOFMONTH',
624
            'argumentCount' => '1',
625
        ],
626
        'DAYS360' => [
627
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
628
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::DAYS360',
629
            'argumentCount' => '2,3',
630
        ],
631
        'DB' => [
632
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
633
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::DB',
634
            'argumentCount' => '4,5',
635
        ],
636
        'DCOUNT' => [
637
            'category' => Calculation\Categories::CATEGORY_DATABASE,
638
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DCOUNT',
639
            'argumentCount' => '3',
640
        ],
641
        'DCOUNTA' => [
642
            'category' => Calculation\Categories::CATEGORY_DATABASE,
643
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DCOUNTA',
644
            'argumentCount' => '3',
645
        ],
646
        'DDB' => [
647
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
648
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::DDB',
649
            'argumentCount' => '4,5',
650
        ],
651
        'DEC2BIN' => [
652
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
653
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::DECTOBIN',
654
            'argumentCount' => '1,2',
655
        ],
656
        'DEC2HEX' => [
657
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
658
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::DECTOHEX',
659
            'argumentCount' => '1,2',
660
        ],
661
        'DEC2OCT' => [
662
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
663
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::DECTOOCT',
664
            'argumentCount' => '1,2',
665
        ],
666
        'DEGREES' => [
667
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
668
            'functionCall' => 'rad2deg',
669
            'argumentCount' => '1',
670
        ],
671
        'DELTA' => [
672
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
673
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::DELTA',
674
            'argumentCount' => '1,2',
675
        ],
676
        'DEVSQ' => [
677
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
678
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::DEVSQ',
679
            'argumentCount' => '1+',
680
        ],
681
        'DGET' => [
682
            'category' => Calculation\Categories::CATEGORY_DATABASE,
683
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DGET',
684
            'argumentCount' => '3',
685
        ],
686
        'DISC' => [
687
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
688
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::DISC',
689
            'argumentCount' => '4,5',
690
        ],
691
        'DMAX' => [
692
            'category' => Calculation\Categories::CATEGORY_DATABASE,
693
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DMAX',
694
            'argumentCount' => '3',
695
        ],
696
        'DMIN' => [
697
            'category' => Calculation\Categories::CATEGORY_DATABASE,
698
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DMIN',
699
            'argumentCount' => '3',
700
        ],
701
        'DOLLAR' => [
702
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
703
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::DOLLAR',
704
            'argumentCount' => '1,2',
705
        ],
706
        'DOLLARDE' => [
707
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
708
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::DOLLARDE',
709
            'argumentCount' => '2',
710
        ],
711
        'DOLLARFR' => [
712
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
713
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::DOLLARFR',
714
            'argumentCount' => '2',
715
        ],
716
        'DPRODUCT' => [
717
            'category' => Calculation\Categories::CATEGORY_DATABASE,
718
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DPRODUCT',
719
            'argumentCount' => '3',
720
        ],
721
        'DSTDEV' => [
722
            'category' => Calculation\Categories::CATEGORY_DATABASE,
723
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DSTDEV',
724
            'argumentCount' => '3',
725
        ],
726
        'DSTDEVP' => [
727
            'category' => Calculation\Categories::CATEGORY_DATABASE,
728
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DSTDEVP',
729
            'argumentCount' => '3',
730
        ],
731
        'DSUM' => [
732
            'category' => Calculation\Categories::CATEGORY_DATABASE,
733
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DSUM',
734
            'argumentCount' => '3',
735
        ],
736
        'DURATION' => [
737
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
738
            'functionCall' => 'Calculation\Categories::DUMMY',
739
            'argumentCount' => '5,6',
740
        ],
741
        'DVAR' => [
742
            'category' => Calculation\Categories::CATEGORY_DATABASE,
743
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DVAR',
744
            'argumentCount' => '3',
745
        ],
746
        'DVARP' => [
747
            'category' => Calculation\Categories::CATEGORY_DATABASE,
748
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Database::DVARP',
749
            'argumentCount' => '3',
750
        ],
751
        'EDATE' => [
752
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
753
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::EDATE',
754
            'argumentCount' => '2',
755
        ],
756
        'EFFECT' => [
757
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
758
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::EFFECT',
759
            'argumentCount' => '2',
760
        ],
761
        'EOMONTH' => [
762
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
763
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::EOMONTH',
764
            'argumentCount' => '2',
765
        ],
766
        'ERF' => [
767
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
768
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::ERF',
769
            'argumentCount' => '1,2',
770
        ],
771
        'ERFC' => [
772
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
773
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::ERFC',
774
            'argumentCount' => '1',
775
        ],
776
        'ERROR.TYPE' => [
777
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
778
            'functionCall' => 'Calculation\Categories::errorType',
779
            'argumentCount' => '1',
780
        ],
781
        'EVEN' => [
782
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
783
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::EVEN',
784
            'argumentCount' => '1',
785
        ],
786
        'EXACT' => [
787
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
788
            'functionCall' => 'Calculation\Categories::DUMMY',
789
            'argumentCount' => '2',
790
        ],
791
        'EXP' => [
792
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
793
            'functionCall' => 'exp',
794
            'argumentCount' => '1',
795
        ],
796
        'EXPONDIST' => [
797
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
798
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::EXPONDIST',
799
            'argumentCount' => '3',
800
        ],
801
        'FACT' => [
802
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
803
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::FACT',
804
            'argumentCount' => '1',
805
        ],
806
        'FACTDOUBLE' => [
807
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
808
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::FACTDOUBLE',
809
            'argumentCount' => '1',
810
        ],
811
        'FALSE' => [
812
            'category' => Calculation\Categories::CATEGORY_LOGICAL,
813
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Logical::FALSE',
814
            'argumentCount' => '0',
815
        ],
816
        'FDIST' => [
817
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
818
            'functionCall' => 'Calculation\Categories::DUMMY',
819
            'argumentCount' => '3',
820
        ],
821
        'FIND' => [
822
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
823
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::SEARCHSENSITIVE',
824
            'argumentCount' => '2,3',
825
        ],
826
        'FINDB' => [
827
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
828
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::SEARCHSENSITIVE',
829
            'argumentCount' => '2,3',
830
        ],
831
        'FINV' => [
832
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
833
            'functionCall' => 'Calculation\Categories::DUMMY',
834
            'argumentCount' => '3',
835
        ],
836
        'FISHER' => [
837
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
838
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::FISHER',
839
            'argumentCount' => '1',
840
        ],
841
        'FISHERINV' => [
842
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
843
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::FISHERINV',
844
            'argumentCount' => '1',
845
        ],
846
        'FIXED' => [
847
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
848
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::FIXEDFORMAT',
849
            'argumentCount' => '1-3',
850
        ],
851
        'FLOOR' => [
852
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
853
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::FLOOR',
854
            'argumentCount' => '2',
855
        ],
856
        'FORECAST' => [
857
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
858
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::FORECAST',
859
            'argumentCount' => '3',
860
        ],
861
        'FREQUENCY' => [
862
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
863
            'functionCall' => 'Calculation\Categories::DUMMY',
864
            'argumentCount' => '2',
865
        ],
866
        'FTEST' => [
867
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
868
            'functionCall' => 'Calculation\Categories::DUMMY',
869
            'argumentCount' => '2',
870
        ],
871
        'FV' => [
872
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
873
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::FV',
874
            'argumentCount' => '3-5',
875
        ],
876
        'FVSCHEDULE' => [
877
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
878
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::FVSCHEDULE',
879
            'argumentCount' => '2',
880
        ],
881
        'GAMMADIST' => [
882
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
883
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::GAMMADIST',
884
            'argumentCount' => '4',
885
        ],
886
        'GAMMAINV' => [
887
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
888
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::GAMMAINV',
889
            'argumentCount' => '3',
890
        ],
891
        'GAMMALN' => [
892
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
893
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::GAMMALN',
894
            'argumentCount' => '1',
895
        ],
896
        'GCD' => [
897
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
898
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::GCD',
899
            'argumentCount' => '1+',
900
        ],
901
        'GEOMEAN' => [
902
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
903
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::GEOMEAN',
904
            'argumentCount' => '1+',
905
        ],
906
        'GESTEP' => [
907
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
908
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::GESTEP',
909
            'argumentCount' => '1,2',
910
        ],
911
        'GETPIVOTDATA' => [
912
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
913
            'functionCall' => 'Calculation\Categories::DUMMY',
914
            'argumentCount' => '2+',
915
        ],
916
        'GROWTH' => [
917
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
918
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::GROWTH',
919
            'argumentCount' => '1-4',
920
        ],
921
        'HARMEAN' => [
922
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
923
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::HARMEAN',
924
            'argumentCount' => '1+',
925
        ],
926
        'HEX2BIN' => [
927
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
928
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::HEXTOBIN',
929
            'argumentCount' => '1,2',
930
        ],
931
        'HEX2DEC' => [
932
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
933
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::HEXTODEC',
934
            'argumentCount' => '1',
935
        ],
936
        'HEX2OCT' => [
937
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
938
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::HEXTOOCT',
939
            'argumentCount' => '1,2',
940
        ],
941
        'HLOOKUP' => [
942
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
943
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::HLOOKUP',
944
            'argumentCount' => '3,4',
945
        ],
946
        'HOUR' => [
947
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
948
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::HOUROFDAY',
949
            'argumentCount' => '1',
950
        ],
951
        'HYPERLINK' => [
952
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
953
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::HYPERLINK',
954
            'argumentCount' => '1,2',
955
            'passCellReference' => true,
956
        ],
957
        'HYPGEOMDIST' => [
958
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
959
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::HYPGEOMDIST',
960
            'argumentCount' => '4',
961
        ],
962
        'IF' => [
963
            'category' => Calculation\Categories::CATEGORY_LOGICAL,
964
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Logical::statementIf',
965
            'argumentCount' => '1-3',
966
        ],
967
        'IFERROR' => [
968
            'category' => Calculation\Categories::CATEGORY_LOGICAL,
969
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Logical::IFERROR',
970
            'argumentCount' => '2',
971
        ],
972
        'IMABS' => [
973
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
974
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMABS',
975
            'argumentCount' => '1',
976
        ],
977
        'IMAGINARY' => [
978
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
979
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMAGINARY',
980
            'argumentCount' => '1',
981
        ],
982
        'IMARGUMENT' => [
983
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
984
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMARGUMENT',
985
            'argumentCount' => '1',
986
        ],
987
        'IMCONJUGATE' => [
988
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
989
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMCONJUGATE',
990
            'argumentCount' => '1',
991
        ],
992
        'IMCOS' => [
993
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
994
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMCOS',
995
            'argumentCount' => '1',
996
        ],
997
        'IMDIV' => [
998
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
999
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMDIV',
1000
            'argumentCount' => '2',
1001
        ],
1002
        'IMEXP' => [
1003
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1004
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMEXP',
1005
            'argumentCount' => '1',
1006
        ],
1007
        'IMLN' => [
1008
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1009
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMLN',
1010
            'argumentCount' => '1',
1011
        ],
1012
        'IMLOG10' => [
1013
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1014
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMLOG10',
1015
            'argumentCount' => '1',
1016
        ],
1017
        'IMLOG2' => [
1018
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1019
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMLOG2',
1020
            'argumentCount' => '1',
1021
        ],
1022
        'IMPOWER' => [
1023
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1024
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMPOWER',
1025
            'argumentCount' => '2',
1026
        ],
1027
        'IMPRODUCT' => [
1028
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1029
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMPRODUCT',
1030
            'argumentCount' => '1+',
1031
        ],
1032
        'IMREAL' => [
1033
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1034
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMREAL',
1035
            'argumentCount' => '1',
1036
        ],
1037
        'IMSIN' => [
1038
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1039
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMSIN',
1040
            'argumentCount' => '1',
1041
        ],
1042
        'IMSQRT' => [
1043
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1044
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMSQRT',
1045
            'argumentCount' => '1',
1046
        ],
1047
        'IMSUB' => [
1048
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1049
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMSUB',
1050
            'argumentCount' => '2',
1051
        ],
1052
        'IMSUM' => [
1053
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1054
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::IMSUM',
1055
            'argumentCount' => '1+',
1056
        ],
1057
        'INDEX' => [
1058
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1059
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::INDEX',
1060
            'argumentCount' => '1-4',
1061
        ],
1062
        'INDIRECT' => [
1063
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1064
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::INDIRECT',
1065
            'argumentCount' => '1,2',
1066
            'passCellReference' => true,
1067
        ],
1068
        'INFO' => [
1069
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1070
            'functionCall' => 'Calculation\Categories::DUMMY',
1071
            'argumentCount' => '1',
1072
        ],
1073
        'INT' => [
1074
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1075
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::INT',
1076
            'argumentCount' => '1',
1077
        ],
1078
        'INTERCEPT' => [
1079
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1080
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::INTERCEPT',
1081
            'argumentCount' => '2',
1082
        ],
1083
        'INTRATE' => [
1084
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1085
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::INTRATE',
1086
            'argumentCount' => '4,5',
1087
        ],
1088
        'IPMT' => [
1089
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1090
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::IPMT',
1091
            'argumentCount' => '4-6',
1092
        ],
1093
        'IRR' => [
1094
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1095
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::IRR',
1096
            'argumentCount' => '1,2',
1097
        ],
1098
        'ISBLANK' => [
1099
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1100
            'functionCall' => 'Calculation\Categories::isBlank',
1101
            'argumentCount' => '1',
1102
        ],
1103
        'ISERR' => [
1104
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1105
            'functionCall' => 'Calculation\Categories::IS_ERR',
1106
            'argumentCount' => '1',
1107
        ],
1108
        'ISERROR' => [
1109
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1110
            'functionCall' => 'Calculation\Categories::IS_ERROR',
1111
            'argumentCount' => '1',
1112
        ],
1113
        'ISEVEN' => [
1114
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1115
            'functionCall' => 'Calculation\Categories::isEven',
1116
            'argumentCount' => '1',
1117
        ],
1118
        'ISLOGICAL' => [
1119
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1120
            'functionCall' => 'Calculation\Categories::isLogical',
1121
            'argumentCount' => '1',
1122
        ],
1123
        'ISNA' => [
1124
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1125
            'functionCall' => 'Calculation\Categories::isNa',
1126
            'argumentCount' => '1',
1127
        ],
1128
        'ISNONTEXT' => [
1129
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1130
            'functionCall' => 'Calculation\Categories::isNonText',
1131
            'argumentCount' => '1',
1132
        ],
1133
        'ISNUMBER' => [
1134
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1135
            'functionCall' => 'Calculation\Categories::isNumber',
1136
            'argumentCount' => '1',
1137
        ],
1138
        'ISODD' => [
1139
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1140
            'functionCall' => 'Calculation\Categories::isOdd',
1141
            'argumentCount' => '1',
1142
        ],
1143
        'ISPMT' => [
1144
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1145
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::ISPMT',
1146
            'argumentCount' => '4',
1147
        ],
1148
        'ISREF' => [
1149
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1150
            'functionCall' => 'Calculation\Categories::DUMMY',
1151
            'argumentCount' => '1',
1152
        ],
1153
        'ISTEXT' => [
1154
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1155
            'functionCall' => 'Calculation\Categories::isText',
1156
            'argumentCount' => '1',
1157
        ],
1158
        'JIS' => [
1159
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1160
            'functionCall' => 'Calculation\Categories::DUMMY',
1161
            'argumentCount' => '1',
1162
        ],
1163
        'KURT' => [
1164
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1165
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::KURT',
1166
            'argumentCount' => '1+',
1167
        ],
1168
        'LARGE' => [
1169
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1170
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::LARGE',
1171
            'argumentCount' => '2',
1172
        ],
1173
        'LCM' => [
1174
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1175
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::LCM',
1176
            'argumentCount' => '1+',
1177
        ],
1178
        'LEFT' => [
1179
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1180
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::LEFT',
1181
            'argumentCount' => '1,2',
1182
        ],
1183
        'LEFTB' => [
1184
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1185
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::LEFT',
1186
            'argumentCount' => '1,2',
1187
        ],
1188
        'LEN' => [
1189
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1190
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::STRINGLENGTH',
1191
            'argumentCount' => '1',
1192
        ],
1193
        'LENB' => [
1194
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1195
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::STRINGLENGTH',
1196
            'argumentCount' => '1',
1197
        ],
1198
        'LINEST' => [
1199
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1200
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::LINEST',
1201
            'argumentCount' => '1-4',
1202
        ],
1203
        'LN' => [
1204
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1205
            'functionCall' => 'log',
1206
            'argumentCount' => '1',
1207
        ],
1208
        'LOG' => [
1209
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1210
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::logBase',
1211
            'argumentCount' => '1,2',
1212
        ],
1213
        'LOG10' => [
1214
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1215
            'functionCall' => 'log10',
1216
            'argumentCount' => '1',
1217
        ],
1218
        'LOGEST' => [
1219
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1220
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::LOGEST',
1221
            'argumentCount' => '1-4',
1222
        ],
1223
        'LOGINV' => [
1224
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1225
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::LOGINV',
1226
            'argumentCount' => '3',
1227
        ],
1228
        'LOGNORMDIST' => [
1229
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1230
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::LOGNORMDIST',
1231
            'argumentCount' => '3',
1232
        ],
1233
        'LOOKUP' => [
1234
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1235
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::LOOKUP',
1236
            'argumentCount' => '2,3',
1237
        ],
1238
        'LOWER' => [
1239
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1240
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::LOWERCASE',
1241
            'argumentCount' => '1',
1242
        ],
1243
        'MATCH' => [
1244
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1245
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::MATCH',
1246
            'argumentCount' => '2,3',
1247
        ],
1248
        'MAX' => [
1249
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1250
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::MAX',
1251
            'argumentCount' => '1+',
1252
        ],
1253
        'MAXA' => [
1254
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1255
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::MAXA',
1256
            'argumentCount' => '1+',
1257
        ],
1258
        'MAXIF' => [
1259
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1260
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::MAXIF',
1261
            'argumentCount' => '2+',
1262
        ],
1263
        'MDETERM' => [
1264
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1265
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::MDETERM',
1266
            'argumentCount' => '1',
1267
        ],
1268
        'MDURATION' => [
1269
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1270
            'functionCall' => 'Calculation\Categories::DUMMY',
1271
            'argumentCount' => '5,6',
1272
        ],
1273
        'MEDIAN' => [
1274
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1275
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::MEDIAN',
1276
            'argumentCount' => '1+',
1277
        ],
1278
        'MEDIANIF' => [
1279
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1280
            'functionCall' => 'Calculation\Categories::DUMMY',
1281
            'argumentCount' => '2+',
1282
        ],
1283
        'MID' => [
1284
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1285
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::MID',
1286
            'argumentCount' => '3',
1287
        ],
1288
        'MIDB' => [
1289
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1290
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::MID',
1291
            'argumentCount' => '3',
1292
        ],
1293
        'MIN' => [
1294
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1295
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::MIN',
1296
            'argumentCount' => '1+',
1297
        ],
1298
        'MINA' => [
1299
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1300
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::MINA',
1301
            'argumentCount' => '1+',
1302
        ],
1303
        'MINIF' => [
1304
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1305
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::MINIF',
1306
            'argumentCount' => '2+',
1307
        ],
1308
        'MINUTE' => [
1309
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1310
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::MINUTEOFHOUR',
1311
            'argumentCount' => '1',
1312
        ],
1313
        'MINVERSE' => [
1314
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1315
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::MINVERSE',
1316
            'argumentCount' => '1',
1317
        ],
1318
        'MIRR' => [
1319
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1320
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::MIRR',
1321
            'argumentCount' => '3',
1322
        ],
1323
        'MMULT' => [
1324
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1325
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::MMULT',
1326
            'argumentCount' => '2',
1327
        ],
1328
        'MOD' => [
1329
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1330
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::MOD',
1331
            'argumentCount' => '2',
1332
        ],
1333
        'MODE' => [
1334
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1335
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::MODE',
1336
            'argumentCount' => '1+',
1337
        ],
1338
        'MONTH' => [
1339
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1340
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::MONTHOFYEAR',
1341
            'argumentCount' => '1',
1342
        ],
1343
        'MROUND' => [
1344
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1345
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::MROUND',
1346
            'argumentCount' => '2',
1347
        ],
1348
        'MULTINOMIAL' => [
1349
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1350
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::MULTINOMIAL',
1351
            'argumentCount' => '1+',
1352
        ],
1353
        'N' => [
1354
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1355
            'functionCall' => 'Calculation\Categories::N',
1356
            'argumentCount' => '1',
1357
        ],
1358
        'NA' => [
1359
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1360
            'functionCall' => 'Calculation\Categories::NA',
1361
            'argumentCount' => '0',
1362
        ],
1363
        'NEGBINOMDIST' => [
1364
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1365
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::NEGBINOMDIST',
1366
            'argumentCount' => '3',
1367
        ],
1368
        'NETWORKDAYS' => [
1369
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1370
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::NETWORKDAYS',
1371
            'argumentCount' => '2+',
1372
        ],
1373
        'NOMINAL' => [
1374
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1375
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::NOMINAL',
1376
            'argumentCount' => '2',
1377
        ],
1378
        'NORMDIST' => [
1379
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1380
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::NORMDIST',
1381
            'argumentCount' => '4',
1382
        ],
1383
        'NORMINV' => [
1384
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1385
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::NORMINV',
1386
            'argumentCount' => '3',
1387
        ],
1388
        'NORMSDIST' => [
1389
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1390
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::NORMSDIST',
1391
            'argumentCount' => '1',
1392
        ],
1393
        'NORMSINV' => [
1394
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1395
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::NORMSINV',
1396
            'argumentCount' => '1',
1397
        ],
1398
        'NOT' => [
1399
            'category' => Calculation\Categories::CATEGORY_LOGICAL,
1400
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Logical::NOT',
1401
            'argumentCount' => '1',
1402
        ],
1403
        'NOW' => [
1404
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1405
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::DATETIMENOW',
1406
            'argumentCount' => '0',
1407
        ],
1408
        'NPER' => [
1409
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1410
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::NPER',
1411
            'argumentCount' => '3-5',
1412
        ],
1413
        'NPV' => [
1414
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1415
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::NPV',
1416
            'argumentCount' => '2+',
1417
        ],
1418
        'OCT2BIN' => [
1419
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1420
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::OCTTOBIN',
1421
            'argumentCount' => '1,2',
1422
        ],
1423
        'OCT2DEC' => [
1424
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1425
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::OCTTODEC',
1426
            'argumentCount' => '1',
1427
        ],
1428
        'OCT2HEX' => [
1429
            'category' => Calculation\Categories::CATEGORY_ENGINEERING,
1430
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Engineering::OCTTOHEX',
1431
            'argumentCount' => '1,2',
1432
        ],
1433
        'ODD' => [
1434
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1435
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::ODD',
1436
            'argumentCount' => '1',
1437
        ],
1438
        'ODDFPRICE' => [
1439
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1440
            'functionCall' => 'Calculation\Categories::DUMMY',
1441
            'argumentCount' => '8,9',
1442
        ],
1443
        'ODDFYIELD' => [
1444
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1445
            'functionCall' => 'Calculation\Categories::DUMMY',
1446
            'argumentCount' => '8,9',
1447
        ],
1448
        'ODDLPRICE' => [
1449
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1450
            'functionCall' => 'Calculation\Categories::DUMMY',
1451
            'argumentCount' => '7,8',
1452
        ],
1453
        'ODDLYIELD' => [
1454
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1455
            'functionCall' => 'Calculation\Categories::DUMMY',
1456
            'argumentCount' => '7,8',
1457
        ],
1458
        'OFFSET' => [
1459
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1460
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::OFFSET',
1461
            'argumentCount' => '3-5',
1462
            'passCellReference' => true,
1463
            'passByReference' => [true],
1464
        ],
1465
        'OR' => [
1466
            'category' => Calculation\Categories::CATEGORY_LOGICAL,
1467
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Logical::logicalOr',
1468
            'argumentCount' => '1+',
1469
        ],
1470
        'PEARSON' => [
1471
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1472
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::CORREL',
1473
            'argumentCount' => '2',
1474
        ],
1475
        'PERCENTILE' => [
1476
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1477
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::PERCENTILE',
1478
            'argumentCount' => '2',
1479
        ],
1480
        'PERCENTRANK' => [
1481
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1482
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::PERCENTRANK',
1483
            'argumentCount' => '2,3',
1484
        ],
1485
        'PERMUT' => [
1486
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1487
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::PERMUT',
1488
            'argumentCount' => '2',
1489
        ],
1490
        'PHONETIC' => [
1491
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1492
            'functionCall' => 'Calculation\Categories::DUMMY',
1493
            'argumentCount' => '1',
1494
        ],
1495
        'PI' => [
1496
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1497
            'functionCall' => 'pi',
1498
            'argumentCount' => '0',
1499
        ],
1500
        'PMT' => [
1501
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1502
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::PMT',
1503
            'argumentCount' => '3-5',
1504
        ],
1505
        'POISSON' => [
1506
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1507
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::POISSON',
1508
            'argumentCount' => '3',
1509
        ],
1510
        'POWER' => [
1511
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1512
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::POWER',
1513
            'argumentCount' => '2',
1514
        ],
1515
        'PPMT' => [
1516
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1517
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::PPMT',
1518
            'argumentCount' => '4-6',
1519
        ],
1520
        'PRICE' => [
1521
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1522
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::PRICE',
1523
            'argumentCount' => '6,7',
1524
        ],
1525
        'PRICEDISC' => [
1526
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1527
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::PRICEDISC',
1528
            'argumentCount' => '4,5',
1529
        ],
1530
        'PRICEMAT' => [
1531
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1532
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::PRICEMAT',
1533
            'argumentCount' => '5,6',
1534
        ],
1535
        'PROB' => [
1536
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1537
            'functionCall' => 'Calculation\Categories::DUMMY',
1538
            'argumentCount' => '3,4',
1539
        ],
1540
        'PRODUCT' => [
1541
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1542
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::PRODUCT',
1543
            'argumentCount' => '1+',
1544
        ],
1545
        'PROPER' => [
1546
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1547
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::PROPERCASE',
1548
            'argumentCount' => '1',
1549
        ],
1550
        'PV' => [
1551
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1552
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::PV',
1553
            'argumentCount' => '3-5',
1554
        ],
1555
        'QUARTILE' => [
1556
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1557
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::QUARTILE',
1558
            'argumentCount' => '2',
1559
        ],
1560
        'QUOTIENT' => [
1561
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1562
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::QUOTIENT',
1563
            'argumentCount' => '2',
1564
        ],
1565
        'RADIANS' => [
1566
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1567
            'functionCall' => 'deg2rad',
1568
            'argumentCount' => '1',
1569
        ],
1570
        'RAND' => [
1571
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1572
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::RAND',
1573
            'argumentCount' => '0',
1574
        ],
1575
        'RANDBETWEEN' => [
1576
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1577
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::RAND',
1578
            'argumentCount' => '2',
1579
        ],
1580
        'RANK' => [
1581
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1582
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::RANK',
1583
            'argumentCount' => '2,3',
1584
        ],
1585
        'RATE' => [
1586
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1587
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::RATE',
1588
            'argumentCount' => '3-6',
1589
        ],
1590
        'RECEIVED' => [
1591
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1592
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::RECEIVED',
1593
            'argumentCount' => '4-5',
1594
        ],
1595
        'REPLACE' => [
1596
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1597
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::REPLACE',
1598
            'argumentCount' => '4',
1599
        ],
1600
        'REPLACEB' => [
1601
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1602
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::REPLACE',
1603
            'argumentCount' => '4',
1604
        ],
1605
        'REPT' => [
1606
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1607
            'functionCall' => 'str_repeat',
1608
            'argumentCount' => '2',
1609
        ],
1610
        'RIGHT' => [
1611
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1612
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::RIGHT',
1613
            'argumentCount' => '1,2',
1614
        ],
1615
        'RIGHTB' => [
1616
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1617
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::RIGHT',
1618
            'argumentCount' => '1,2',
1619
        ],
1620
        'ROMAN' => [
1621
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1622
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::ROMAN',
1623
            'argumentCount' => '1,2',
1624
        ],
1625
        'ROUND' => [
1626
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1627
            'functionCall' => 'round',
1628
            'argumentCount' => '2',
1629
        ],
1630
        'ROUNDDOWN' => [
1631
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1632
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::ROUNDDOWN',
1633
            'argumentCount' => '2',
1634
        ],
1635
        'ROUNDUP' => [
1636
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1637
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::ROUNDUP',
1638
            'argumentCount' => '2',
1639
        ],
1640
        'ROW' => [
1641
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1642
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::ROW',
1643
            'argumentCount' => '-1',
1644
            'passByReference' => [true],
1645
        ],
1646
        'ROWS' => [
1647
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1648
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::ROWS',
1649
            'argumentCount' => '1',
1650
        ],
1651
        'RSQ' => [
1652
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1653
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::RSQ',
1654
            'argumentCount' => '2',
1655
        ],
1656
        'RTD' => [
1657
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1658
            'functionCall' => 'Calculation\Categories::DUMMY',
1659
            'argumentCount' => '1+',
1660
        ],
1661
        'SEARCH' => [
1662
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1663
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::SEARCHINSENSITIVE',
1664
            'argumentCount' => '2,3',
1665
        ],
1666
        'SEARCHB' => [
1667
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1668
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::SEARCHINSENSITIVE',
1669
            'argumentCount' => '2,3',
1670
        ],
1671
        'SECOND' => [
1672
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1673
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::SECONDOFMINUTE',
1674
            'argumentCount' => '1',
1675
        ],
1676
        'SERIESSUM' => [
1677
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1678
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SERIESSUM',
1679
            'argumentCount' => '4',
1680
        ],
1681
        'SIGN' => [
1682
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1683
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SIGN',
1684
            'argumentCount' => '1',
1685
        ],
1686
        'SIN' => [
1687
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1688
            'functionCall' => 'sin',
1689
            'argumentCount' => '1',
1690
        ],
1691
        'SINH' => [
1692
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1693
            'functionCall' => 'sinh',
1694
            'argumentCount' => '1',
1695
        ],
1696
        'SKEW' => [
1697
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1698
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::SKEW',
1699
            'argumentCount' => '1+',
1700
        ],
1701
        'SLN' => [
1702
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1703
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::SLN',
1704
            'argumentCount' => '3',
1705
        ],
1706
        'SLOPE' => [
1707
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1708
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::SLOPE',
1709
            'argumentCount' => '2',
1710
        ],
1711
        'SMALL' => [
1712
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1713
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::SMALL',
1714
            'argumentCount' => '2',
1715
        ],
1716
        'SQRT' => [
1717
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1718
            'functionCall' => 'sqrt',
1719
            'argumentCount' => '1',
1720
        ],
1721
        'SQRTPI' => [
1722
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1723
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SQRTPI',
1724
            'argumentCount' => '1',
1725
        ],
1726
        'STANDARDIZE' => [
1727
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1728
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::STANDARDIZE',
1729
            'argumentCount' => '3',
1730
        ],
1731
        'STDEV' => [
1732
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1733
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::STDEV',
1734
            'argumentCount' => '1+',
1735
        ],
1736
        'STDEVA' => [
1737
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1738
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::STDEVA',
1739
            'argumentCount' => '1+',
1740
        ],
1741
        'STDEVP' => [
1742
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1743
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::STDEVP',
1744
            'argumentCount' => '1+',
1745
        ],
1746
        'STDEVPA' => [
1747
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1748
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::STDEVPA',
1749
            'argumentCount' => '1+',
1750
        ],
1751
        'STEYX' => [
1752
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1753
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::STEYX',
1754
            'argumentCount' => '2',
1755
        ],
1756
        'SUBSTITUTE' => [
1757
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1758
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::SUBSTITUTE',
1759
            'argumentCount' => '3,4',
1760
        ],
1761
        'SUBTOTAL' => [
1762
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1763
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SUBTOTAL',
1764
            'argumentCount' => '2+',
1765
        ],
1766
        'SUM' => [
1767
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1768
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SUM',
1769
            'argumentCount' => '1+',
1770
        ],
1771
        'SUMIF' => [
1772
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1773
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SUMIF',
1774
            'argumentCount' => '2,3',
1775
        ],
1776
        'SUMIFS' => [
1777
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1778
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SUMIFS',
1779
            'argumentCount' => '3+',
1780
        ],
1781
        'SUMPRODUCT' => [
1782
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1783
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SUMPRODUCT',
1784
            'argumentCount' => '1+',
1785
        ],
1786
        'SUMSQ' => [
1787
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1788
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SUMSQ',
1789
            'argumentCount' => '1+',
1790
        ],
1791
        'SUMX2MY2' => [
1792
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1793
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SUMX2MY2',
1794
            'argumentCount' => '2',
1795
        ],
1796
        'SUMX2PY2' => [
1797
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1798
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SUMX2PY2',
1799
            'argumentCount' => '2',
1800
        ],
1801
        'SUMXMY2' => [
1802
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1803
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::SUMXMY2',
1804
            'argumentCount' => '2',
1805
        ],
1806
        'SYD' => [
1807
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1808
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::SYD',
1809
            'argumentCount' => '4',
1810
        ],
1811
        'T' => [
1812
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1813
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::RETURNSTRING',
1814
            'argumentCount' => '1',
1815
        ],
1816
        'TAN' => [
1817
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1818
            'functionCall' => 'tan',
1819
            'argumentCount' => '1',
1820
        ],
1821
        'TANH' => [
1822
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1823
            'functionCall' => 'tanh',
1824
            'argumentCount' => '1',
1825
        ],
1826
        'TBILLEQ' => [
1827
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1828
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::TBILLEQ',
1829
            'argumentCount' => '3',
1830
        ],
1831
        'TBILLPRICE' => [
1832
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1833
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::TBILLPRICE',
1834
            'argumentCount' => '3',
1835
        ],
1836
        'TBILLYIELD' => [
1837
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1838
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::TBILLYIELD',
1839
            'argumentCount' => '3',
1840
        ],
1841
        'TDIST' => [
1842
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1843
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::TDIST',
1844
            'argumentCount' => '3',
1845
        ],
1846
        'TEXT' => [
1847
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1848
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::TEXTFORMAT',
1849
            'argumentCount' => '2',
1850
        ],
1851
        'TIME' => [
1852
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1853
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::TIME',
1854
            'argumentCount' => '3',
1855
        ],
1856
        'TIMEVALUE' => [
1857
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1858
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::TIMEVALUE',
1859
            'argumentCount' => '1',
1860
        ],
1861
        'TINV' => [
1862
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1863
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::TINV',
1864
            'argumentCount' => '2',
1865
        ],
1866
        'TODAY' => [
1867
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1868
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::DATENOW',
1869
            'argumentCount' => '0',
1870
        ],
1871
        'TRANSPOSE' => [
1872
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1873
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::TRANSPOSE',
1874
            'argumentCount' => '1',
1875
        ],
1876
        'TREND' => [
1877
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1878
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::TREND',
1879
            'argumentCount' => '1-4',
1880
        ],
1881
        'TRIM' => [
1882
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1883
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::TRIMSPACES',
1884
            'argumentCount' => '1',
1885
        ],
1886
        'TRIMMEAN' => [
1887
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1888
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::TRIMMEAN',
1889
            'argumentCount' => '2',
1890
        ],
1891
        'TRUE' => [
1892
            'category' => Calculation\Categories::CATEGORY_LOGICAL,
1893
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Logical::TRUE',
1894
            'argumentCount' => '0',
1895
        ],
1896
        'TRUNC' => [
1897
            'category' => Calculation\Categories::CATEGORY_MATH_AND_TRIG,
1898
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\MathTrig::TRUNC',
1899
            'argumentCount' => '1,2',
1900
        ],
1901
        'TTEST' => [
1902
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1903
            'functionCall' => 'Calculation\Categories::DUMMY',
1904
            'argumentCount' => '4',
1905
        ],
1906
        'TYPE' => [
1907
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1908
            'functionCall' => 'Calculation\Categories::TYPE',
1909
            'argumentCount' => '1',
1910
        ],
1911
        'UPPER' => [
1912
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1913
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::UPPERCASE',
1914
            'argumentCount' => '1',
1915
        ],
1916
        'USDOLLAR' => [
1917
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1918
            'functionCall' => 'Calculation\Categories::DUMMY',
1919
            'argumentCount' => '2',
1920
        ],
1921
        'VALUE' => [
1922
            'category' => Calculation\Categories::CATEGORY_TEXT_AND_DATA,
1923
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\TextData::VALUE',
1924
            'argumentCount' => '1',
1925
        ],
1926
        'VAR' => [
1927
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1928
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::VARFunc',
1929
            'argumentCount' => '1+',
1930
        ],
1931
        'VARA' => [
1932
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1933
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::VARA',
1934
            'argumentCount' => '1+',
1935
        ],
1936
        'VARP' => [
1937
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1938
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::VARP',
1939
            'argumentCount' => '1+',
1940
        ],
1941
        'VARPA' => [
1942
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1943
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::VARPA',
1944
            'argumentCount' => '1+',
1945
        ],
1946
        'VDB' => [
1947
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1948
            'functionCall' => 'Calculation\Categories::DUMMY',
1949
            'argumentCount' => '5-7',
1950
        ],
1951
        'VERSION' => [
1952
            'category' => Calculation\Categories::CATEGORY_INFORMATION,
1953
            'functionCall' => 'Calculation\Categories::VERSION',
1954
            'argumentCount' => '0',
1955
        ],
1956
        'VLOOKUP' => [
1957
            'category' => Calculation\Categories::CATEGORY_LOOKUP_AND_REFERENCE,
1958
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\LookupRef::VLOOKUP',
1959
            'argumentCount' => '3,4',
1960
        ],
1961
        'WEEKDAY' => [
1962
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1963
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::DAYOFWEEK',
1964
            'argumentCount' => '1,2',
1965
        ],
1966
        'WEEKNUM' => [
1967
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1968
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::WEEKOFYEAR',
1969
            'argumentCount' => '1,2',
1970
        ],
1971
        'WEIBULL' => [
1972
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
1973
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::WEIBULL',
1974
            'argumentCount' => '4',
1975
        ],
1976
        'WORKDAY' => [
1977
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1978
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::WORKDAY',
1979
            'argumentCount' => '2+',
1980
        ],
1981
        'XIRR' => [
1982
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1983
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::XIRR',
1984
            'argumentCount' => '2,3',
1985
        ],
1986
        'XNPV' => [
1987
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
1988
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::XNPV',
1989
            'argumentCount' => '3',
1990
        ],
1991
        'YEAR' => [
1992
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1993
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::YEAR',
1994
            'argumentCount' => '1',
1995
        ],
1996
        'YEARFRAC' => [
1997
            'category' => Calculation\Categories::CATEGORY_DATE_AND_TIME,
1998
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\DateTime::YEARFRAC',
1999
            'argumentCount' => '2,3',
2000
        ],
2001
        'YIELD' => [
2002
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
2003
            'functionCall' => 'Calculation\Categories::DUMMY',
2004
            'argumentCount' => '6,7',
2005
        ],
2006
        'YIELDDISC' => [
2007
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
2008
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::YIELDDISC',
2009
            'argumentCount' => '4,5',
2010
        ],
2011
        'YIELDMAT' => [
2012
            'category' => Calculation\Categories::CATEGORY_FINANCIAL,
2013
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Financial::YIELDMAT',
2014
            'argumentCount' => '5,6',
2015
        ],
2016
        'ZTEST' => [
2017
            'category' => Calculation\Categories::CATEGORY_STATISTICAL,
2018
            'functionCall' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical::ZTEST',
2019
            'argumentCount' => '2-3',
2020
        ],
2021
    ];
2022
2023
    //    Internal functions used for special control purposes
2024
    private static $controlFunctions = [
2025
        'MKMATRIX' => [
2026
            'argumentCount' => '*',
2027
            'functionCall' => 'self::mkMatrix',
2028
        ],
2029
    ];
2030
2031 2
    public function __construct(Spreadsheet $spreadsheet = null)
2032
    {
2033 2
        $this->delta = 1 * pow(10, 0 - ini_get('precision'));
0 ignored issues
show
Documentation Bug introduced by
It seems like 1 * pow(10, 0 - ini_get('precision')) can also be of type integer. However, the property $delta is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2034
2035 2
        $this->spreadsheet = $spreadsheet;
0 ignored issues
show
Documentation Bug introduced by
It seems like $spreadsheet can also be of type object<PhpOffice\PhpSpreadsheet\Spreadsheet>. However, the property $spreadsheet is declared as type object<PhpOffice\PhpSpreadsheet\PhpSpreadsheet>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2036 2
        $this->cyclicReferenceStack = new CalcEngine\CyclicReferenceStack();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \PhpOffice\PhpSpread...\CyclicReferenceStack() of type object<PhpOffice\PhpSpre...e\CyclicReferenceStack> is incompatible with the declared type array of property $cyclicReferenceStack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2037 2
        $this->_debugLog = new CalcEngine\Logger($this->cyclicReferenceStack);
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2038 2
    }
2039
2040
    private static function loadLocales()
2041
    {
2042
        $localeFileDirectory = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet/locale/';
2043
        foreach (glob($localeFileDirectory . '/*', GLOB_ONLYDIR) as $filename) {
2044
            $filename = substr($filename, strlen($localeFileDirectory) + 1);
2045
            if ($filename != 'en') {
2046
                self::$validLocaleLanguages[] = $filename;
2047
            }
2048
        }
2049
    }
2050
2051
    /**
2052
     * Get an instance of this class
2053
     *
2054
     * @param   Spreadsheet $spreadsheet  Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object,
2055
     *                                    or NULL to create a standalone claculation engine
2056
     * @return Calculation
2057
     */
2058 57
    public static function getInstance(Spreadsheet $spreadsheet = null)
2059
    {
2060 57
        if ($spreadsheet !== null) {
2061
            $instance = $spreadsheet->getCalculationEngine();
2062
            if (isset($instance)) {
2063
                return $instance;
2064
            }
2065
        }
2066
2067 57
        if (!isset(self::$instance) || (self::$instance === null)) {
2068 1
            self::$instance = new \PhpOffice\PhpSpreadsheet\Calculation();
2069
        }
2070
2071 57
        return self::$instance;
2072
    }
2073
2074
    /**
2075
     * Unset an instance of this class
2076
     *
2077
     * @param   Spreadsheet $spreadsheet  Injected spreadsheet identifying the instance to unset
0 ignored issues
show
Bug introduced by
There is no parameter named $spreadsheet. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
2078
     */
2079
    public function __destruct()
2080
    {
2081
        $this->workbook = null;
0 ignored issues
show
Bug introduced by
The property workbook does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2082
    }
2083
2084
    /**
2085
     * Flush the calculation cache for any existing instance of this class
2086
     *        but only if a \PhpOffice\PhpSpreadsheet\Calculation instance exists
2087
     */
2088
    public function flushInstance()
2089
    {
2090
        $this->clearCalculationCache();
2091
    }
2092
2093
    /**
2094
     * Get the debuglog for this claculation engine instance
2095
     *
2096
     * @return CalcEngine\Logger
2097
     */
2098
    public function getDebugLog()
2099
    {
2100
        return $this->_debugLog;
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2101
    }
2102
2103
    /**
2104
     * __clone implementation. Cloning should not be allowed in a Singleton!
2105
     *
2106
     * @throws    Calculation\Exception
2107
     */
2108
    final public function __clone()
2109
    {
2110
        throw new Calculation\Exception('Cloning the calculation engine is not allowed!');
2111
    }
2112
2113
    /**
2114
     * Return the locale-specific translation of TRUE
2115
     *
2116
     * @return     string        locale-specific translation of TRUE
2117
     */
2118 35
    public static function getTRUE()
2119
    {
2120 35
        return self::$localeBoolean['TRUE'];
2121
    }
2122
2123
    /**
2124
     * Return the locale-specific translation of FALSE
2125
     *
2126
     * @return     string        locale-specific translation of FALSE
2127
     */
2128 28
    public static function getFALSE()
2129
    {
2130 28
        return self::$localeBoolean['FALSE'];
2131
    }
2132
2133
    /**
2134
     * Set the Array Return Type (Array or Value of first element in the array)
2135
     *
2136
     * @param     string    $returnType            Array return type
2137
     * @return     bool                    Success or failure
2138
     */
2139 11
    public static function setArrayReturnType($returnType)
2140
    {
2141 11
        if (($returnType == self::RETURN_ARRAY_AS_VALUE) ||
2142 11
            ($returnType == self::RETURN_ARRAY_AS_ERROR) ||
2143 11
            ($returnType == self::RETURN_ARRAY_AS_ARRAY)) {
2144 11
            self::$returnArrayAsType = $returnType;
2145
2146 11
            return true;
2147
        }
2148
2149
        return false;
2150
    }
2151
2152
    /**
2153
     * Return the Array Return Type (Array or Value of first element in the array)
2154
     *
2155
     * @return     string        $returnType            Array return type
2156
     */
2157
    public static function getArrayReturnType()
2158
    {
2159
        return self::$returnArrayAsType;
2160
    }
2161
2162
    /**
2163
     * Is calculation caching enabled?
2164
     *
2165
     * @return bool
2166
     */
2167
    public function getCalculationCacheEnabled()
2168
    {
2169
        return $this->calculationCacheEnabled;
2170
    }
2171
2172
    /**
2173
     * Enable/disable calculation cache
2174
     *
2175
     * @param bool $pValue
2176
     */
2177
    public function setCalculationCacheEnabled($pValue = true)
2178
    {
2179
        $this->calculationCacheEnabled = $pValue;
2180
        $this->clearCalculationCache();
2181
    }
2182
2183
    /**
2184
     * Enable calculation cache
2185
     */
2186
    public function enableCalculationCache()
2187
    {
2188
        $this->setCalculationCacheEnabled(true);
2189
    }
2190
2191
    /**
2192
     * Disable calculation cache
2193
     */
2194
    public function disableCalculationCache()
2195
    {
2196
        $this->setCalculationCacheEnabled(false);
2197
    }
2198
2199
    /**
2200
     * Clear calculation cache
2201
     */
2202
    public function clearCalculationCache()
2203
    {
2204
        $this->calculationCache = [];
2205
    }
2206
2207
    /**
2208
     * Clear calculation cache for a specified worksheet
2209
     *
2210
     * @param string $worksheetName
2211
     */
2212
    public function clearCalculationCacheForWorksheet($worksheetName)
2213
    {
2214
        if (isset($this->calculationCache[$worksheetName])) {
2215
            unset($this->calculationCache[$worksheetName]);
2216
        }
2217
    }
2218
2219
    /**
2220
     * Rename calculation cache for a specified worksheet
2221
     *
2222
     * @param string $fromWorksheetName
2223
     * @param string $toWorksheetName
2224
     */
2225 1
    public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName)
2226
    {
2227 1
        if (isset($this->calculationCache[$fromWorksheetName])) {
2228
            $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName];
2229
            unset($this->calculationCache[$fromWorksheetName]);
2230
        }
2231 1
    }
2232
2233
    /**
2234
     * Get the currently defined locale code
2235
     *
2236
     * @return string
2237
     */
2238
    public function getLocale()
2239
    {
2240
        return self::$localeLanguage;
2241
    }
2242
2243
    /**
2244
     * Set the locale code
2245
     *
2246
     * @param string $locale  The locale to use for formula translation
2247
     * @return bool
2248
     */
2249
    public function setLocale($locale = 'en_us')
2250
    {
2251
        //    Identify our locale and language
2252
        $language = $locale = strtolower($locale);
2253
        if (strpos($locale, '_') !== false) {
2254
            list($language) = explode('_', $locale);
2255
        }
2256
2257
        if (count(self::$validLocaleLanguages) == 1) {
2258
            self::loadLocales();
2259
        }
2260
        //    Test whether we have any language data for this language (any locale)
2261
        if (in_array($language, self::$validLocaleLanguages)) {
2262
            //    initialise language/locale settings
2263
            self::$localeFunctions = [];
2264
            self::$localeArgumentSeparator = ',';
2265
            self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL'];
0 ignored issues
show
Documentation Bug introduced by
It seems like array('TRUE' => 'TRUE', ...LSE', 'NULL' => 'NULL') of type array<string,string,{"TR...ring","NULL":"string"}> is incompatible with the declared type array<integer,string> of property $localeBoolean.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2266
            //    Default is English, if user isn't requesting english, then read the necessary data from the locale files
2267
            if ($locale != 'en_us') {
2268
                //    Search for a file with a list of function names for locale
2269
                $functionNamesFile = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'functions';
2270
                if (!file_exists($functionNamesFile)) {
2271
                    //    If there isn't a locale specific function file, look for a language specific function file
2272
                    $functionNamesFile = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . 'functions';
2273
                    if (!file_exists($functionNamesFile)) {
2274
                        return false;
2275
                    }
2276
                }
2277
                //    Retrieve the list of locale or language specific function names
2278
                $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2279
                foreach ($localeFunctions as $localeFunction) {
2280
                    list($localeFunction) = explode('##', $localeFunction); //    Strip out comments
2281
                    if (strpos($localeFunction, '=') !== false) {
2282
                        list($fName, $lfName) = explode('=', $localeFunction);
2283
                        $fName = trim($fName);
2284
                        $lfName = trim($lfName);
2285
                        if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
2286
                            self::$localeFunctions[$fName] = $lfName;
2287
                        }
2288
                    }
2289
                }
2290
                //    Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions
2291
                if (isset(self::$localeFunctions['TRUE'])) {
2292
                    self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE'];
2293
                }
2294
                if (isset(self::$localeFunctions['FALSE'])) {
2295
                    self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE'];
2296
                }
2297
2298
                $configFile = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'config';
2299
                if (!file_exists($configFile)) {
2300
                    $configFile = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . 'config';
2301
                }
2302
                if (file_exists($configFile)) {
2303
                    $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2304
                    foreach ($localeSettings as $localeSetting) {
2305
                        list($localeSetting) = explode('##', $localeSetting); //    Strip out comments
2306
                        if (strpos($localeSetting, '=') !== false) {
2307
                            list($settingName, $settingValue) = explode('=', $localeSetting);
2308
                            $settingName = strtoupper(trim($settingName));
2309
                            switch ($settingName) {
2310
                                case 'ARGUMENTSEPARATOR':
2311
                                    self::$localeArgumentSeparator = trim($settingValue);
2312
                                    break;
2313
                            }
2314
                        }
2315
                    }
2316
                }
2317
            }
2318
2319
            self::$functionReplaceFromExcel = self::$functionReplaceToExcel =
2320
            self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null;
2321
            self::$localeLanguage = $locale;
2322
2323
            return true;
2324
        }
2325
2326
        return false;
2327
    }
2328
2329
    public static function translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
2330
    {
2331
        $strlen = mb_strlen($formula);
2332
        for ($i = 0; $i < $strlen; ++$i) {
2333
            $chr = mb_substr($formula, $i, 1);
2334
            switch ($chr) {
2335
                case '{':
2336
                    $inBraces = true;
2337
                    break;
2338
                case '}':
2339
                    $inBraces = false;
2340
                    break;
2341
                case $fromSeparator:
2342
                    if (!$inBraces) {
2343
                        $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
2344
                    }
2345
            }
2346
        }
2347
2348
        return $formula;
2349
    }
2350
2351
    /**
2352
     * @param string $fromSeparator
2353
     * @param string $toSeparator
2354
     */
2355
    private static function translateFormula($from, $to, $formula, $fromSeparator, $toSeparator)
2356
    {
2357
        //    Convert any Excel function names to the required language
2358
        if (self::$localeLanguage !== 'en_us') {
2359
            $inBraces = false;
2360
            //    If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
2361
            if (strpos($formula, '"') !== false) {
2362
                //    So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
2363
                //        the formula
2364
                $temp = explode('"', $formula);
2365
                $i = false;
2366
                foreach ($temp as &$value) {
2367
                    //    Only count/replace in alternating array entries
2368 View Code Duplication
                    if ($i = !$i) {
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...
2369
                        $value = preg_replace($from, $to, $value);
2370
                        $value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces);
2371
                    }
2372
                }
2373
                unset($value);
2374
                //    Then rebuild the formula string
2375
                $formula = implode('"', $temp);
2376 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...
2377
                //    If there's no quoted strings, then we do a simple count/replace
2378
                $formula = preg_replace($from, $to, $formula);
2379
                $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces);
2380
            }
2381
        }
2382
2383
        return $formula;
2384
    }
2385
2386
    private static $functionReplaceFromExcel = null;
2387
    private static $functionReplaceToLocale = null;
2388
2389 View Code Duplication
    public function _translateFormulaToLocale($formula)
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...
2390
    {
2391
        if (self::$functionReplaceFromExcel === null) {
2392
            self::$functionReplaceFromExcel = [];
2393
            foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
2394
                self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName) . '([\s]*\()/Ui';
2395
            }
2396
            foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
2397
                self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean) . '([^\w\.])/Ui';
2398
            }
2399
        }
2400
2401
        if (self::$functionReplaceToLocale === null) {
2402
            self::$functionReplaceToLocale = [];
2403
            foreach (array_values(self::$localeFunctions) as $localeFunctionName) {
2404
                self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2';
2405
            }
2406
            foreach (array_values(self::$localeBoolean) as $localeBoolean) {
2407
                self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2';
2408
            }
2409
        }
2410
2411
        return self::translateFormula(self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula, ',', self::$localeArgumentSeparator);
2412
    }
2413
2414
    private static $functionReplaceFromLocale = null;
2415
    private static $functionReplaceToExcel = null;
2416
2417 View Code Duplication
    public function _translateFormulaToEnglish($formula)
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...
2418
    {
2419
        if (self::$functionReplaceFromLocale === null) {
2420
            self::$functionReplaceFromLocale = [];
2421
            foreach (array_values(self::$localeFunctions) as $localeFunctionName) {
2422
                self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName) . '([\s]*\()/Ui';
2423
            }
2424
            foreach (array_values(self::$localeBoolean) as $excelBoolean) {
2425
                self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean) . '([^\w\.])/Ui';
2426
            }
2427
        }
2428
2429
        if (self::$functionReplaceToExcel === null) {
2430
            self::$functionReplaceToExcel = [];
2431
            foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
2432
                self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2';
2433
            }
2434
            foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
2435
                self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2';
2436
            }
2437
        }
2438
2439
        return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ',');
2440
    }
2441
2442
    public static function localeFunc($function)
2443
    {
2444
        if (self::$localeLanguage !== 'en_us') {
2445
            $functionName = trim($function, '(');
2446
            if (isset(self::$localeFunctions[$functionName])) {
2447
                $brace = ($functionName != $function);
2448
                $function = self::$localeFunctions[$functionName];
2449
                if ($brace) {
2450
                    $function .= '(';
2451
                }
2452
            }
2453
        }
2454
2455
        return $function;
2456
    }
2457
2458
    /**
2459
     * Wrap string values in quotes
2460
     *
2461
     * @param mixed $value
2462
     * @return mixed
2463
     */
2464 33
    public static function wrapResult($value)
2465
    {
2466 33
        if (is_string($value)) {
2467
            //    Error values cannot be "wrapped"
2468 33
            if (preg_match('/^' . self::CALCULATION_REGEXP_ERROR . '$/i', $value, $match)) {
2469
                //    Return Excel errors "as is"
2470
                return $value;
2471
            }
2472
            //    Return strings wrapped in quotes
2473 33
            return '"' . $value . '"';
2474
        //    Convert numeric errors to NaN error
2475
        } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
2476
            return Calculation\Functions::NAN();
2477
        }
2478
2479
        return $value;
2480
    }
2481
2482
    /**
2483
     * Remove quotes used as a wrapper to identify string values
2484
     *
2485
     * @param mixed $value
2486
     * @return mixed
2487
     */
2488 33
    public static function unwrapResult($value)
2489
    {
2490 33
        if (is_string($value)) {
2491 33
            if ((isset($value{0})) && ($value{0} == '"') && (substr($value, -1) == '"')) {
2492 33
                return substr($value, 1, -1);
2493
            }
2494
        //    Convert numeric errors to NAN error
2495
        } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
2496
            return Calculation\Functions::NAN();
2497
        }
2498
2499
        return $value;
2500
    }
2501
2502
    /**
2503
     * Calculate cell value (using formula from a cell ID)
2504
     * Retained for backward compatibility
2505
     *
2506
     * @param    Cell    $pCell    Cell to calculate
2507
     * @throws    Calculation\Exception
2508
     * @return    mixed
2509
     */
2510
    public function calculate(Cell $pCell = null)
2511
    {
2512
        try {
2513
            return $this->calculateCellValue($pCell);
2514
        } catch (Exception $e) {
2515
            throw new Calculation\Exception($e->getMessage());
2516
        }
2517
    }
2518
2519
    /**
2520
     * Calculate the value of a cell formula
2521
     *
2522
     * @param    Cell    $pCell        Cell to calculate
2523
     * @param    bool            $resetLog    Flag indicating whether the debug log should be reset or not
2524
     * @throws    Calculation\Exception
2525
     * @return    mixed
2526
     */
2527
    public function calculateCellValue(Cell $pCell = null, $resetLog = true)
2528
    {
2529
        if ($pCell === null) {
2530
            return null;
2531
        }
2532
2533
        $returnArrayAsType = self::$returnArrayAsType;
2534
        if ($resetLog) {
2535
            //    Initialise the logging settings if requested
2536
            $this->formulaError = null;
2537
            $this->_debugLog->clearLog();
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2538
            $this->cyclicReferenceStack->clear();
0 ignored issues
show
Bug introduced by
The method clear cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2539
            $this->cyclicFormulaCounter = 1;
2540
2541
            self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY;
2542
        }
2543
2544
        //    Execute the calculation for the cell formula
2545
        $this->cellStack[] = [
2546
            'sheet' => $pCell->getWorksheet()->getTitle(),
2547
            'cell' => $pCell->getCoordinate(),
2548
        ];
2549
        try {
2550
            $result = self::unwrapResult($this->_calculateFormulaValue($pCell->getValue(), $pCell->getCoordinate(), $pCell));
2551
            $cellAddress = array_pop($this->cellStack);
2552
            $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
2553
        } catch (Exception $e) {
2554
            $cellAddress = array_pop($this->cellStack);
2555
            $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
2556
            throw new Calculation\Exception($e->getMessage());
2557
        }
2558
2559
        if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
2560
            self::$returnArrayAsType = $returnArrayAsType;
2561
            $testResult = Calculation\Functions::flattenArray($result);
2562
            if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
2563
                return Calculation\Functions::VALUE();
2564
            }
2565
            //    If there's only a single cell in the array, then we allow it
2566
            if (count($testResult) != 1) {
2567
                //    If keys are numeric, then it's a matrix result rather than a cell range result, so we permit it
2568
                $r = array_keys($result);
2569
                $r = array_shift($r);
2570
                if (!is_numeric($r)) {
2571
                    return Calculation\Functions::VALUE();
2572
                }
2573
                if (is_array($result[$r])) {
2574
                    $c = array_keys($result[$r]);
2575
                    $c = array_shift($c);
2576
                    if (!is_numeric($c)) {
2577
                        return Calculation\Functions::VALUE();
2578
                    }
2579
                }
2580
            }
2581
            $result = array_shift($testResult);
2582
        }
2583
        self::$returnArrayAsType = $returnArrayAsType;
2584
2585
        if ($result === null) {
2586
            return 0;
2587
        } elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) {
2588
            return Calculation\Functions::NAN();
2589
        }
2590
2591
        return $result;
2592
    }
2593
2594
    /**
2595
     * Validate and parse a formula string
2596
     *
2597
     * @param    string        $formula        Formula to parse
2598
     * @throws    Calculation\Exception
2599
     * @return    array
2600
     */
2601
    public function parseFormula($formula)
2602
    {
2603
        //    Basic validation that this is indeed a formula
2604
        //    We return an empty array if not
2605
        $formula = trim($formula);
2606
        if ((!isset($formula{0})) || ($formula{0} != '=')) {
2607
            return [];
2608
        }
2609
        $formula = ltrim(substr($formula, 1));
2610
        if (!isset($formula{0})) {
2611
            return [];
2612
        }
2613
2614
        //    Parse the formula and return the token stack
2615
        return $this->_parseFormula($formula);
2616
    }
2617
2618
    /**
2619
     * Calculate the value of a formula
2620
     *
2621
     * @param    string            $formula    Formula to parse
2622
     * @param    string            $cellID        Address of the cell to calculate
2623
     * @param    Cell    $pCell        Cell to calculate
2624
     * @throws    Calculation\Exception
2625
     * @return    mixed
2626
     */
2627
    public function calculateFormula($formula, $cellID = null, Cell $pCell = null)
2628
    {
2629
        //    Initialise the logging settings
2630
        $this->formulaError = null;
2631
        $this->_debugLog->clearLog();
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2632
        $this->cyclicReferenceStack->clear();
0 ignored issues
show
Bug introduced by
The method clear cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2633
2634
        if ($this->spreadsheet !== null && $cellID === null && $pCell === null) {
2635
            $cellID = 'A1';
2636
            $pCell = $this->spreadsheet->getActiveSheet()->getCell($cellID);
2637
        } else {
2638
            //    Disable calculation cacheing because it only applies to cell calculations, not straight formulae
2639
            //    But don't actually flush any cache
2640
            $resetCache = $this->getCalculationCacheEnabled();
2641
            $this->calculationCacheEnabled = false;
2642
        }
2643
2644
        //    Execute the calculation
2645
        try {
2646
            $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $pCell));
2647
        } catch (Exception $e) {
2648
            throw new Calculation\Exception($e->getMessage());
2649
        }
2650
2651
        if ($this->spreadsheet === null) {
2652
            //    Reset calculation cacheing to its previous state
2653
            $this->calculationCacheEnabled = $resetCache;
0 ignored issues
show
Bug introduced by
The variable $resetCache 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...
2654
        }
2655
2656
        return $result;
2657
    }
2658
2659
    public function getValueFromCache($cellReference, &$cellValue)
2660
    {
2661
        // Is calculation cacheing enabled?
2662
        // Is the value present in calculation cache?
2663
        $this->_debugLog->writeDebugLog('Testing cache value for cell ', $cellReference);
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2664
        if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) {
2665
            $this->_debugLog->writeDebugLog('Retrieving value for cell ', $cellReference, ' from cache');
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2666
            // Return the cached result
2667
            $cellValue = $this->calculationCache[$cellReference];
2668
2669
            return true;
2670
        }
2671
2672
        return false;
2673
    }
2674
2675
    /**
2676
     * @param string $cellReference
2677
     */
2678
    public function saveValueToCache($cellReference, $cellValue)
2679
    {
2680
        if ($this->calculationCacheEnabled) {
2681
            $this->calculationCache[$cellReference] = $cellValue;
2682
        }
2683
    }
2684
2685
    /**
2686
     * Parse a cell formula and calculate its value
2687
     *
2688
     * @param    string            $formula    The formula to parse and calculate
2689
     * @param    string            $cellID        The ID (e.g. A3) of the cell that we are calculating
2690
     * @param    Cell    $pCell        Cell to calculate
2691
     * @throws   Calculation\Exception
2692
     * @return   mixed
2693
     */
2694 57
    public function _calculateFormulaValue($formula, $cellID = null, Cell $pCell = null)
2695
    {
2696 57
        $cellValue = null;
2697
2698
        //    Basic validation that this is indeed a formula
2699
        //    We simply return the cell value if not
2700 57
        $formula = trim($formula);
2701 57
        if ($formula{0} != '=') {
2702
            return self::wrapResult($formula);
2703
        }
2704 57
        $formula = ltrim(substr($formula, 1));
2705 57
        if (!isset($formula{0})) {
2706
            return self::wrapResult($formula);
2707
        }
2708
2709 57
        $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
2710 57
        $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk";
2711 57
        $wsCellReference = $wsTitle . '!' . $cellID;
2712
2713 57
        if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) {
2714
            return $cellValue;
2715
        }
2716
2717 57
        if (($wsTitle{0} !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) {
0 ignored issues
show
Bug introduced by
The method onStack cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2718
            if ($this->cyclicFormulaCount <= 0) {
2719
                $this->cyclicFormulaCell = '';
2720
2721
                return $this->raiseFormulaError('Cyclic Reference in Formula');
2722
            } elseif ($this->cyclicFormulaCell === $wsCellReference) {
2723
                ++$this->cyclicFormulaCounter;
2724
                if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
2725
                    $this->cyclicFormulaCell = '';
2726
2727
                    return $cellValue;
2728
                }
2729
            } elseif ($this->cyclicFormulaCell == '') {
2730
                if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
2731
                    return $cellValue;
2732
                }
2733
                $this->cyclicFormulaCell = $wsCellReference;
2734
            }
2735
        }
2736
2737
        //    Parse the formula onto the token stack and calculate the value
2738 57
        $this->cyclicReferenceStack->push($wsCellReference);
0 ignored issues
show
Bug introduced by
The method push cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2739 57
        $cellValue = $this->processTokenStack($this->_parseFormula($formula, $pCell), $cellID, $pCell);
2740 57
        $this->cyclicReferenceStack->pop();
0 ignored issues
show
Bug introduced by
The method pop cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2741
2742
        // Save to calculation cache
2743 57
        if ($cellID !== null) {
2744
            $this->saveValueToCache($wsCellReference, $cellValue);
2745
        }
2746
2747
        //    Return the calculated value
2748 57
        return $cellValue;
2749
    }
2750
2751
    /**
2752
     * Ensure that paired matrix operands are both matrices and of the same size
2753
     *
2754
     * @param    mixed        &$operand1    First matrix operand
2755
     * @param    mixed        &$operand2    Second matrix operand
2756
     * @param    int        $resize        Flag indicating whether the matrices should be resized to match
2757
     *                                        and (if so), whether the smaller dimension should grow or the
2758
     *                                        larger should shrink.
2759
     *                                            0 = no resize
2760
     *                                            1 = shrink to fit
2761
     *                                            2 = extend to fit
2762
     */
2763
    private static function checkMatrixOperands(&$operand1, &$operand2, $resize = 1)
2764
    {
2765
        //    Examine each of the two operands, and turn them into an array if they aren't one already
2766
        //    Note that this function should only be called if one or both of the operand is already an array
2767
        if (!is_array($operand1)) {
2768
            list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand2);
2769
            $operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1));
2770
            $resize = 0;
2771
        } elseif (!is_array($operand2)) {
2772
            list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand1);
2773
            $operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2));
2774
            $resize = 0;
2775
        }
2776
2777
        list($matrix1Rows, $matrix1Columns) = self::getMatrixDimensions($operand1);
2778
        list($matrix2Rows, $matrix2Columns) = self::getMatrixDimensions($operand2);
2779
        if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
2780
            $resize = 1;
2781
        }
2782
2783
        if ($resize == 2) {
2784
            //    Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger
2785
            self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
2786
        } elseif ($resize == 1) {
2787
            //    Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller
2788
            self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
2789
        }
2790
2791
        return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns];
2792
    }
2793
2794
    /**
2795
     * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0
2796
     *
2797
     * @param    mixed        &$matrix        matrix operand
2798
     * @return    int[]        An array comprising the number of rows, and number of columns
2799
     */
2800
    private static function getMatrixDimensions(&$matrix)
2801
    {
2802
        $matrixRows = count($matrix);
2803
        $matrixColumns = 0;
2804
        foreach ($matrix as $rowKey => $rowValue) {
2805
            $matrixColumns = max(count($rowValue), $matrixColumns);
2806
            if (!is_array($rowValue)) {
2807
                $matrix[$rowKey] = [$rowValue];
2808
            } else {
2809
                $matrix[$rowKey] = array_values($rowValue);
2810
            }
2811
        }
2812
        $matrix = array_values($matrix);
2813
2814
        return [$matrixRows, $matrixColumns];
2815
    }
2816
2817
    /**
2818
     * Ensure that paired matrix operands are both matrices of the same size
2819
     *
2820
     * @param    mixed        &$matrix1        First matrix operand
2821
     * @param    mixed        &$matrix2        Second matrix operand
2822
     * @param    int        $matrix1Rows    Row size of first matrix operand
2823
     * @param    int        $matrix1Columns    Column size of first matrix operand
2824
     * @param    int        $matrix2Rows    Row size of second matrix operand
2825
     * @param    int        $matrix2Columns    Column size of second matrix operand
2826
     */
2827
    private static function resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
2828
    {
2829 View Code Duplication
        if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
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...
2830
            if ($matrix2Rows < $matrix1Rows) {
2831
                for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
2832
                    unset($matrix1[$i]);
2833
                }
2834
            }
2835
            if ($matrix2Columns < $matrix1Columns) {
2836
                for ($i = 0; $i < $matrix1Rows; ++$i) {
2837
                    for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
2838
                        unset($matrix1[$i][$j]);
2839
                    }
2840
                }
2841
            }
2842
        }
2843
2844 View Code Duplication
        if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
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...
2845
            if ($matrix1Rows < $matrix2Rows) {
2846
                for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
2847
                    unset($matrix2[$i]);
2848
                }
2849
            }
2850
            if ($matrix1Columns < $matrix2Columns) {
2851
                for ($i = 0; $i < $matrix2Rows; ++$i) {
2852
                    for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
2853
                        unset($matrix2[$i][$j]);
2854
                    }
2855
                }
2856
            }
2857
        }
2858
    }
2859
2860
    /**
2861
     * Ensure that paired matrix operands are both matrices of the same size
2862
     *
2863
     * @param    mixed        &$matrix1    First matrix operand
2864
     * @param    mixed        &$matrix2    Second matrix operand
2865
     * @param    int        $matrix1Rows    Row size of first matrix operand
2866
     * @param    int        $matrix1Columns    Column size of first matrix operand
2867
     * @param    int        $matrix2Rows    Row size of second matrix operand
2868
     * @param    int        $matrix2Columns    Column size of second matrix operand
2869
     */
2870
    private static function resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
2871
    {
2872 View Code Duplication
        if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
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...
2873
            if ($matrix2Columns < $matrix1Columns) {
2874
                for ($i = 0; $i < $matrix2Rows; ++$i) {
2875
                    $x = $matrix2[$i][$matrix2Columns - 1];
2876
                    for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
2877
                        $matrix2[$i][$j] = $x;
2878
                    }
2879
                }
2880
            }
2881
            if ($matrix2Rows < $matrix1Rows) {
2882
                $x = $matrix2[$matrix2Rows - 1];
2883
                for ($i = 0; $i < $matrix1Rows; ++$i) {
2884
                    $matrix2[$i] = $x;
2885
                }
2886
            }
2887
        }
2888
2889 View Code Duplication
        if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
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...
2890
            if ($matrix1Columns < $matrix2Columns) {
2891
                for ($i = 0; $i < $matrix1Rows; ++$i) {
2892
                    $x = $matrix1[$i][$matrix1Columns - 1];
2893
                    for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
2894
                        $matrix1[$i][$j] = $x;
2895
                    }
2896
                }
2897
            }
2898
            if ($matrix1Rows < $matrix2Rows) {
2899
                $x = $matrix1[$matrix1Rows - 1];
2900
                for ($i = 0; $i < $matrix2Rows; ++$i) {
2901
                    $matrix1[$i] = $x;
2902
                }
2903
            }
2904
        }
2905
    }
2906
2907
    /**
2908
     * Format details of an operand for display in the log (based on operand type)
2909
     *
2910
     * @param    mixed        $value    First matrix operand
2911
     * @return    mixed
2912
     */
2913 56
    private function showValue($value)
2914
    {
2915 56
        if ($this->_debugLog->getWriteDebugLog()) {
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2916
            $testArray = Calculation\Functions::flattenArray($value);
2917
            if (count($testArray) == 1) {
2918
                $value = array_pop($testArray);
2919
            }
2920
2921
            if (is_array($value)) {
2922
                $returnMatrix = [];
2923
                $pad = $rpad = ', ';
2924
                foreach ($value as $row) {
2925
                    if (is_array($row)) {
2926
                        $returnMatrix[] = implode($pad, array_map([$this, 'showValue'], $row));
2927
                        $rpad = '; ';
2928
                    } else {
2929
                        $returnMatrix[] = $this->showValue($row);
2930
                    }
2931
                }
2932
2933
                return '{ ' . implode($rpad, $returnMatrix) . ' }';
2934
            } elseif (is_string($value) && (trim($value, '"') == $value)) {
2935
                return '"' . $value . '"';
2936
            } elseif (is_bool($value)) {
2937
                return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
2938
            }
2939
        }
2940
2941 56
        return Calculation\Functions::flattenSingleValue($value);
2942
    }
2943
2944
    /**
2945
     * Format type and details of an operand for display in the log (based on operand type)
2946
     *
2947
     * @param    mixed        $value    First matrix operand
2948
     * @return    string|null
2949
     */
2950 56
    private function showTypeDetails($value)
2951
    {
2952 56
        if ($this->_debugLog->getWriteDebugLog()) {
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2953
            $testArray = Calculation\Functions::flattenArray($value);
2954
            if (count($testArray) == 1) {
2955
                $value = array_pop($testArray);
2956
            }
2957
2958
            if ($value === null) {
2959
                return 'a NULL value';
2960
            } elseif (is_float($value)) {
2961
                $typeString = 'a floating point number';
2962
            } elseif (is_int($value)) {
2963
                $typeString = 'an integer number';
2964
            } elseif (is_bool($value)) {
2965
                $typeString = 'a boolean';
2966
            } elseif (is_array($value)) {
2967
                $typeString = 'a matrix';
2968
            } else {
2969
                if ($value == '') {
2970
                    return 'an empty string';
2971
                } elseif ($value{0} == '#') {
2972
                    return 'a ' . $value . ' error';
2973
                } else {
2974
                    $typeString = 'a string';
2975
                }
2976
            }
2977
2978
            return $typeString . ' with a value of ' . $this->showValue($value);
2979
        }
2980 56
    }
2981
2982 57
    private function convertMatrixReferences($formula)
2983
    {
2984 57
        static $matrixReplaceFrom = ['{', ';', '}'];
2985 57
        static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
2986
2987
        //    Convert any Excel matrix references to the MKMATRIX() function
2988 57
        if (strpos($formula, '{') !== false) {
2989
            //    If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
2990
            if (strpos($formula, '"') !== false) {
2991
                //    So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
2992
                //        the formula
2993
                $temp = explode('"', $formula);
2994
                //    Open and Closed counts used for trapping mismatched braces in the formula
2995
                $openCount = $closeCount = 0;
2996
                $i = false;
2997
                foreach ($temp as &$value) {
2998
                    //    Only count/replace in alternating array entries
2999
                    if ($i = !$i) {
3000
                        $openCount += substr_count($value, '{');
3001
                        $closeCount += substr_count($value, '}');
3002
                        $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
3003
                    }
3004
                }
3005
                unset($value);
3006
                //    Then rebuild the formula string
3007
                $formula = implode('"', $temp);
3008
            } else {
3009
                //    If there's no quoted strings, then we do a simple count/replace
3010
                $openCount = substr_count($formula, '{');
3011
                $closeCount = substr_count($formula, '}');
3012
                $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
3013
            }
3014
            //    Trap for mismatched braces and trigger an appropriate error
3015
            if ($openCount < $closeCount) {
3016
                if ($openCount > 0) {
3017
                    return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '}'");
3018
                } else {
3019
                    return $this->raiseFormulaError("Formula Error: Unexpected '}' encountered");
3020
                }
3021
            } elseif ($openCount > $closeCount) {
3022
                if ($closeCount > 0) {
3023
                    return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '{'");
3024
                } else {
3025
                    return $this->raiseFormulaError("Formula Error: Unexpected '{' encountered");
3026
                }
3027
            }
3028
        }
3029
3030 57
        return $formula;
3031
    }
3032
3033
    private static function mkMatrix()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
3034
    {
3035
        return func_get_args();
3036
    }
3037
3038
    //    Binary Operators
3039
    //    These operators always work on two values
3040
    //    Array key is the operator, the value indicates whether this is a left or right associative operator
3041
    private static $operatorAssociativity = [
3042
        '^' => 0, //    Exponentiation
3043
        '*' => 0, '/' => 0, //    Multiplication and Division
3044
        '+' => 0, '-' => 0, //    Addition and Subtraction
3045
        '&' => 0, //    Concatenation
3046
        '|' => 0, ':' => 0, //    Intersect and Range
3047
        '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, //    Comparison
3048
    ];
3049
3050
    //    Comparison (Boolean) Operators
3051
    //    These operators work on two values, but always return a boolean result
3052
    private static $comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true];
3053
3054
    //    Operator Precedence
3055
    //    This list includes all valid operators, whether binary (including boolean) or unary (such as %)
3056
    //    Array key is the operator, the value is its precedence
3057
    private static $operatorPrecedence = [
3058
        ':' => 8, //    Range
3059
        '|' => 7, //    Intersect
3060
        '~' => 6, //    Negation
3061
        '%' => 5, //    Percentage
3062
        '^' => 4, //    Exponentiation
3063
        '*' => 3, '/' => 3, //    Multiplication and Division
3064
        '+' => 2, '-' => 2, //    Addition and Subtraction
3065
        '&' => 1, //    Concatenation
3066
        '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, //    Comparison
3067
    ];
3068
3069
    // Convert infix to postfix notation
3070 57
    private function _parseFormula($formula, Cell $pCell = null)
3071
    {
3072 57
        if (($formula = $this->convertMatrixReferences(trim($formula))) === false) {
3073
            return false;
3074
        }
3075
3076
        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet),
3077
        //        so we store the parent worksheet so that we can re-attach it when necessary
3078 57
        $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
3079
3080 57
        $regexpMatchString = '/^(' . self::CALCULATION_REGEXP_FUNCTION .
3081 57
                                '|' . self::CALCULATION_REGEXP_CELLREF .
3082 57
                                '|' . self::CALCULATION_REGEXP_NUMBER .
3083 57
                                '|' . self::CALCULATION_REGEXP_STRING .
3084 57
                                '|' . self::CALCULATION_REGEXP_OPENBRACE .
3085 57
                                '|' . self::CALCULATION_REGEXP_NAMEDRANGE .
3086 57
                                '|' . self::CALCULATION_REGEXP_ERROR .
3087 57
                                ')/si';
3088
3089
        //    Start with initialisation
3090 57
        $index = 0;
3091 57
        $stack = new Calculation\Token\Stack();
3092 57
        $output = [];
3093 57
        $expectingOperator = false; //    We use this test in syntax-checking the expression to determine when a
3094
                                                    //        - is a negation or + is a positive operator rather than an operation
3095 57
        $expectingOperand = false; //    We use this test in syntax-checking the expression to determine whether an operand
3096
                                                    //        should be null in a function call
3097
        //    The guts of the lexical parser
3098
        //    Loop through the formula extracting each operator and operand in turn
3099 57
        while (true) {
3100 57
            $opCharacter = $formula{$index};    //    Get the first character of the value at the current index position
3101 57
            if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula{$index + 1}]))) {
3102 22
                $opCharacter .= $formula{++$index};
3103
            }
3104
3105
            //    Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand
3106 57
            $isOperandOrFunction = preg_match($regexpMatchString, substr($formula, $index), $match);
3107
3108 57
            if ($opCharacter == '-' && !$expectingOperator) {                //    Is it a negation instead of a minus?
3109
                $stack->push('Unary Operator', '~'); //    Put a negation on the stack
3110
                ++$index; //        and drop the negation symbol
3111 57
            } elseif ($opCharacter == '%' && $expectingOperator) {
3112
                $stack->push('Unary Operator', '%'); //    Put a percentage on the stack
3113
                ++$index;
3114 57
            } elseif ($opCharacter == '+' && !$expectingOperator) {            //    Positive (unary plus rather than binary operator plus) can be discarded?
3115
                ++$index; //    Drop the redundant plus symbol
3116 57
            } elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) {    //    We have to explicitly deny a tilde or pipe, because they are legal
3117
                return $this->raiseFormulaError("Formula Error: Illegal character '~'"); //        on the stack but not in the input expression
3118 57
            } elseif ((isset(self::$operators[$opCharacter]) or $isOperandOrFunction) && $expectingOperator) {    //    Are we putting an operator on the stack?
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
3119 56 View Code Duplication
                while ($stack->count() > 0 &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3120 56
                    ($o2 = $stack->last()) &&
3121 56
                    isset(self::$operators[$o2['value']]) &&
3122 56
                    @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) {
3123
                    $output[] = $stack->pop(); //    Swap operands and higher precedence operators from the stack to the output
3124
                }
3125 56
                $stack->push('Binary Operator', $opCharacter); //    Finally put our current operator onto the stack
3126 56
                ++$index;
3127 56
                $expectingOperator = false;
3128 57
            } elseif ($opCharacter == ')' && $expectingOperator) {            //    Are we expecting to close a parenthesis?
3129
                $expectingOperand = false;
3130 View Code Duplication
                while (($o2 = $stack->pop()) && $o2['value'] != '(') {        //    Pop off the stack back to the last (
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...
3131
                    if ($o2 === null) {
3132
                        return $this->raiseFormulaError('Formula Error: Unexpected closing brace ")"');
3133
                    } else {
3134
                        $output[] = $o2;
3135
                    }
3136
                }
3137
                $d = $stack->last(2);
3138
                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {    //    Did this parenthesis just close a function?
3139
                    $functionName = $matches[1]; //    Get the function name
3140
                    $d = $stack->pop();
3141
                    $argumentCount = $d['value']; //    See how many arguments there were (argument count is the next value stored on the stack)
3142
                    $output[] = $d; //    Dump the argument count on the output
3143
                    $output[] = $stack->pop(); //    Pop the function and push onto the output
3144
                    if (isset(self::$controlFunctions[$functionName])) {
3145
                        $expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount'];
3146
                        $functionCall = self::$controlFunctions[$functionName]['functionCall'];
0 ignored issues
show
Unused Code introduced by
$functionCall 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...
3147
                    } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
3148
                        $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount'];
3149
                        $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
0 ignored issues
show
Unused Code introduced by
$functionCall 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...
3150
                    } else {    // did we somehow push a non-function on the stack? this should never happen
3151
                        return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack');
3152
                    }
3153
                    //    Check the argument count
3154
                    $argumentCountError = false;
3155
                    if (is_numeric($expectedArgumentCount)) {
3156
                        if ($expectedArgumentCount < 0) {
3157
                            if ($argumentCount > abs($expectedArgumentCount)) {
3158
                                $argumentCountError = true;
3159
                                $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount);
3160
                            }
3161
                        } else {
3162
                            if ($argumentCount != $expectedArgumentCount) {
3163
                                $argumentCountError = true;
3164
                                $expectedArgumentCountString = $expectedArgumentCount;
3165
                            }
3166
                        }
3167
                    } elseif ($expectedArgumentCount != '*') {
3168
                        $isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
0 ignored issues
show
Unused Code introduced by
$isOperandOrFunction 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...
3169
                        switch ($argMatch[2]) {
3170
                            case '+':
3171
                                if ($argumentCount < $argMatch[1]) {
3172
                                    $argumentCountError = true;
3173
                                    $expectedArgumentCountString = $argMatch[1] . ' or more ';
3174
                                }
3175
                                break;
3176 View Code Duplication
                            case '-':
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...
3177
                                if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) {
3178
                                    $argumentCountError = true;
3179
                                    $expectedArgumentCountString = 'between ' . $argMatch[1] . ' and ' . $argMatch[3];
3180
                                }
3181
                                break;
3182 View Code Duplication
                            case ',':
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...
3183
                                if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) {
3184
                                    $argumentCountError = true;
3185
                                    $expectedArgumentCountString = 'either ' . $argMatch[1] . ' or ' . $argMatch[3];
3186
                                }
3187
                                break;
3188
                        }
3189
                    }
3190
                    if ($argumentCountError) {
3191
                        return $this->raiseFormulaError("Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, " . $expectedArgumentCountString . ' expected');
0 ignored issues
show
Bug introduced by
The variable $expectedArgumentCountString 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...
3192
                    }
3193
                }
3194
                ++$index;
3195 57
            } elseif ($opCharacter == ',') {            //    Is this the separator for function arguments?
3196 View Code Duplication
                while (($o2 = $stack->pop()) && $o2['value'] != '(') {        //    Pop off the stack back to the last (
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...
3197
                    if ($o2 === null) {
3198
                        return $this->raiseFormulaError('Formula Error: Unexpected ,');
3199
                    } else {
3200
                        $output[] = $o2; // pop the argument expression stuff and push onto the output
3201
                    }
3202
                }
3203
                //    If we've a comma when we're expecting an operand, then what we actually have is a null operand;
3204
                //        so push a null onto the stack
3205
                if (($expectingOperand) || (!$expectingOperator)) {
3206
                    $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
3207
                }
3208
                // make sure there was a function
3209
                $d = $stack->last(2);
3210
                if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {
3211
                    return $this->raiseFormulaError('Formula Error: Unexpected ,');
3212
                }
3213
                $d = $stack->pop();
3214
                $stack->push($d['type'], ++$d['value'], $d['reference']); // increment the argument count
3215
                $stack->push('Brace', '('); // put the ( back on, we'll need to pop back to it again
3216
                $expectingOperator = false;
3217
                $expectingOperand = true;
3218
                ++$index;
3219 57
            } elseif ($opCharacter == '(' && !$expectingOperator) {
3220
                $stack->push('Brace', '(');
3221
                ++$index;
3222 57
            } elseif ($isOperandOrFunction && !$expectingOperator) {    // do we now have a function/variable/number?
3223 57
                $expectingOperator = true;
3224 57
                $expectingOperand = false;
3225 57
                $val = $match[1];
3226 57
                $length = strlen($val);
3227
3228 57
                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $val, $matches)) {
3229
                    $val = preg_replace('/\s/u', '', $val);
3230
                    if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) {    // it's a function
3231
                        $stack->push('Function', strtoupper($val));
3232
                        $ax = preg_match('/^\s*(\s*\))/ui', substr($formula, $index + $length), $amatch);
3233
                        if ($ax) {
3234
                            $stack->push('Operand Count for Function ' . strtoupper($val) . ')', 0);
3235
                            $expectingOperator = true;
3236
                        } else {
3237
                            $stack->push('Operand Count for Function ' . strtoupper($val) . ')', 1);
3238
                            $expectingOperator = false;
3239
                        }
3240
                        $stack->push('Brace', '(');
3241
                    } else {    // it's a var w/ implicit multiplication
3242
                        $output[] = ['type' => 'Value', 'value' => $matches[1], 'reference' => null];
3243
                    }
3244 57
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $val, $matches)) {
3245
                    //    Watch for this case-change when modifying to allow cell references in different worksheets...
3246
                    //    Should only be applied to the actual cell column, not the worksheet name
3247
3248
                    //    If the last entry on the stack was a : operator, then we have a cell range reference
3249
                    $testPrevOp = $stack->last(1);
3250
                    if ($testPrevOp['value'] == ':') {
3251
                        //    If we have a worksheet reference, then we're playing with a 3D reference
3252
                        if ($matches[2] == '') {
3253
                            //    Otherwise, we 'inherit' the worksheet reference from the start cell reference
3254
                            //    The start of the cell range reference should be the last entry in $output
3255
                            $startCellRef = $output[count($output) - 1]['value'];
3256
                            preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $startCellRef, $startMatches);
3257
                            if ($startMatches[2] > '') {
3258
                                $val = $startMatches[2] . '!' . $val;
3259
                            }
3260
                        } else {
3261
                            return $this->raiseFormulaError('3D Range references are not yet supported');
3262
                        }
3263
                    }
3264
3265
                    $output[] = ['type' => 'Cell Reference', 'value' => $val, 'reference' => $val];
3266
                } else {    // it's a variable, constant, string, number or boolean
3267
                    //    If the last entry on the stack was a : operator, then we may have a row or column range reference
3268 57
                    $testPrevOp = $stack->last(1);
3269 57
                    if ($testPrevOp['value'] == ':') {
3270
                        $startRowColRef = $output[count($output) - 1]['value'];
3271
                        $rangeWS1 = '';
3272
                        if (strpos('!', $startRowColRef) !== false) {
3273
                            list($rangeWS1, $startRowColRef) = explode('!', $startRowColRef);
3274
                        }
3275
                        if ($rangeWS1 != '') {
3276
                            $rangeWS1 .= '!';
3277
                        }
3278
                        $rangeWS2 = $rangeWS1;
3279
                        if (strpos('!', $val) !== false) {
3280
                            list($rangeWS2, $val) = explode('!', $val);
3281
                        }
3282
                        if ($rangeWS2 != '') {
3283
                            $rangeWS2 .= '!';
3284
                        }
3285
                        if ((is_integer($startRowColRef)) && (ctype_digit($val)) &&
3286
                            ($startRowColRef <= 1048576) && ($val <= 1048576)) {
3287
                            //    Row range
3288
                            $endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestColumn() : 'XFD'; //    Max 16,384 columns for Excel2007
3289
                            $output[count($output) - 1]['value'] = $rangeWS1 . 'A' . $startRowColRef;
3290
                            $val = $rangeWS2 . $endRowColRef . $val;
3291
                        } elseif ((ctype_alpha($startRowColRef)) && (ctype_alpha($val)) &&
3292
                            (strlen($startRowColRef) <= 3) && (strlen($val) <= 3)) {
3293
                            //    Column range
3294
                            $endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestRow() : 1048576; //    Max 1,048,576 rows for Excel2007
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% 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...
3295
                            $output[count($output) - 1]['value'] = $rangeWS1 . strtoupper($startRowColRef) . '1';
3296
                            $val = $rangeWS2 . $val . $endRowColRef;
3297
                        }
3298
                    }
3299
3300 57
                    $localeConstant = false;
3301 57
                    if ($opCharacter == '"') {
3302
                        //    UnEscape any quotes within the string
3303 33
                        $val = self::wrapResult(str_replace('""', '"', self::unwrapResult($val)));
3304 26
                    } elseif (is_numeric($val)) {
3305 25
                        if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
3306 6
                            $val = (float) $val;
3307
                        } else {
3308 25
                            $val = (integer) $val;
3309
                        }
3310 1
                    } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) {
3311 1
                        $excelConstant = trim(strtoupper($val));
3312 1
                        $val = self::$excelConstants[$excelConstant];
3313
                    } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) {
3314
                        $val = self::$excelConstants[$localeConstant];
3315
                    }
3316 57
                    $details = ['type' => 'Value', 'value' => $val, 'reference' => null];
3317 57
                    if ($localeConstant) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $localeConstant of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3318
                        $details['localeValue'] = $localeConstant;
3319
                    }
3320 57
                    $output[] = $details;
3321
                }
3322 57
                $index += $length;
3323
            } elseif ($opCharacter == '$') {    // absolute row or column range
3324
                ++$index;
3325
            } elseif ($opCharacter == ')') {    // miscellaneous error checking
3326
                if ($expectingOperand) {
3327
                    $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
3328
                    $expectingOperand = false;
3329
                    $expectingOperator = true;
3330
                } else {
3331
                    return $this->raiseFormulaError("Formula Error: Unexpected ')'");
3332
                }
3333
            } elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) {
3334
                return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
3335
            } else {    // I don't even want to know what you did to get here
3336
                return $this->raiseFormulaError('Formula Error: An unexpected error occured');
3337
            }
3338
            //    Test for end of formula string
3339 57
            if ($index == strlen($formula)) {
3340
                //    Did we end with an operator?.
3341
                //    Only valid for the % unary operator
3342 57
                if ((isset(self::$operators[$opCharacter])) && ($opCharacter != '%')) {
3343
                    return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
3344
                } else {
3345 57
                    break;
3346
                }
3347
            }
3348
            //    Ignore white space
3349 56
            while (($formula{$index} == "\n") || ($formula{$index} == "\r")) {
3350
                ++$index;
3351
            }
3352 56
            if ($formula{$index} == ' ') {
3353 51
                while ($formula{$index} == ' ') {
3354 51
                    ++$index;
3355
                }
3356
                //    If we're expecting an operator, but only have a space between the previous and next operands (and both are
3357
                //        Cell References) then we have an INTERSECTION operator
3358 51
                if (($expectingOperator) && (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) &&
3359 51
                    ($output[count($output) - 1]['type'] == 'Cell Reference')) {
3360 View Code Duplication
                    while ($stack->count() > 0 &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3361
                        ($o2 = $stack->last()) &&
3362
                        isset(self::$operators[$o2['value']]) &&
3363
                        @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) {
3364
                        $output[] = $stack->pop(); //    Swap operands and higher precedence operators from the stack to the output
3365
                    }
3366
                    $stack->push('Binary Operator', '|'); //    Put an Intersect Operator on the stack
3367
                    $expectingOperator = false;
3368
                }
3369
            }
3370
        }
3371
3372 57 View Code Duplication
        while (($op = $stack->pop()) !== null) {    // pop everything off the stack and push onto output
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...
3373 56
            if ((is_array($op) && $op['value'] == '(') || ($op === '(')) {
3374
                return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
3375
            }
3376 56
            $output[] = $op;
3377
        }
3378
3379 57
        return $output;
3380
    }
3381
3382 56
    private static function dataTestReference(&$operandData)
3383
    {
3384 56
        $operand = $operandData['value'];
3385 56
        if (($operandData['reference'] === null) && (is_array($operand))) {
3386
            $rKeys = array_keys($operand);
3387
            $rowKey = array_shift($rKeys);
3388
            $cKeys = array_keys(array_keys($operand[$rowKey]));
3389
            $colKey = array_shift($cKeys);
3390
            if (ctype_upper($colKey)) {
3391
                $operandData['reference'] = $colKey . $rowKey;
3392
            }
3393
        }
3394
3395 56
        return $operand;
3396
    }
3397
3398
    // evaluate postfix notation
3399
3400
    /**
3401
     * @param string $cellID
3402
     */
3403 57
    private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
3404
    {
3405 57
        if ($tokens == false) {
3406
            return false;
3407
        }
3408
3409
        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
3410
        //        so we store the parent cell collection so that we can re-attach it when necessary
3411 57
        $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null;
3412 57
        $pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
3413 57
        $stack = new Calculation\Token\Stack();
3414
3415
        //    Loop through each token in turn
3416 57
        foreach ($tokens as $tokenData) {
3417 57
            $token = $tokenData['value'];
3418
            // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
3419 57
            if (isset(self::$binaryOperators[$token])) {
3420
                //    We must have two operands, error if we don't
3421 56
                if (($operand2Data = $stack->pop()) === null) {
3422
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
3423
                }
3424 56
                if (($operand1Data = $stack->pop()) === null) {
3425
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
3426
                }
3427
3428 56
                $operand1 = self::dataTestReference($operand1Data);
3429 56
                $operand2 = self::dataTestReference($operand2Data);
3430
3431
                //    Log what we're doing
3432 56
                if ($token == ':') {
3433
                    $this->_debugLog->writeDebugLog('Evaluating Range ', $this->showValue($operand1Data['reference']), ' ', $token, ' ', $this->showValue($operand2Data['reference']));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3434
                } else {
3435 56
                    $this->_debugLog->writeDebugLog('Evaluating ', $this->showValue($operand1), ' ', $token, ' ', $this->showValue($operand2));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3436
                }
3437
3438
                //    Process the operation in the appropriate manner
3439
                switch ($token) {
3440
                    //    Comparison (Boolean) Operators
3441 56
                    case '>':            //    Greater than
3442 42
                    case '<':            //    Less than
3443 35
                    case '>=':            //    Greater than or Equal to
3444 27
                    case '<=':            //    Less than or Equal to
3445 20
                    case '=':            //    Equality
3446 11
                    case '<>':            //    Inequality
3447 52
                        $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
3448 52
                        break;
3449
                    //    Binary Operators
3450 4
                    case ':':            //    Range
3451
                        $sheet1 = $sheet2 = '';
3452
                        if (strpos($operand1Data['reference'], '!') !== false) {
3453
                            list($sheet1, $operand1Data['reference']) = explode('!', $operand1Data['reference']);
3454
                        } else {
3455
                            $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
3456
                        }
3457
                        if (strpos($operand2Data['reference'], '!') !== false) {
3458
                            list($sheet2, $operand2Data['reference']) = explode('!', $operand2Data['reference']);
3459
                        } else {
3460
                            $sheet2 = $sheet1;
3461
                        }
3462
                        if ($sheet1 == $sheet2) {
3463 View Code Duplication
                            if ($operand1Data['reference'] === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3464
                                if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
3465
                                    $operand1Data['reference'] = $pCell->getColumn() . $operand1Data['value'];
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...
3466
                                } elseif (trim($operand1Data['reference']) == '') {
3467
                                    $operand1Data['reference'] = $pCell->getCoordinate();
3468
                                } else {
3469
                                    $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow();
3470
                                }
3471
                            }
3472 View Code Duplication
                            if ($operand2Data['reference'] === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3473
                                if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
3474
                                    $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value'];
3475
                                } elseif (trim($operand2Data['reference']) == '') {
3476
                                    $operand2Data['reference'] = $pCell->getCoordinate();
3477
                                } else {
3478
                                    $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow();
3479
                                }
3480
                            }
3481
3482
                            $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
3483
                            $oCol = $oRow = [];
3484
                            foreach ($oData as $oDatum) {
3485
                                $oCR = Cell::coordinateFromString($oDatum);
3486
                                $oCol[] = Cell::columnIndexFromString($oCR[0]) - 1;
3487
                                $oRow[] = $oCR[1];
3488
                            }
3489
                            $cellRef = Cell::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . Cell::stringFromColumnIndex(max($oCol)) . max($oRow);
3490 View Code Duplication
                            if ($pCellParent !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3491
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
3492
                            } else {
3493
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3494
                            }
3495
                            $stack->push('Cell Reference', $cellValue, $cellRef);
3496
                        } else {
3497
                            $stack->push('Error', Calculation\Functions::REF(), null);
3498
                        }
3499
                        break;
3500 4
                    case '+':            //    Addition
3501 2
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'plusEquals', $stack);
3502 2
                        break;
3503 2
                    case '-':            //    Subtraction
3504 2
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'minusEquals', $stack);
3505 2
                        break;
3506
                    case '*':            //    Multiplication
3507
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'arrayTimesEquals', $stack);
3508
                        break;
3509
                    case '/':            //    Division
3510
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'arrayRightDivide', $stack);
3511
                        break;
3512
                    case '^':            //    Exponential
3513
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'power', $stack);
3514
                        break;
3515
                    case '&':            //    Concatenation
3516
                        //    If either of the operands is a matrix, we need to treat them both as matrices
3517
                        //        (converting the other operand to a matrix if need be); then perform the required
3518
                        //        matrix operation
3519
                        if (is_bool($operand1)) {
3520
                            $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3521
                        }
3522
                        if (is_bool($operand2)) {
3523
                            $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3524
                        }
3525
                        if ((is_array($operand1)) || (is_array($operand2))) {
3526
                            //    Ensure that both operands are arrays/matrices
3527
                            self::checkMatrixOperands($operand1, $operand2, 2);
3528
                            try {
3529
                                //    Convert operand 1 from a PHP array to a matrix
3530
                                $matrix = new Shared\JAMA\Matrix($operand1);
3531
                                //    Perform the required operation against the operand 1 matrix, passing in operand 2
3532
                                $matrixResult = $matrix->concat($operand2);
3533
                                $result = $matrixResult->getArray();
3534
                            } catch (Exception $ex) {
3535
                                $this->_debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3536
                                $result = '#VALUE!';
3537
                            }
3538
                        } else {
3539
                            $result = '"' . str_replace('""', '"', self::unwrapResult($operand1, '"') . self::unwrapResult($operand2, '"')) . '"';
0 ignored issues
show
Unused Code introduced by
The call to Calculation::unwrapResult() has too many arguments starting with '"'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
3540
                        }
3541
                        $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3542
                        $stack->push('Value', $result);
3543
                        break;
3544
                    case '|':            //    Intersect
3545
                        $rowIntersect = array_intersect_key($operand1, $operand2);
3546
                        $cellIntersect = $oCol = $oRow = [];
3547
                        foreach (array_keys($rowIntersect) as $row) {
3548
                            $oRow[] = $row;
3549
                            foreach ($rowIntersect[$row] as $col => $data) {
3550
                                $oCol[] = Cell::columnIndexFromString($col) - 1;
3551
                                $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
3552
                            }
3553
                        }
3554
                        $cellRef = Cell::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . Cell::stringFromColumnIndex(max($oCol)) . max($oRow);
3555
                        $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3556
                        $stack->push('Value', $cellIntersect, $cellRef);
3557 56
                        break;
3558
                }
3559
3560
            // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
3561 57
            } elseif (($token === '~') || ($token === '%')) {
3562
                if (($arg = $stack->pop()) === null) {
3563
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
3564
                }
3565
                $arg = $arg['value'];
3566
                if ($token === '~') {
3567
                    $this->_debugLog->writeDebugLog('Evaluating Negation of ', $this->showValue($arg));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3568
                    $multiplier = -1;
3569
                } else {
3570
                    $this->_debugLog->writeDebugLog('Evaluating Percentile of ', $this->showValue($arg));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3571
                    $multiplier = 0.01;
3572
                }
3573
                if (is_array($arg)) {
3574
                    self::checkMatrixOperands($arg, $multiplier, 2);
3575
                    try {
3576
                        $matrix1 = new Shared\JAMA\Matrix($arg);
3577
                        $matrixResult = $matrix1->arrayTimesEquals($multiplier);
3578
                        $result = $matrixResult->getArray();
3579
                    } catch (Exception $ex) {
3580
                        $this->_debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3581
                        $result = '#VALUE!';
3582
                    }
3583
                    $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3584
                    $stack->push('Value', $result);
3585
                } else {
3586
                    $this->executeNumericBinaryOperation($cellID, $multiplier, $arg, '*', 'arrayTimesEquals', $stack);
3587
                }
3588 57
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) {
3589
                $cellRef = null;
3590
                if (isset($matches[8])) {
3591
                    if ($pCell === null) {
3592
                        //                        We can't access the range, so return a REF error
3593
                        $cellValue = Calculation\Functions::REF();
3594
                    } else {
3595
                        $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
3596
                        if ($matches[2] > '') {
3597
                            $matches[2] = trim($matches[2], "\"'");
3598 View Code Duplication
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== 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...
3599
                                //    It's a Reference to an external spreadsheet (not currently supported)
3600
                                return $this->raiseFormulaError('Unable to access External Workbook');
3601
                            }
3602
                            $matches[2] = trim($matches[2], "\"'");
3603
                            $this->_debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]);
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3604 View Code Duplication
                            if ($pCellParent !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3605
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
3606
                            } else {
3607
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3608
                            }
3609
                            $this->_debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3610
                        } else {
3611
                            $this->_debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet');
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3612
                            if ($pCellParent !== null) {
3613
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
3614
                            } else {
3615
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3616
                            }
3617
                            $this->_debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3618
                        }
3619
                    }
3620
                } else {
3621
                    if ($pCell === null) {
3622
                        //                        We can't access the cell, so return a REF error
3623
                        $cellValue = Calculation\Functions::REF();
3624
                    } else {
3625
                        $cellRef = $matches[6] . $matches[7];
3626
                        if ($matches[2] > '') {
3627
                            $matches[2] = trim($matches[2], "\"'");
3628 View Code Duplication
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== 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...
3629
                                //    It's a Reference to an external spreadsheet (not currently supported)
3630
                                return $this->raiseFormulaError('Unable to access External Workbook');
3631
                            }
3632
                            $this->_debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]);
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3633
                            if ($pCellParent !== null) {
3634
                                $cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
3635
                                if ($cellSheet && $cellSheet->cellExists($cellRef)) {
3636
                                    $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
3637
                                    $pCell->attach($pCellParent);
3638
                                } else {
3639
                                    $cellValue = null;
3640
                                }
3641
                            } else {
3642
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3643
                            }
3644
                            $this->_debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3645
                        } else {
3646
                            $this->_debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet');
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3647
                            if ($pCellParent->isDataSet($cellRef)) {
3648
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
3649
                                $pCell->attach($pCellParent);
0 ignored issues
show
Bug introduced by
It seems like $pCellParent defined by $pCell !== null ? $pCell->getParent() : null on line 3412 can be null; however, PhpOffice\PhpSpreadsheet\Cell::attach() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
3650
                            } else {
3651
                                $cellValue = null;
3652
                            }
3653
                            $this->_debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3654
                        }
3655
                    }
3656
                }
3657
                $stack->push('Value', $cellValue, $cellRef);
3658
3659
            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
3660 57
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
3661
                $functionName = $matches[1];
3662
                $argCount = $stack->pop();
3663
                $argCount = $argCount['value'];
3664
                if ($functionName != 'MKMATRIX') {
3665
                    $this->_debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's'));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3666
                }
3667
                if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) {    // function
3668
                    if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
3669
                        $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
3670
                        $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']);
3671
                        $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']);
3672
                    } elseif (isset(self::$controlFunctions[$functionName])) {
3673
                        $functionCall = self::$controlFunctions[$functionName]['functionCall'];
3674
                        $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']);
3675
                        $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']);
3676
                    }
3677
                    // get the arguments for this function
3678
                    $args = $argArrayVals = [];
3679
                    for ($i = 0; $i < $argCount; ++$i) {
3680
                        $arg = $stack->pop();
3681
                        $a = $argCount - $i - 1;
3682
                        if (($passByReference) &&
0 ignored issues
show
Bug introduced by
The variable $passByReference 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...
3683
                            (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) &&
3684
                            (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) {
3685
                            if ($arg['reference'] === null) {
3686
                                $args[] = $cellID;
3687
                                if ($functionName != 'MKMATRIX') {
3688
                                    $argArrayVals[] = $this->showValue($cellID);
3689
                                }
3690
                            } else {
3691
                                $args[] = $arg['reference'];
3692
                                if ($functionName != 'MKMATRIX') {
3693
                                    $argArrayVals[] = $this->showValue($arg['reference']);
3694
                                }
3695
                            }
3696
                        } else {
3697
                            $args[] = self::unwrapResult($arg['value']);
3698
                            if ($functionName != 'MKMATRIX') {
3699
                                $argArrayVals[] = $this->showValue($arg['value']);
3700
                            }
3701
                        }
3702
                    }
3703
                    //    Reverse the order of the arguments
3704
                    krsort($args);
3705
                    if (($passByReference) && ($argCount == 0)) {
3706
                        $args[] = $cellID;
3707
                        $argArrayVals[] = $this->showValue($cellID);
3708
                    }
3709
3710
                    if ($functionName != 'MKMATRIX') {
3711
                        if ($this->_debugLog->getWriteDebugLog()) {
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3712
                            krsort($argArrayVals);
3713
                            $this->_debugLog->writeDebugLog('Evaluating ', self::localeFunc($functionName), '( ', implode(self::$localeArgumentSeparator . ' ', Calculation\Functions::flattenArray($argArrayVals)), ' )');
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3714
                        }
3715
                    }
3716
3717
                    //    Process the argument with the appropriate function call
3718
                    if ($passCellReference) {
0 ignored issues
show
Bug introduced by
The variable $passCellReference 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...
3719
                        $args[] = $pCell;
3720
                    }
3721
                    if (strpos($functionCall, '::') !== false) {
3722
                        $result = call_user_func_array(explode('::', $functionCall), $args);
0 ignored issues
show
Bug introduced by
The variable $functionCall 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...
3723
                    } else {
3724
                        foreach ($args as &$arg) {
3725
                            $arg = Calculation\Functions::flattenSingleValue($arg);
3726
                        }
3727
                        unset($arg);
3728
                        $result = call_user_func_array($functionCall, $args);
3729
                    }
3730
                    if ($functionName != 'MKMATRIX') {
3731
                        $this->_debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3732
                    }
3733
                    $stack->push('Value', self::wrapResult($result));
3734
                }
3735
            } else {
3736
                // if the token is a number, boolean, string or an Excel error, push it onto the stack
3737 57
                if (isset(self::$excelConstants[strtoupper($token)])) {
3738
                    $excelConstant = strtoupper($token);
3739
                    $stack->push('Constant Value', self::$excelConstants[$excelConstant]);
3740
                    $this->_debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant]));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3741 57
                } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token{0} == '"') || ($token{0} == '#')) {
3742 57
                    $stack->push('Value', $token);
3743
                // if the token is a named range, push the named range name onto the stack
3744
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
3745
                    $namedRange = $matches[6];
3746
                    $this->_debugLog->writeDebugLog('Evaluating Named Range ', $namedRange);
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3747
                    $cellValue = $this->extractNamedRange($namedRange, ((null !== $pCell) ? $pCellWorksheet : null), false);
3748
                    $pCell->attach($pCellParent);
0 ignored issues
show
Bug introduced by
It seems like $pCellParent defined by $pCell !== null ? $pCell->getParent() : null on line 3412 can be null; however, PhpOffice\PhpSpreadsheet\Cell::attach() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
3749
                    $this->_debugLog->writeDebugLog('Evaluation Result for named range ', $namedRange, ' is ', $this->showTypeDetails($cellValue));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3750
                    $stack->push('Named Range', $cellValue, $namedRange);
3751
                } else {
3752 57
                    return $this->raiseFormulaError("undefined variable '$token'");
3753
                }
3754
            }
3755
        }
3756
        // when we're out of tokens, the stack should have a single element, the final result
3757 57
        if ($stack->count() != 1) {
3758
            return $this->raiseFormulaError('internal error');
3759
        }
3760 57
        $output = $stack->pop();
3761 57
        $output = $output['value'];
3762
3763
//        if ((is_array($output)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% 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...
3764
//            return array_shift(Calculation\Functions::flattenArray($output));
3765
//        }
3766 57
        return $output;
3767
    }
3768
3769 4
    private function validateBinaryOperand($cellID, &$operand, &$stack)
0 ignored issues
show
Unused Code introduced by
The parameter $cellID 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...
3770
    {
3771 4
        if (is_array($operand)) {
3772
            if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) {
3773
                do {
3774
                    $operand = array_pop($operand);
3775
                } while (is_array($operand));
3776
            }
3777
        }
3778
        //    Numbers, matrices and booleans can pass straight through, as they're already valid
3779 4
        if (is_string($operand)) {
3780
            //    We only need special validations for the operand if it is a string
3781
            //    Start by stripping off the quotation marks we use to identify true excel string values internally
3782
            if ($operand > '' && $operand{0} == '"') {
3783
                $operand = self::unwrapResult($operand);
3784
            }
3785
            //    If the string is a numeric value, we treat it as a numeric, so no further testing
3786
            if (!is_numeric($operand)) {
3787
                //    If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations
3788
                if ($operand > '' && $operand{0} == '#') {
3789
                    $stack->push('Value', $operand);
3790
                    $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($operand));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3791
3792
                    return false;
3793
                } elseif (!Shared\StringHelper::convertToNumberIfFraction($operand)) {
3794
                    //    If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations
3795
                    $stack->push('Value', '#VALUE!');
3796
                    $this->_debugLog->writeDebugLog('Evaluation Result is a ', $this->showTypeDetails('#VALUE!'));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3797
3798
                    return false;
3799
                }
3800
            }
3801
        }
3802
3803
        //    return a true if the value of the operand is one that we can use in normal binary operations
3804 4
        return true;
3805
    }
3806
3807 52
    private function executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, &$stack, $recursingArrays = false)
3808
    {
3809
        //    If we're dealing with matrix operations, we want a matrix result
3810 52
        if ((is_array($operand1)) || (is_array($operand2))) {
3811
            $result = [];
3812
            if ((is_array($operand1)) && (!is_array($operand2))) {
3813 View Code Duplication
                foreach ($operand1 as $x => $operandData) {
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...
3814
                    $this->_debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3815
                    $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack);
3816
                    $r = $stack->pop();
3817
                    $result[$x] = $r['value'];
3818
                }
3819
            } elseif ((!is_array($operand1)) && (is_array($operand2))) {
3820 View Code Duplication
                foreach ($operand2 as $x => $operandData) {
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...
3821
                    $this->_debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operand1), ' ', $operation, ' ', $this->showValue($operandData));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3822
                    $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack);
3823
                    $r = $stack->pop();
3824
                    $result[$x] = $r['value'];
3825
                }
3826
            } else {
3827
                if (!$recursingArrays) {
3828
                    self::checkMatrixOperands($operand1, $operand2, 2);
3829
                }
3830
                foreach ($operand1 as $x => $operandData) {
3831
                    $this->_debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2[$x]));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3832
                    $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true);
3833
                    $r = $stack->pop();
3834
                    $result[$x] = $r['value'];
3835
                }
3836
            }
3837
            //    Log the result details
3838
            $this->_debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3839
            //    And push the result onto the stack
3840
            $stack->push('Array', $result);
3841
3842
            return true;
3843
        }
3844
3845
        //    Simple validate the two operands if they are string values
3846 52
        if (is_string($operand1) && $operand1 > '' && $operand1{0} == '"') {
3847 33
            $operand1 = self::unwrapResult($operand1);
3848
        }
3849 52
        if (is_string($operand2) && $operand2 > '' && $operand2{0} == '"') {
3850 33
            $operand2 = self::unwrapResult($operand2);
3851
        }
3852
3853
        // Use case insensitive comparaison if not OpenOffice mode
3854 52
        if (Calculation\Functions::getCompatibilityMode() != Calculation\Functions::COMPATIBILITY_OPENOFFICE) {
3855 52
            if (is_string($operand1)) {
3856 33
                $operand1 = strtoupper($operand1);
3857
            }
3858 52
            if (is_string($operand2)) {
3859 33
                $operand2 = strtoupper($operand2);
3860
            }
3861
        }
3862
3863 52
        $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && Calculation\Functions::getCompatibilityMode() == Calculation\Functions::COMPATIBILITY_OPENOFFICE;
3864
3865
        //    execute the necessary operation
3866
        switch ($operation) {
3867
            //    Greater than
3868 52 View Code Duplication
            case '>':
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...
3869 14
                if ($useLowercaseFirstComparison) {
3870 9
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) > 0;
3871
                } else {
3872 14
                    $result = ($operand1 > $operand2);
3873
                }
3874 14
                break;
3875
            //    Less than
3876 38 View Code Duplication
            case '<':
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...
3877 7
                if ($useLowercaseFirstComparison) {
3878 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) < 0;
3879
                } else {
3880 7
                    $result = ($operand1 < $operand2);
3881
                }
3882 7
                break;
3883
            //    Equality
3884 31 View Code Duplication
            case '=':
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...
3885 9
                if (is_numeric($operand1) && is_numeric($operand2)) {
3886 3
                    $result = (abs($operand1 - $operand2) < $this->delta);
3887
                } else {
3888 6
                    $result = strcmp($operand1, $operand2) == 0;
3889
                }
3890 9
                break;
3891
            //    Greater than or equal
3892 22
            case '>=':
3893 8
                if (is_numeric($operand1) && is_numeric($operand2)) {
3894 4
                    $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 > $operand2));
3895 4
                } elseif ($useLowercaseFirstComparison) {
3896 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0;
3897
                } else {
3898 4
                    $result = strcmp($operand1, $operand2) >= 0;
3899
                }
3900 8
                break;
3901
            //    Less than or equal
3902 14
            case '<=':
3903 7
                if (is_numeric($operand1) && is_numeric($operand2)) {
3904 3
                    $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 < $operand2));
3905 4
                } elseif ($useLowercaseFirstComparison) {
3906 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0;
3907
                } else {
3908 4
                    $result = strcmp($operand1, $operand2) <= 0;
3909
                }
3910 7
                break;
3911
            //    Inequality
3912 7 View Code Duplication
            case '<>':
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...
3913 7
                if (is_numeric($operand1) && is_numeric($operand2)) {
3914 3
                    $result = (abs($operand1 - $operand2) > 1E-14);
3915
                } else {
3916 4
                    $result = strcmp($operand1, $operand2) != 0;
3917
                }
3918 7
                break;
3919
        }
3920
3921
        //    Log the result details
3922 52
        $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
Bug introduced by
The variable $result 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...
3923
        //    And push the result onto the stack
3924 52
        $stack->push('Value', $result);
3925
3926 52
        return true;
3927
    }
3928
3929
    /**
3930
     * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters
3931
     * @param    string    $str1    First string value for the comparison
3932
     * @param    string    $str2    Second string value for the comparison
3933
     * @return   int
3934
     */
3935 21
    private function strcmpLowercaseFirst($str1, $str2)
3936
    {
3937 21
        $inversedStr1 = Shared\StringHelper::strCaseReverse($str1);
3938 21
        $inversedStr2 = Shared\StringHelper::strCaseReverse($str2);
3939
3940 21
        return strcmp($inversedStr1, $inversedStr2);
3941
    }
3942
3943
    /**
3944
     * @param string $matrixFunction
3945
     */
3946 4
    private function executeNumericBinaryOperation($cellID, $operand1, $operand2, $operation, $matrixFunction, &$stack)
3947
    {
3948
        //    Validate the two operands
3949 4
        if (!$this->validateBinaryOperand($cellID, $operand1, $stack)) {
3950
            return false;
3951
        }
3952 4
        if (!$this->validateBinaryOperand($cellID, $operand2, $stack)) {
3953
            return false;
3954
        }
3955
3956
        //    If either of the operands is a matrix, we need to treat them both as matrices
3957
        //        (converting the other operand to a matrix if need be); then perform the required
3958
        //        matrix operation
3959 4
        if ((is_array($operand1)) || (is_array($operand2))) {
3960
            //    Ensure that both operands are arrays/matrices of the same size
3961
            self::checkMatrixOperands($operand1, $operand2, 2);
3962
3963
            try {
3964
                //    Convert operand 1 from a PHP array to a matrix
3965
                $matrix = new Shared\JAMA\Matrix($operand1);
3966
                //    Perform the required operation against the operand 1 matrix, passing in operand 2
3967
                $matrixResult = $matrix->$matrixFunction($operand2);
3968
                $result = $matrixResult->getArray();
3969
            } catch (Exception $ex) {
3970
                $this->_debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3971
                $result = '#VALUE!';
3972
            }
3973
        } else {
3974 4
            if ((Calculation\Functions::getCompatibilityMode() != Calculation\Functions::COMPATIBILITY_OPENOFFICE) &&
3975 4
                ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) ||
3976 4
                 (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))) {
3977
                $result = Calculation\Functions::VALUE();
3978
            } else {
3979
                //    If we're dealing with non-matrix operations, execute the necessary operation
3980
                switch ($operation) {
3981
                    //    Addition
3982 4
                    case '+':
3983 2
                        $result = $operand1 + $operand2;
3984 2
                        break;
3985
                    //    Subtraction
3986 2
                    case '-':
3987 2
                        $result = $operand1 - $operand2;
3988 2
                        break;
3989
                    //    Multiplication
3990
                    case '*':
3991
                        $result = $operand1 * $operand2;
3992
                        break;
3993
                    //    Division
3994
                    case '/':
3995
                        if ($operand2 == 0) {
3996
                            //    Trap for Divide by Zero error
3997
                            $stack->push('Value', '#DIV/0!');
3998
                            $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails('#DIV/0!'));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
3999
4000
                            return false;
4001
                        } else {
4002
                            $result = $operand1 / $operand2;
4003
                        }
4004
                        break;
4005
                    //    Power
4006
                    case '^':
4007
                        $result = pow($operand1, $operand2);
4008
                        break;
4009
                }
4010
            }
4011
        }
4012
4013
        //    Log the result details
4014 4
        $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
Bug introduced by
The property _debugLog does not seem to exist. Did you mean debugLog?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
Bug introduced by
The variable $result 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...
4015
        //    And push the result onto the stack
4016 4
        $stack->push('Value', $result);
4017
4018 4
        return true;
4019
    }
4020
4021
    // trigger an error, but nicely, if need be
4022
    protected function raiseFormulaError($errorMessage)
4023
    {
4024
        $this->formulaError = $errorMessage;
4025
        $this->cyclicReferenceStack->clear();
0 ignored issues
show
Bug introduced by
The method clear cannot be called on $this->cyclicReferenceStack (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
4026
        if (!$this->suppressFormulaErrors) {
4027
            throw new Calculation\Exception($errorMessage);
4028
        }
4029
        trigger_error($errorMessage, E_USER_ERROR);
4030
    }
4031
4032
    /**
4033
     * Extract range values
4034
     *
4035
     * @param    string      &$pRange    String based range representation
4036
     * @param    Worksheet   $pSheet        Worksheet
4037
     * @param    bool     $resetLog    Flag indicating whether calculation log should be reset or not
4038
     * @throws   Calculation\Exception
4039
     * @return   mixed       Array of values in range if range contains more than one element. Otherwise, a single value is returned.
4040
     */
4041
    public function extractCellRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true)
4042
    {
4043
        // Return value
4044
        $returnValue = [];
4045
4046
        if ($pSheet !== null) {
4047
            $pSheetName = $pSheet->getTitle();
4048 View Code Duplication
            if (strpos($pRange, '!') !== 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...
4049
                list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
4050
                $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
4051
            }
4052
4053
            // Extract range
4054
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
4055
            $pRange = $pSheetName . '!' . $pRange;
4056
            if (!isset($aReferences[1])) {
4057
                //    Single cell in range
4058
                sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
0 ignored issues
show
Bug introduced by
The variable $currentRow does not exist. Did you forget to declare it?

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

Loading history...
4059
                $cellValue = null;
0 ignored issues
show
Unused Code introduced by
$cellValue 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...
4060 View Code Duplication
                if ($pSheet->cellExists($aReferences[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4061
                    $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
4062
                } else {
4063
                    $returnValue[$currentRow][$currentCol] = null;
4064
                }
4065
            } else {
4066
                // Extract cell data for all cells in the range
4067
                foreach ($aReferences as $reference) {
4068
                    // Extract range
4069
                    sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
4070
                    $cellValue = null;
0 ignored issues
show
Unused Code introduced by
$cellValue 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...
4071 View Code Duplication
                    if ($pSheet->cellExists($reference)) {
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...
4072
                        $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
4073
                    } else {
4074
                        $returnValue[$currentRow][$currentCol] = null;
4075
                    }
4076
                }
4077
            }
4078
        }
4079
4080
        return $returnValue;
4081
    }
4082
4083
    /**
4084
     * Extract range values
4085
     *
4086
     * @param    string       &$pRange    String based range representation
4087
     * @param    Worksheet    $pSheet        Worksheet
4088
     * @param    bool      $resetLog    Flag indicating whether calculation log should be reset or not
4089
     * @throws   Calculation\Exception
4090
     * @return   mixed        Array of values in range if range contains more than one element. Otherwise, a single value is returned.
4091
     */
4092
    public function extractNamedRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true)
4093
    {
4094
        // Return value
4095
        $returnValue = [];
4096
4097
        if ($pSheet !== null) {
4098
            $pSheetName = $pSheet->getTitle();
4099 View Code Duplication
            if (strpos($pRange, '!') !== 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...
4100
                list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
4101
                $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
4102
            }
4103
4104
            // Named range?
4105
            $namedRange = NamedRange::resolveRange($pRange, $pSheet);
4106
            if ($namedRange !== null) {
4107
                $pSheet = $namedRange->getWorksheet();
4108
                $pRange = $namedRange->getRange();
4109
                $splitRange = Cell::splitRange($pRange);
4110
                //    Convert row and column references
4111
                if (ctype_alpha($splitRange[0][0])) {
4112
                    $pRange = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow();
4113
                } elseif (ctype_digit($splitRange[0][0])) {
4114
                    $pRange = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1];
4115
                }
4116
            } else {
4117
                return Calculation\Functions::REF();
4118
            }
4119
4120
            // Extract range
4121
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
4122
            if (!isset($aReferences[1])) {
4123
                //    Single cell (or single column or row) in range
4124
                list($currentCol, $currentRow) = Cell::coordinateFromString($aReferences[0]);
4125
                $cellValue = null;
0 ignored issues
show
Unused Code introduced by
$cellValue 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...
4126 View Code Duplication
                if ($pSheet->cellExists($aReferences[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4127
                    $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
4128
                } else {
4129
                    $returnValue[$currentRow][$currentCol] = null;
4130
                }
4131
            } else {
4132
                // Extract cell data for all cells in the range
4133
                foreach ($aReferences as $reference) {
4134
                    // Extract range
4135
                    list($currentCol, $currentRow) = Cell::coordinateFromString($reference);
4136
                    $cellValue = null;
0 ignored issues
show
Unused Code introduced by
$cellValue 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...
4137 View Code Duplication
                    if ($pSheet->cellExists($reference)) {
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...
4138
                        $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
4139
                    } else {
4140
                        $returnValue[$currentRow][$currentCol] = null;
4141
                    }
4142
                }
4143
            }
4144
        }
4145
4146
        return $returnValue;
4147
    }
4148
4149
    /**
4150
     * Is a specific function implemented?
4151
     *
4152
     * @param    string    $pFunction    Function Name
4153
     * @return    bool
4154
     */
4155
    public function isImplemented($pFunction = '')
4156
    {
4157
        $pFunction = strtoupper($pFunction);
4158
        if (isset(self::$phpSpreadsheetFunctions[$pFunction])) {
4159
            return self::$phpSpreadsheetFunctions[$pFunction]['functionCall'] != 'Calculation\Categories::DUMMY';
4160
        } else {
4161
            return false;
4162
        }
4163
    }
4164
4165
    /**
4166
     * Get a list of all implemented functions as an array of function objects
4167
     *
4168
     * @return    array of Calculation\Categories
4169
     */
4170
    public function listFunctions()
4171
    {
4172
        $returnValue = [];
4173
4174
        foreach (self::$phpSpreadsheetFunctions as $functionName => $function) {
4175
            if ($function['functionCall'] != 'Calculation\Categories::DUMMY') {
4176
                $returnValue[$functionName] = new Calculation\Categories(
4177
                    $function['category'],
4178
                    $functionName,
4179
                    $function['functionCall']
4180
                );
4181
            }
4182
        }
4183
4184
        return $returnValue;
4185
    }
4186
4187
    /**
4188
     * Get a list of all Excel function names
4189
     *
4190
     * @return    array
4191
     */
4192
    public function listAllFunctionNames()
4193
    {
4194
        return array_keys(self::$phpSpreadsheetFunctions);
4195
    }
4196
4197
    /**
4198
     * Get a list of implemented Excel function names
4199
     *
4200
     * @return    array
4201
     */
4202
    public function listFunctionNames()
4203
    {
4204
        $returnValue = [];
4205
        foreach (self::$phpSpreadsheetFunctions as $functionName => $function) {
4206
            if ($function['functionCall'] != 'Calculation\Categories::DUMMY') {
4207
                $returnValue[] = $functionName;
4208
            }
4209
        }
4210
4211
        return $returnValue;
4212
    }
4213
}
4214