Completed
Push — develop ( a045a4...56245d )
by Adrien
17:07
created

Calculation::isImplemented()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Category;
6
use PhpOffice\PhpSpreadsheet\Calculation\Database;
7
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
8
use PhpOffice\PhpSpreadsheet\Calculation\Engineering;
9
use PhpOffice\PhpSpreadsheet\Calculation\Financial;
10
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
11
use PhpOffice\PhpSpreadsheet\Calculation\Logical;
12
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
13
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
14
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
15
use PhpOffice\PhpSpreadsheet\Calculation\TextData;
16
17 61
if (!defined('CALCULATION_REGEXP_CELLREF')) {
18
    //    Test for support of \P (multibyte options) in PCRE
19 61
    if (defined('PREG_BAD_UTF8_ERROR')) {
20
        //    Cell reference (cell or range of cells, with or without a sheet reference)
21 61
        define('CALCULATION_REGEXP_CELLREF', '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d{1,7})');
22
        //    Named Range of cells
23 61
        define('CALCULATION_REGEXP_NAMEDRANGE', '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_A-Z][_A-Z0-9\.]*)');
24
    } else {
25
        //    Cell reference (cell or range of cells, with or without a sheet reference)
26
        define('CALCULATION_REGEXP_CELLREF', '(((\w*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d+)');
27
        //    Named Range of cells
28
        define('CALCULATION_REGEXP_NAMEDRANGE', '(((\w*)|(\'.*\')|(\".*\"))!)?([_A-Z][_A-Z0-9\.]*)');
29
    }
30
}
31
32
/**
33
 * Copyright (c) 2006 - 2016 PhpSpreadsheet.
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 *
49
 * @category   PhpSpreadsheet
50
 *
51
 * @copyright  Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
52
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
53
 */
54
class Calculation
55
{
56
    /** Constants                */
57
    /** Regular Expressions        */
58
    //    Numeric operand
59
    const CALCULATION_REGEXP_NUMBER = '[-+]?\d*\.?\d+(e[-+]?\d+)?';
60
    //    String operand
61
    const CALCULATION_REGEXP_STRING = '"(?:[^"]|"")*"';
62
    //    Opening bracket
63
    const CALCULATION_REGEXP_OPENBRACE = '\(';
64
    //    Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
65
    const CALCULATION_REGEXP_FUNCTION = '@?([A-Z][A-Z0-9\.]*)[\s]*\(';
66
    //    Cell reference (cell or range of cells, with or without a sheet reference)
67
    const CALCULATION_REGEXP_CELLREF = CALCULATION_REGEXP_CELLREF;
68
    //    Named Range of cells
69
    const CALCULATION_REGEXP_NAMEDRANGE = CALCULATION_REGEXP_NAMEDRANGE;
70
    //    Error
71
    const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?';
72
73
    /** constants */
74
    const RETURN_ARRAY_AS_ERROR = 'error';
75
    const RETURN_ARRAY_AS_VALUE = 'value';
76
    const RETURN_ARRAY_AS_ARRAY = 'array';
77
78
    private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
79
80
    /**
81
     * Instance of this class.
82
     *
83
     * @var \PhpOffice\PhpSpreadsheet\Calculation
84
     */
85
    private static $instance;
86
87
    /**
88
     * Instance of the spreadsheet this Calculation Engine is using.
89
     *
90
     * @var PhpSpreadsheet
91
     */
92
    private $spreadsheet;
93
94
    /**
95
     * List of instances of the calculation engine that we've instantiated for individual spreadsheets.
96
     *
97
     * @var \PhpOffice\PhpSpreadsheet\Calculation[]
98
     */
99
    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...
100
101
    /**
102
     * Calculation cache.
103
     *
104
     * @var array
105
     */
106
    private $calculationCache = [];
107
108
    /**
109
     * Calculation cache enabled.
110
     *
111
     * @var bool
112
     */
113
    private $calculationCacheEnabled = true;
114
115
    /**
116
     * List of operators that can be used within formulae
117
     * The true/false value indicates whether it is a binary operator or a unary operator.
118
     *
119
     * @var array
120
     */
121
    private static $operators = [
122
        '+' => true, '-' => true, '*' => true, '/' => true,
123
        '^' => true, '&' => true, '%' => false, '~' => false,
124
        '>' => true, '<' => true, '=' => true, '>=' => true,
125
        '<=' => true, '<>' => true, '|' => true, ':' => true,
126
    ];
127
128
    /**
129
     * List of binary operators (those that expect two operands).
130
     *
131
     * @var array
132
     */
133
    private static $binaryOperators = [
134
        '+' => true, '-' => true, '*' => true, '/' => true,
135
        '^' => true, '&' => true, '>' => true, '<' => true,
136
        '=' => true, '>=' => true, '<=' => true, '<>' => true,
137
        '|' => true, ':' => true,
138
    ];
139
140
    /**
141
     * The debug log generated by the calculation engine.
142
     *
143
     * @var CalcEngine\Logger
144
     */
145
    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...
146
147
    /**
148
     * Flag to determine how formula errors should be handled
149
     *        If true, then a user error will be triggered
150
     *        If false, then an exception will be thrown.
151
     *
152
     * @var bool
153
     */
154
    public $suppressFormulaErrors = false;
155
156
    /**
157
     * Error message for any error that was raised/thrown by the calculation engine.
158
     *
159
     * @var string
160
     */
161
    public $formulaError = null;
162
163
    /**
164
     * An array of the nested cell references accessed by the calculation engine, used for the debug log.
165
     *
166
     * @var array of string
167
     */
168
    private $cyclicReferenceStack;
169
170
    private $cellStack = [];
171
172
    /**
173
     * Current iteration counter for cyclic formulae
174
     * If the value is 0 (or less) then cyclic formulae will throw an exception,
175
     *    otherwise they will iterate to the limit defined here before returning a result.
176
     *
177
     * @var int
178
     */
179
    private $cyclicFormulaCounter = 1;
180
181
    private $cyclicFormulaCell = '';
182
183
    /**
184
     * Number of iterations for cyclic formulae.
185
     *
186
     * @var int
187
     */
188
    public $cyclicFormulaCount = 1;
189
190
    /**
191
     * Epsilon Precision used for comparisons in calculations.
192
     *
193
     * @var float
194
     */
195
    private $delta = 0.1e-12;
196
197
    /**
198
     * The current locale setting.
199
     *
200
     * @var string
201
     */
202
    private static $localeLanguage = 'en_us'; //    US English    (default locale)
203
204
    /**
205
     * List of available locale settings
206
     * Note that this is read for the locale subdirectory only when requested.
207
     *
208
     * @var string[]
209
     */
210
    private static $validLocaleLanguages = [
211
        '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...
212
    ];
213
214
    /**
215
     * Locale-specific argument separator for function arguments.
216
     *
217
     * @var string
218
     */
219
    private static $localeArgumentSeparator = ',';
220
    private static $localeFunctions = [];
221
222
    /**
223
     * Locale-specific translations for Excel constants (True, False and Null).
224
     *
225
     * @var string[]
226
     */
227
    public static $localeBoolean = [
228
        'TRUE' => 'TRUE',
229
        'FALSE' => 'FALSE',
230
        'NULL' => 'NULL',
231
    ];
232
233
    /**
234
     * Excel constant string translations to their PHP equivalents
235
     * Constant conversion from text name/value to actual (datatyped) value.
236
     *
237
     * @var string[]
238
     */
239
    private static $excelConstants = [
240
        'TRUE' => true,
241
        'FALSE' => false,
242
        'NULL' => null,
243
    ];
244
245
    // PhpSpreadsheet functions
246
    private static $phpSpreadsheetFunctions = [
247
        'ABS' => [
248
            'category' => Category::CATEGORY_MATH_AND_TRIG,
249
            'functionCall' => 'abs',
250
            'argumentCount' => '1',
251
        ],
252
        'ACCRINT' => [
253
            'category' => Category::CATEGORY_FINANCIAL,
254
            'functionCall' => [Financial::class, 'ACCRINT'],
255
            'argumentCount' => '4-7',
256
        ],
257
        'ACCRINTM' => [
258
            'category' => Category::CATEGORY_FINANCIAL,
259
            'functionCall' => [Financial::class, 'ACCRINTM'],
260
            'argumentCount' => '3-5',
261
        ],
262
        'ACOS' => [
263
            'category' => Category::CATEGORY_MATH_AND_TRIG,
264
            'functionCall' => 'acos',
265
            'argumentCount' => '1',
266
        ],
267
        'ACOSH' => [
268
            'category' => Category::CATEGORY_MATH_AND_TRIG,
269
            'functionCall' => 'acosh',
270
            'argumentCount' => '1',
271
        ],
272
        'ADDRESS' => [
273
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
274
            'functionCall' => [LookupRef::class, 'cellAddress'],
275
            'argumentCount' => '2-5',
276
        ],
277
        'AMORDEGRC' => [
278
            'category' => Category::CATEGORY_FINANCIAL,
279
            'functionCall' => [Financial::class, 'AMORDEGRC'],
280
            'argumentCount' => '6,7',
281
        ],
282
        'AMORLINC' => [
283
            'category' => Category::CATEGORY_FINANCIAL,
284
            'functionCall' => [Financial::class, 'AMORLINC'],
285
            'argumentCount' => '6,7',
286
        ],
287
        'AND' => [
288
            'category' => Category::CATEGORY_LOGICAL,
289
            'functionCall' => [Logical::class, 'logicalAnd'],
290
            'argumentCount' => '1+',
291
        ],
292
        'AREAS' => [
293
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
294
            'functionCall' => [Functions::class, 'DUMMY'],
295
            'argumentCount' => '1',
296
        ],
297
        'ASC' => [
298
            'category' => Category::CATEGORY_TEXT_AND_DATA,
299
            'functionCall' => [Functions::class, 'DUMMY'],
300
            'argumentCount' => '1',
301
        ],
302
        'ASIN' => [
303
            'category' => Category::CATEGORY_MATH_AND_TRIG,
304
            'functionCall' => 'asin',
305
            'argumentCount' => '1',
306
        ],
307
        'ASINH' => [
308
            'category' => Category::CATEGORY_MATH_AND_TRIG,
309
            'functionCall' => 'asinh',
310
            'argumentCount' => '1',
311
        ],
312
        'ATAN' => [
313
            'category' => Category::CATEGORY_MATH_AND_TRIG,
314
            'functionCall' => 'atan',
315
            'argumentCount' => '1',
316
        ],
317
        'ATAN2' => [
318
            'category' => Category::CATEGORY_MATH_AND_TRIG,
319
            'functionCall' => [MathTrig::class, 'ATAN2'],
320
            'argumentCount' => '2',
321
        ],
322
        'ATANH' => [
323
            'category' => Category::CATEGORY_MATH_AND_TRIG,
324
            'functionCall' => 'atanh',
325
            'argumentCount' => '1',
326
        ],
327
        'AVEDEV' => [
328
            'category' => Category::CATEGORY_STATISTICAL,
329
            'functionCall' => [Statistical::class, 'AVEDEV'],
330
            'argumentCount' => '1+',
331
        ],
332
        'AVERAGE' => [
333
            'category' => Category::CATEGORY_STATISTICAL,
334
            'functionCall' => [Statistical::class, 'AVERAGE'],
335
            'argumentCount' => '1+',
336
        ],
337
        'AVERAGEA' => [
338
            'category' => Category::CATEGORY_STATISTICAL,
339
            'functionCall' => [Statistical::class, 'AVERAGEA'],
340
            'argumentCount' => '1+',
341
        ],
342
        'AVERAGEIF' => [
343
            'category' => Category::CATEGORY_STATISTICAL,
344
            'functionCall' => [Statistical::class, 'AVERAGEIF'],
345
            'argumentCount' => '2,3',
346
        ],
347
        'AVERAGEIFS' => [
348
            'category' => Category::CATEGORY_STATISTICAL,
349
            'functionCall' => [Functions::class, 'DUMMY'],
350
            'argumentCount' => '3+',
351
        ],
352
        'BAHTTEXT' => [
353
            'category' => Category::CATEGORY_TEXT_AND_DATA,
354
            'functionCall' => [Functions::class, 'DUMMY'],
355
            'argumentCount' => '1',
356
        ],
357
        'BESSELI' => [
358
            'category' => Category::CATEGORY_ENGINEERING,
359
            'functionCall' => [Engineering::class, 'BESSELI'],
360
            'argumentCount' => '2',
361
        ],
362
        'BESSELJ' => [
363
            'category' => Category::CATEGORY_ENGINEERING,
364
            'functionCall' => [Engineering::class, 'BESSELJ'],
365
            'argumentCount' => '2',
366
        ],
367
        'BESSELK' => [
368
            'category' => Category::CATEGORY_ENGINEERING,
369
            'functionCall' => [Engineering::class, 'BESSELK'],
370
            'argumentCount' => '2',
371
        ],
372
        'BESSELY' => [
373
            'category' => Category::CATEGORY_ENGINEERING,
374
            'functionCall' => [Engineering::class, 'BESSELY'],
375
            'argumentCount' => '2',
376
        ],
377
        'BETADIST' => [
378
            'category' => Category::CATEGORY_STATISTICAL,
379
            'functionCall' => [Statistical::class, 'BETADIST'],
380
            'argumentCount' => '3-5',
381
        ],
382
        'BETAINV' => [
383
            'category' => Category::CATEGORY_STATISTICAL,
384
            'functionCall' => [Statistical::class, 'BETAINV'],
385
            'argumentCount' => '3-5',
386
        ],
387
        'BIN2DEC' => [
388
            'category' => Category::CATEGORY_ENGINEERING,
389
            'functionCall' => [Engineering::class, 'BINTODEC'],
390
            'argumentCount' => '1',
391
        ],
392
        'BIN2HEX' => [
393
            'category' => Category::CATEGORY_ENGINEERING,
394
            'functionCall' => [Engineering::class, 'BINTOHEX'],
395
            'argumentCount' => '1,2',
396
        ],
397
        'BIN2OCT' => [
398
            'category' => Category::CATEGORY_ENGINEERING,
399
            'functionCall' => [Engineering::class, 'BINTOOCT'],
400
            'argumentCount' => '1,2',
401
        ],
402
        'BINOMDIST' => [
403
            'category' => Category::CATEGORY_STATISTICAL,
404
            'functionCall' => [Statistical::class, 'BINOMDIST'],
405
            'argumentCount' => '4',
406
        ],
407
        'CEILING' => [
408
            'category' => Category::CATEGORY_MATH_AND_TRIG,
409
            'functionCall' => [MathTrig::class, 'CEILING'],
410
            'argumentCount' => '2',
411
        ],
412
        'CELL' => [
413
            'category' => Category::CATEGORY_INFORMATION,
414
            'functionCall' => [Functions::class, 'DUMMY'],
415
            'argumentCount' => '1,2',
416
        ],
417
        'CHAR' => [
418
            'category' => Category::CATEGORY_TEXT_AND_DATA,
419
            'functionCall' => [TextData::class, 'CHARACTER'],
420
            'argumentCount' => '1',
421
        ],
422
        'CHIDIST' => [
423
            'category' => Category::CATEGORY_STATISTICAL,
424
            'functionCall' => [Statistical::class, 'CHIDIST'],
425
            'argumentCount' => '2',
426
        ],
427
        'CHIINV' => [
428
            'category' => Category::CATEGORY_STATISTICAL,
429
            'functionCall' => [Statistical::class, 'CHIINV'],
430
            'argumentCount' => '2',
431
        ],
432
        'CHITEST' => [
433
            'category' => Category::CATEGORY_STATISTICAL,
434
            'functionCall' => [Functions::class, 'DUMMY'],
435
            'argumentCount' => '2',
436
        ],
437
        'CHOOSE' => [
438
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
439
            'functionCall' => [LookupRef::class, 'CHOOSE'],
440
            'argumentCount' => '2+',
441
        ],
442
        'CLEAN' => [
443
            'category' => Category::CATEGORY_TEXT_AND_DATA,
444
            'functionCall' => [TextData::class, 'TRIMNONPRINTABLE'],
445
            'argumentCount' => '1',
446
        ],
447
        'CODE' => [
448
            'category' => Category::CATEGORY_TEXT_AND_DATA,
449
            'functionCall' => [TextData::class, 'ASCIICODE'],
450
            'argumentCount' => '1',
451
        ],
452
        'COLUMN' => [
453
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
454
            'functionCall' => [LookupRef::class, 'COLUMN'],
455
            'argumentCount' => '-1',
456
            'passByReference' => [true],
457
        ],
458
        'COLUMNS' => [
459
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
460
            'functionCall' => [LookupRef::class, 'COLUMNS'],
461
            'argumentCount' => '1',
462
        ],
463
        'COMBIN' => [
464
            'category' => Category::CATEGORY_MATH_AND_TRIG,
465
            'functionCall' => [MathTrig::class, 'COMBIN'],
466
            'argumentCount' => '2',
467
        ],
468
        'COMPLEX' => [
469
            'category' => Category::CATEGORY_ENGINEERING,
470
            'functionCall' => [Engineering::class, 'COMPLEX'],
471
            'argumentCount' => '2,3',
472
        ],
473
        'CONCATENATE' => [
474
            'category' => Category::CATEGORY_TEXT_AND_DATA,
475
            'functionCall' => [TextData::class, 'CONCATENATE'],
476
            'argumentCount' => '1+',
477
        ],
478
        'CONFIDENCE' => [
479
            'category' => Category::CATEGORY_STATISTICAL,
480
            'functionCall' => [Statistical::class, 'CONFIDENCE'],
481
            'argumentCount' => '3',
482
        ],
483
        'CONVERT' => [
484
            'category' => Category::CATEGORY_ENGINEERING,
485
            'functionCall' => [Engineering::class, 'CONVERTUOM'],
486
            'argumentCount' => '3',
487
        ],
488
        'CORREL' => [
489
            'category' => Category::CATEGORY_STATISTICAL,
490
            'functionCall' => [Statistical::class, 'CORREL'],
491
            'argumentCount' => '2',
492
        ],
493
        'COS' => [
494
            'category' => Category::CATEGORY_MATH_AND_TRIG,
495
            'functionCall' => 'cos',
496
            'argumentCount' => '1',
497
        ],
498
        'COSH' => [
499
            'category' => Category::CATEGORY_MATH_AND_TRIG,
500
            'functionCall' => 'cosh',
501
            'argumentCount' => '1',
502
        ],
503
        'COUNT' => [
504
            'category' => Category::CATEGORY_STATISTICAL,
505
            'functionCall' => [Statistical::class, 'COUNT'],
506
            'argumentCount' => '1+',
507
        ],
508
        'COUNTA' => [
509
            'category' => Category::CATEGORY_STATISTICAL,
510
            'functionCall' => [Statistical::class, 'COUNTA'],
511
            'argumentCount' => '1+',
512
        ],
513
        'COUNTBLANK' => [
514
            'category' => Category::CATEGORY_STATISTICAL,
515
            'functionCall' => [Statistical::class, 'COUNTBLANK'],
516
            'argumentCount' => '1',
517
        ],
518
        'COUNTIF' => [
519
            'category' => Category::CATEGORY_STATISTICAL,
520
            'functionCall' => [Statistical::class, 'COUNTIF'],
521
            'argumentCount' => '2',
522
        ],
523
        'COUNTIFS' => [
524
            'category' => Category::CATEGORY_STATISTICAL,
525
            'functionCall' => [Functions::class, 'DUMMY'],
526
            'argumentCount' => '2',
527
        ],
528
        'COUPDAYBS' => [
529
            'category' => Category::CATEGORY_FINANCIAL,
530
            'functionCall' => [Financial::class, 'COUPDAYBS'],
531
            'argumentCount' => '3,4',
532
        ],
533
        'COUPDAYS' => [
534
            'category' => Category::CATEGORY_FINANCIAL,
535
            'functionCall' => [Financial::class, 'COUPDAYS'],
536
            'argumentCount' => '3,4',
537
        ],
538
        'COUPDAYSNC' => [
539
            'category' => Category::CATEGORY_FINANCIAL,
540
            'functionCall' => [Financial::class, 'COUPDAYSNC'],
541
            'argumentCount' => '3,4',
542
        ],
543
        'COUPNCD' => [
544
            'category' => Category::CATEGORY_FINANCIAL,
545
            'functionCall' => [Financial::class, 'COUPNCD'],
546
            'argumentCount' => '3,4',
547
        ],
548
        'COUPNUM' => [
549
            'category' => Category::CATEGORY_FINANCIAL,
550
            'functionCall' => [Financial::class, 'COUPNUM'],
551
            'argumentCount' => '3,4',
552
        ],
553
        'COUPPCD' => [
554
            'category' => Category::CATEGORY_FINANCIAL,
555
            'functionCall' => [Financial::class, 'COUPPCD'],
556
            'argumentCount' => '3,4',
557
        ],
558
        'COVAR' => [
559
            'category' => Category::CATEGORY_STATISTICAL,
560
            'functionCall' => [Statistical::class, 'COVAR'],
561
            'argumentCount' => '2',
562
        ],
563
        'CRITBINOM' => [
564
            'category' => Category::CATEGORY_STATISTICAL,
565
            'functionCall' => [Statistical::class, 'CRITBINOM'],
566
            'argumentCount' => '3',
567
        ],
568
        'CUBEKPIMEMBER' => [
569
            'category' => Category::CATEGORY_CUBE,
570
            'functionCall' => [Functions::class, 'DUMMY'],
571
            'argumentCount' => '?',
572
        ],
573
        'CUBEMEMBER' => [
574
            'category' => Category::CATEGORY_CUBE,
575
            'functionCall' => [Functions::class, 'DUMMY'],
576
            'argumentCount' => '?',
577
        ],
578
        'CUBEMEMBERPROPERTY' => [
579
            'category' => Category::CATEGORY_CUBE,
580
            'functionCall' => [Functions::class, 'DUMMY'],
581
            'argumentCount' => '?',
582
        ],
583
        'CUBERANKEDMEMBER' => [
584
            'category' => Category::CATEGORY_CUBE,
585
            'functionCall' => [Functions::class, 'DUMMY'],
586
            'argumentCount' => '?',
587
        ],
588
        'CUBESET' => [
589
            'category' => Category::CATEGORY_CUBE,
590
            'functionCall' => [Functions::class, 'DUMMY'],
591
            'argumentCount' => '?',
592
        ],
593
        'CUBESETCOUNT' => [
594
            'category' => Category::CATEGORY_CUBE,
595
            'functionCall' => [Functions::class, 'DUMMY'],
596
            'argumentCount' => '?',
597
        ],
598
        'CUBEVALUE' => [
599
            'category' => Category::CATEGORY_CUBE,
600
            'functionCall' => [Functions::class, 'DUMMY'],
601
            'argumentCount' => '?',
602
        ],
603
        'CUMIPMT' => [
604
            'category' => Category::CATEGORY_FINANCIAL,
605
            'functionCall' => [Financial::class, 'CUMIPMT'],
606
            'argumentCount' => '6',
607
        ],
608
        'CUMPRINC' => [
609
            'category' => Category::CATEGORY_FINANCIAL,
610
            'functionCall' => [Financial::class, 'CUMPRINC'],
611
            'argumentCount' => '6',
612
        ],
613
        'DATE' => [
614
            'category' => Category::CATEGORY_DATE_AND_TIME,
615
            'functionCall' => [DateTime::class, 'DATE'],
616
            'argumentCount' => '3',
617
        ],
618
        'DATEDIF' => [
619
            'category' => Category::CATEGORY_DATE_AND_TIME,
620
            'functionCall' => [DateTime::class, 'DATEDIF'],
621
            'argumentCount' => '2,3',
622
        ],
623
        'DATEVALUE' => [
624
            'category' => Category::CATEGORY_DATE_AND_TIME,
625
            'functionCall' => [DateTime::class, 'DATEVALUE'],
626
            'argumentCount' => '1',
627
        ],
628
        'DAVERAGE' => [
629
            'category' => Category::CATEGORY_DATABASE,
630
            'functionCall' => [Database::class, 'DAVERAGE'],
631
            'argumentCount' => '3',
632
        ],
633
        'DAY' => [
634
            'category' => Category::CATEGORY_DATE_AND_TIME,
635
            'functionCall' => [DateTime::class, 'DAYOFMONTH'],
636
            'argumentCount' => '1',
637
        ],
638
        'DAYS360' => [
639
            'category' => Category::CATEGORY_DATE_AND_TIME,
640
            'functionCall' => [DateTime::class, 'DAYS360'],
641
            'argumentCount' => '2,3',
642
        ],
643
        'DB' => [
644
            'category' => Category::CATEGORY_FINANCIAL,
645
            'functionCall' => [Financial::class, 'DB'],
646
            'argumentCount' => '4,5',
647
        ],
648
        'DCOUNT' => [
649
            'category' => Category::CATEGORY_DATABASE,
650
            'functionCall' => [Database::class, 'DCOUNT'],
651
            'argumentCount' => '3',
652
        ],
653
        'DCOUNTA' => [
654
            'category' => Category::CATEGORY_DATABASE,
655
            'functionCall' => [Database::class, 'DCOUNTA'],
656
            'argumentCount' => '3',
657
        ],
658
        'DDB' => [
659
            'category' => Category::CATEGORY_FINANCIAL,
660
            'functionCall' => [Financial::class, 'DDB'],
661
            'argumentCount' => '4,5',
662
        ],
663
        'DEC2BIN' => [
664
            'category' => Category::CATEGORY_ENGINEERING,
665
            'functionCall' => [Engineering::class, 'DECTOBIN'],
666
            'argumentCount' => '1,2',
667
        ],
668
        'DEC2HEX' => [
669
            'category' => Category::CATEGORY_ENGINEERING,
670
            'functionCall' => [Engineering::class, 'DECTOHEX'],
671
            'argumentCount' => '1,2',
672
        ],
673
        'DEC2OCT' => [
674
            'category' => Category::CATEGORY_ENGINEERING,
675
            'functionCall' => [Engineering::class, 'DECTOOCT'],
676
            'argumentCount' => '1,2',
677
        ],
678
        'DEGREES' => [
679
            'category' => Category::CATEGORY_MATH_AND_TRIG,
680
            'functionCall' => 'rad2deg',
681
            'argumentCount' => '1',
682
        ],
683
        'DELTA' => [
684
            'category' => Category::CATEGORY_ENGINEERING,
685
            'functionCall' => [Engineering::class, 'DELTA'],
686
            'argumentCount' => '1,2',
687
        ],
688
        'DEVSQ' => [
689
            'category' => Category::CATEGORY_STATISTICAL,
690
            'functionCall' => [Statistical::class, 'DEVSQ'],
691
            'argumentCount' => '1+',
692
        ],
693
        'DGET' => [
694
            'category' => Category::CATEGORY_DATABASE,
695
            'functionCall' => [Database::class, 'DGET'],
696
            'argumentCount' => '3',
697
        ],
698
        'DISC' => [
699
            'category' => Category::CATEGORY_FINANCIAL,
700
            'functionCall' => [Financial::class, 'DISC'],
701
            'argumentCount' => '4,5',
702
        ],
703
        'DMAX' => [
704
            'category' => Category::CATEGORY_DATABASE,
705
            'functionCall' => [Database::class, 'DMAX'],
706
            'argumentCount' => '3',
707
        ],
708
        'DMIN' => [
709
            'category' => Category::CATEGORY_DATABASE,
710
            'functionCall' => [Database::class, 'DMIN'],
711
            'argumentCount' => '3',
712
        ],
713
        'DOLLAR' => [
714
            'category' => Category::CATEGORY_TEXT_AND_DATA,
715
            'functionCall' => [TextData::class, 'DOLLAR'],
716
            'argumentCount' => '1,2',
717
        ],
718
        'DOLLARDE' => [
719
            'category' => Category::CATEGORY_FINANCIAL,
720
            'functionCall' => [Financial::class, 'DOLLARDE'],
721
            'argumentCount' => '2',
722
        ],
723
        'DOLLARFR' => [
724
            'category' => Category::CATEGORY_FINANCIAL,
725
            'functionCall' => [Financial::class, 'DOLLARFR'],
726
            'argumentCount' => '2',
727
        ],
728
        'DPRODUCT' => [
729
            'category' => Category::CATEGORY_DATABASE,
730
            'functionCall' => [Database::class, 'DPRODUCT'],
731
            'argumentCount' => '3',
732
        ],
733
        'DSTDEV' => [
734
            'category' => Category::CATEGORY_DATABASE,
735
            'functionCall' => [Database::class, 'DSTDEV'],
736
            'argumentCount' => '3',
737
        ],
738
        'DSTDEVP' => [
739
            'category' => Category::CATEGORY_DATABASE,
740
            'functionCall' => [Database::class, 'DSTDEVP'],
741
            'argumentCount' => '3',
742
        ],
743
        'DSUM' => [
744
            'category' => Category::CATEGORY_DATABASE,
745
            'functionCall' => [Database::class, 'DSUM'],
746
            'argumentCount' => '3',
747
        ],
748
        'DURATION' => [
749
            'category' => Category::CATEGORY_FINANCIAL,
750
            'functionCall' => [Functions::class, 'DUMMY'],
751
            'argumentCount' => '5,6',
752
        ],
753
        'DVAR' => [
754
            'category' => Category::CATEGORY_DATABASE,
755
            'functionCall' => [Database::class, 'DVAR'],
756
            'argumentCount' => '3',
757
        ],
758
        'DVARP' => [
759
            'category' => Category::CATEGORY_DATABASE,
760
            'functionCall' => [Database::class, 'DVARP'],
761
            'argumentCount' => '3',
762
        ],
763
        'EDATE' => [
764
            'category' => Category::CATEGORY_DATE_AND_TIME,
765
            'functionCall' => [DateTime::class, 'EDATE'],
766
            'argumentCount' => '2',
767
        ],
768
        'EFFECT' => [
769
            'category' => Category::CATEGORY_FINANCIAL,
770
            'functionCall' => [Financial::class, 'EFFECT'],
771
            'argumentCount' => '2',
772
        ],
773
        'EOMONTH' => [
774
            'category' => Category::CATEGORY_DATE_AND_TIME,
775
            'functionCall' => [DateTime::class, 'EOMONTH'],
776
            'argumentCount' => '2',
777
        ],
778
        'ERF' => [
779
            'category' => Category::CATEGORY_ENGINEERING,
780
            'functionCall' => [Engineering::class, 'ERF'],
781
            'argumentCount' => '1,2',
782
        ],
783
        'ERFC' => [
784
            'category' => Category::CATEGORY_ENGINEERING,
785
            'functionCall' => [Engineering::class, 'ERFC'],
786
            'argumentCount' => '1',
787
        ],
788
        'ERROR.TYPE' => [
789
            'category' => Category::CATEGORY_INFORMATION,
790
            'functionCall' => [Functions::class, 'errorType'],
791
            'argumentCount' => '1',
792
        ],
793
        'EVEN' => [
794
            'category' => Category::CATEGORY_MATH_AND_TRIG,
795
            'functionCall' => [MathTrig::class, 'EVEN'],
796
            'argumentCount' => '1',
797
        ],
798
        'EXACT' => [
799
            'category' => Category::CATEGORY_TEXT_AND_DATA,
800
            'functionCall' => [Functions::class, 'DUMMY'],
801
            'argumentCount' => '2',
802
        ],
803
        'EXP' => [
804
            'category' => Category::CATEGORY_MATH_AND_TRIG,
805
            'functionCall' => 'exp',
806
            'argumentCount' => '1',
807
        ],
808
        'EXPONDIST' => [
809
            'category' => Category::CATEGORY_STATISTICAL,
810
            'functionCall' => [Statistical::class, 'EXPONDIST'],
811
            'argumentCount' => '3',
812
        ],
813
        'FACT' => [
814
            'category' => Category::CATEGORY_MATH_AND_TRIG,
815
            'functionCall' => [MathTrig::class, 'FACT'],
816
            'argumentCount' => '1',
817
        ],
818
        'FACTDOUBLE' => [
819
            'category' => Category::CATEGORY_MATH_AND_TRIG,
820
            'functionCall' => [MathTrig::class, 'FACTDOUBLE'],
821
            'argumentCount' => '1',
822
        ],
823
        'FALSE' => [
824
            'category' => Category::CATEGORY_LOGICAL,
825
            'functionCall' => [Logical::class, 'FALSE'],
826
            'argumentCount' => '0',
827
        ],
828
        'FDIST' => [
829
            'category' => Category::CATEGORY_STATISTICAL,
830
            'functionCall' => [Functions::class, 'DUMMY'],
831
            'argumentCount' => '3',
832
        ],
833
        'FIND' => [
834
            'category' => Category::CATEGORY_TEXT_AND_DATA,
835
            'functionCall' => [TextData::class, 'SEARCHSENSITIVE'],
836
            'argumentCount' => '2,3',
837
        ],
838
        'FINDB' => [
839
            'category' => Category::CATEGORY_TEXT_AND_DATA,
840
            'functionCall' => [TextData::class, 'SEARCHSENSITIVE'],
841
            'argumentCount' => '2,3',
842
        ],
843
        'FINV' => [
844
            'category' => Category::CATEGORY_STATISTICAL,
845
            'functionCall' => [Functions::class, 'DUMMY'],
846
            'argumentCount' => '3',
847
        ],
848
        'FISHER' => [
849
            'category' => Category::CATEGORY_STATISTICAL,
850
            'functionCall' => [Statistical::class, 'FISHER'],
851
            'argumentCount' => '1',
852
        ],
853
        'FISHERINV' => [
854
            'category' => Category::CATEGORY_STATISTICAL,
855
            'functionCall' => [Statistical::class, 'FISHERINV'],
856
            'argumentCount' => '1',
857
        ],
858
        'FIXED' => [
859
            'category' => Category::CATEGORY_TEXT_AND_DATA,
860
            'functionCall' => [TextData::class, 'FIXEDFORMAT'],
861
            'argumentCount' => '1-3',
862
        ],
863
        'FLOOR' => [
864
            'category' => Category::CATEGORY_MATH_AND_TRIG,
865
            'functionCall' => [MathTrig::class, 'FLOOR'],
866
            'argumentCount' => '2',
867
        ],
868
        'FORECAST' => [
869
            'category' => Category::CATEGORY_STATISTICAL,
870
            'functionCall' => [Statistical::class, 'FORECAST'],
871
            'argumentCount' => '3',
872
        ],
873
        'FREQUENCY' => [
874
            'category' => Category::CATEGORY_STATISTICAL,
875
            'functionCall' => [Functions::class, 'DUMMY'],
876
            'argumentCount' => '2',
877
        ],
878
        'FTEST' => [
879
            'category' => Category::CATEGORY_STATISTICAL,
880
            'functionCall' => [Functions::class, 'DUMMY'],
881
            'argumentCount' => '2',
882
        ],
883
        'FV' => [
884
            'category' => Category::CATEGORY_FINANCIAL,
885
            'functionCall' => [Financial::class, 'FV'],
886
            'argumentCount' => '3-5',
887
        ],
888
        'FVSCHEDULE' => [
889
            'category' => Category::CATEGORY_FINANCIAL,
890
            'functionCall' => [Financial::class, 'FVSCHEDULE'],
891
            'argumentCount' => '2',
892
        ],
893
        'GAMMADIST' => [
894
            'category' => Category::CATEGORY_STATISTICAL,
895
            'functionCall' => [Statistical::class, 'GAMMADIST'],
896
            'argumentCount' => '4',
897
        ],
898
        'GAMMAINV' => [
899
            'category' => Category::CATEGORY_STATISTICAL,
900
            'functionCall' => [Statistical::class, 'GAMMAINV'],
901
            'argumentCount' => '3',
902
        ],
903
        'GAMMALN' => [
904
            'category' => Category::CATEGORY_STATISTICAL,
905
            'functionCall' => [Statistical::class, 'GAMMALN'],
906
            'argumentCount' => '1',
907
        ],
908
        'GCD' => [
909
            'category' => Category::CATEGORY_MATH_AND_TRIG,
910
            'functionCall' => [MathTrig::class, 'GCD'],
911
            'argumentCount' => '1+',
912
        ],
913
        'GEOMEAN' => [
914
            'category' => Category::CATEGORY_STATISTICAL,
915
            'functionCall' => [Statistical::class, 'GEOMEAN'],
916
            'argumentCount' => '1+',
917
        ],
918
        'GESTEP' => [
919
            'category' => Category::CATEGORY_ENGINEERING,
920
            'functionCall' => [Engineering::class, 'GESTEP'],
921
            'argumentCount' => '1,2',
922
        ],
923
        'GETPIVOTDATA' => [
924
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
925
            'functionCall' => [Functions::class, 'DUMMY'],
926
            'argumentCount' => '2+',
927
        ],
928
        'GROWTH' => [
929
            'category' => Category::CATEGORY_STATISTICAL,
930
            'functionCall' => [Statistical::class, 'GROWTH'],
931
            'argumentCount' => '1-4',
932
        ],
933
        'HARMEAN' => [
934
            'category' => Category::CATEGORY_STATISTICAL,
935
            'functionCall' => [Statistical::class, 'HARMEAN'],
936
            'argumentCount' => '1+',
937
        ],
938
        'HEX2BIN' => [
939
            'category' => Category::CATEGORY_ENGINEERING,
940
            'functionCall' => [Engineering::class, 'HEXTOBIN'],
941
            'argumentCount' => '1,2',
942
        ],
943
        'HEX2DEC' => [
944
            'category' => Category::CATEGORY_ENGINEERING,
945
            'functionCall' => [Engineering::class, 'HEXTODEC'],
946
            'argumentCount' => '1',
947
        ],
948
        'HEX2OCT' => [
949
            'category' => Category::CATEGORY_ENGINEERING,
950
            'functionCall' => [Engineering::class, 'HEXTOOCT'],
951
            'argumentCount' => '1,2',
952
        ],
953
        'HLOOKUP' => [
954
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
955
            'functionCall' => [LookupRef::class, 'HLOOKUP'],
956
            'argumentCount' => '3,4',
957
        ],
958
        'HOUR' => [
959
            'category' => Category::CATEGORY_DATE_AND_TIME,
960
            'functionCall' => [DateTime::class, 'HOUROFDAY'],
961
            'argumentCount' => '1',
962
        ],
963
        'HYPERLINK' => [
964
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
965
            'functionCall' => [LookupRef::class, 'HYPERLINK'],
966
            'argumentCount' => '1,2',
967
            'passCellReference' => true,
968
        ],
969
        'HYPGEOMDIST' => [
970
            'category' => Category::CATEGORY_STATISTICAL,
971
            'functionCall' => [Statistical::class, 'HYPGEOMDIST'],
972
            'argumentCount' => '4',
973
        ],
974
        'IF' => [
975
            'category' => Category::CATEGORY_LOGICAL,
976
            'functionCall' => [Logical::class, 'statementIf'],
977
            'argumentCount' => '1-3',
978
        ],
979
        'IFERROR' => [
980
            'category' => Category::CATEGORY_LOGICAL,
981
            'functionCall' => [Logical::class, 'IFERROR'],
982
            'argumentCount' => '2',
983
        ],
984
        'IMABS' => [
985
            'category' => Category::CATEGORY_ENGINEERING,
986
            'functionCall' => [Engineering::class, 'IMABS'],
987
            'argumentCount' => '1',
988
        ],
989
        'IMAGINARY' => [
990
            'category' => Category::CATEGORY_ENGINEERING,
991
            'functionCall' => [Engineering::class, 'IMAGINARY'],
992
            'argumentCount' => '1',
993
        ],
994
        'IMARGUMENT' => [
995
            'category' => Category::CATEGORY_ENGINEERING,
996
            'functionCall' => [Engineering::class, 'IMARGUMENT'],
997
            'argumentCount' => '1',
998
        ],
999
        'IMCONJUGATE' => [
1000
            'category' => Category::CATEGORY_ENGINEERING,
1001
            'functionCall' => [Engineering::class, 'IMCONJUGATE'],
1002
            'argumentCount' => '1',
1003
        ],
1004
        'IMCOS' => [
1005
            'category' => Category::CATEGORY_ENGINEERING,
1006
            'functionCall' => [Engineering::class, 'IMCOS'],
1007
            'argumentCount' => '1',
1008
        ],
1009
        'IMDIV' => [
1010
            'category' => Category::CATEGORY_ENGINEERING,
1011
            'functionCall' => [Engineering::class, 'IMDIV'],
1012
            'argumentCount' => '2',
1013
        ],
1014
        'IMEXP' => [
1015
            'category' => Category::CATEGORY_ENGINEERING,
1016
            'functionCall' => [Engineering::class, 'IMEXP'],
1017
            'argumentCount' => '1',
1018
        ],
1019
        'IMLN' => [
1020
            'category' => Category::CATEGORY_ENGINEERING,
1021
            'functionCall' => [Engineering::class, 'IMLN'],
1022
            'argumentCount' => '1',
1023
        ],
1024
        'IMLOG10' => [
1025
            'category' => Category::CATEGORY_ENGINEERING,
1026
            'functionCall' => [Engineering::class, 'IMLOG10'],
1027
            'argumentCount' => '1',
1028
        ],
1029
        'IMLOG2' => [
1030
            'category' => Category::CATEGORY_ENGINEERING,
1031
            'functionCall' => [Engineering::class, 'IMLOG2'],
1032
            'argumentCount' => '1',
1033
        ],
1034
        'IMPOWER' => [
1035
            'category' => Category::CATEGORY_ENGINEERING,
1036
            'functionCall' => [Engineering::class, 'IMPOWER'],
1037
            'argumentCount' => '2',
1038
        ],
1039
        'IMPRODUCT' => [
1040
            'category' => Category::CATEGORY_ENGINEERING,
1041
            'functionCall' => [Engineering::class, 'IMPRODUCT'],
1042
            'argumentCount' => '1+',
1043
        ],
1044
        'IMREAL' => [
1045
            'category' => Category::CATEGORY_ENGINEERING,
1046
            'functionCall' => [Engineering::class, 'IMREAL'],
1047
            'argumentCount' => '1',
1048
        ],
1049
        'IMSIN' => [
1050
            'category' => Category::CATEGORY_ENGINEERING,
1051
            'functionCall' => [Engineering::class, 'IMSIN'],
1052
            'argumentCount' => '1',
1053
        ],
1054
        'IMSQRT' => [
1055
            'category' => Category::CATEGORY_ENGINEERING,
1056
            'functionCall' => [Engineering::class, 'IMSQRT'],
1057
            'argumentCount' => '1',
1058
        ],
1059
        'IMSUB' => [
1060
            'category' => Category::CATEGORY_ENGINEERING,
1061
            'functionCall' => [Engineering::class, 'IMSUB'],
1062
            'argumentCount' => '2',
1063
        ],
1064
        'IMSUM' => [
1065
            'category' => Category::CATEGORY_ENGINEERING,
1066
            'functionCall' => [Engineering::class, 'IMSUM'],
1067
            'argumentCount' => '1+',
1068
        ],
1069
        'INDEX' => [
1070
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1071
            'functionCall' => [LookupRef::class, 'INDEX'],
1072
            'argumentCount' => '1-4',
1073
        ],
1074
        'INDIRECT' => [
1075
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1076
            'functionCall' => [LookupRef::class, 'INDIRECT'],
1077
            'argumentCount' => '1,2',
1078
            'passCellReference' => true,
1079
        ],
1080
        'INFO' => [
1081
            'category' => Category::CATEGORY_INFORMATION,
1082
            'functionCall' => [Functions::class, 'DUMMY'],
1083
            'argumentCount' => '1',
1084
        ],
1085
        'INT' => [
1086
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1087
            'functionCall' => [MathTrig::class, 'INT'],
1088
            'argumentCount' => '1',
1089
        ],
1090
        'INTERCEPT' => [
1091
            'category' => Category::CATEGORY_STATISTICAL,
1092
            'functionCall' => [Statistical::class, 'INTERCEPT'],
1093
            'argumentCount' => '2',
1094
        ],
1095
        'INTRATE' => [
1096
            'category' => Category::CATEGORY_FINANCIAL,
1097
            'functionCall' => [Financial::class, 'INTRATE'],
1098
            'argumentCount' => '4,5',
1099
        ],
1100
        'IPMT' => [
1101
            'category' => Category::CATEGORY_FINANCIAL,
1102
            'functionCall' => [Financial::class, 'IPMT'],
1103
            'argumentCount' => '4-6',
1104
        ],
1105
        'IRR' => [
1106
            'category' => Category::CATEGORY_FINANCIAL,
1107
            'functionCall' => [Financial::class, 'IRR'],
1108
            'argumentCount' => '1,2',
1109
        ],
1110
        'ISBLANK' => [
1111
            'category' => Category::CATEGORY_INFORMATION,
1112
            'functionCall' => [Functions::class, 'isBlank'],
1113
            'argumentCount' => '1',
1114
        ],
1115
        'ISERR' => [
1116
            'category' => Category::CATEGORY_INFORMATION,
1117
            'functionCall' => [Functions::class, 'isErr'],
1118
            'argumentCount' => '1',
1119
        ],
1120
        'ISERROR' => [
1121
            'category' => Category::CATEGORY_INFORMATION,
1122
            'functionCall' => [Functions::class, 'isError'],
1123
            'argumentCount' => '1',
1124
        ],
1125
        'ISEVEN' => [
1126
            'category' => Category::CATEGORY_INFORMATION,
1127
            'functionCall' => [Functions::class, 'isEven'],
1128
            'argumentCount' => '1',
1129
        ],
1130
        'ISLOGICAL' => [
1131
            'category' => Category::CATEGORY_INFORMATION,
1132
            'functionCall' => [Functions::class, 'isLogical'],
1133
            'argumentCount' => '1',
1134
        ],
1135
        'ISNA' => [
1136
            'category' => Category::CATEGORY_INFORMATION,
1137
            'functionCall' => [Functions::class, 'isNa'],
1138
            'argumentCount' => '1',
1139
        ],
1140
        'ISNONTEXT' => [
1141
            'category' => Category::CATEGORY_INFORMATION,
1142
            'functionCall' => [Functions::class, 'isNonText'],
1143
            'argumentCount' => '1',
1144
        ],
1145
        'ISNUMBER' => [
1146
            'category' => Category::CATEGORY_INFORMATION,
1147
            'functionCall' => [Functions::class, 'isNumber'],
1148
            'argumentCount' => '1',
1149
        ],
1150
        'ISODD' => [
1151
            'category' => Category::CATEGORY_INFORMATION,
1152
            'functionCall' => [Functions::class, 'isOdd'],
1153
            'argumentCount' => '1',
1154
        ],
1155
        'ISPMT' => [
1156
            'category' => Category::CATEGORY_FINANCIAL,
1157
            'functionCall' => [Financial::class, 'ISPMT'],
1158
            'argumentCount' => '4',
1159
        ],
1160
        'ISREF' => [
1161
            'category' => Category::CATEGORY_INFORMATION,
1162
            'functionCall' => [Functions::class, 'DUMMY'],
1163
            'argumentCount' => '1',
1164
        ],
1165
        'ISTEXT' => [
1166
            'category' => Category::CATEGORY_INFORMATION,
1167
            'functionCall' => [Functions::class, 'isText'],
1168
            'argumentCount' => '1',
1169
        ],
1170
        'JIS' => [
1171
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1172
            'functionCall' => [Functions::class, 'DUMMY'],
1173
            'argumentCount' => '1',
1174
        ],
1175
        'KURT' => [
1176
            'category' => Category::CATEGORY_STATISTICAL,
1177
            'functionCall' => [Statistical::class, 'KURT'],
1178
            'argumentCount' => '1+',
1179
        ],
1180
        'LARGE' => [
1181
            'category' => Category::CATEGORY_STATISTICAL,
1182
            'functionCall' => [Statistical::class, 'LARGE'],
1183
            'argumentCount' => '2',
1184
        ],
1185
        'LCM' => [
1186
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1187
            'functionCall' => [MathTrig::class, 'LCM'],
1188
            'argumentCount' => '1+',
1189
        ],
1190
        'LEFT' => [
1191
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1192
            'functionCall' => [TextData::class, 'LEFT'],
1193
            'argumentCount' => '1,2',
1194
        ],
1195
        'LEFTB' => [
1196
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1197
            'functionCall' => [TextData::class, 'LEFT'],
1198
            'argumentCount' => '1,2',
1199
        ],
1200
        'LEN' => [
1201
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1202
            'functionCall' => [TextData::class, 'STRINGLENGTH'],
1203
            'argumentCount' => '1',
1204
        ],
1205
        'LENB' => [
1206
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1207
            'functionCall' => [TextData::class, 'STRINGLENGTH'],
1208
            'argumentCount' => '1',
1209
        ],
1210
        'LINEST' => [
1211
            'category' => Category::CATEGORY_STATISTICAL,
1212
            'functionCall' => [Statistical::class, 'LINEST'],
1213
            'argumentCount' => '1-4',
1214
        ],
1215
        'LN' => [
1216
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1217
            'functionCall' => 'log',
1218
            'argumentCount' => '1',
1219
        ],
1220
        'LOG' => [
1221
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1222
            'functionCall' => [MathTrig::class, 'logBase'],
1223
            'argumentCount' => '1,2',
1224
        ],
1225
        'LOG10' => [
1226
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1227
            'functionCall' => 'log10',
1228
            'argumentCount' => '1',
1229
        ],
1230
        'LOGEST' => [
1231
            'category' => Category::CATEGORY_STATISTICAL,
1232
            'functionCall' => [Statistical::class, 'LOGEST'],
1233
            'argumentCount' => '1-4',
1234
        ],
1235
        'LOGINV' => [
1236
            'category' => Category::CATEGORY_STATISTICAL,
1237
            'functionCall' => [Statistical::class, 'LOGINV'],
1238
            'argumentCount' => '3',
1239
        ],
1240
        'LOGNORMDIST' => [
1241
            'category' => Category::CATEGORY_STATISTICAL,
1242
            'functionCall' => [Statistical::class, 'LOGNORMDIST'],
1243
            'argumentCount' => '3',
1244
        ],
1245
        'LOOKUP' => [
1246
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1247
            'functionCall' => [LookupRef::class, 'LOOKUP'],
1248
            'argumentCount' => '2,3',
1249
        ],
1250
        'LOWER' => [
1251
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1252
            'functionCall' => [TextData::class, 'LOWERCASE'],
1253
            'argumentCount' => '1',
1254
        ],
1255
        'MATCH' => [
1256
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1257
            'functionCall' => [LookupRef::class, 'MATCH'],
1258
            'argumentCount' => '2,3',
1259
        ],
1260
        'MAX' => [
1261
            'category' => Category::CATEGORY_STATISTICAL,
1262
            'functionCall' => [Statistical::class, 'MAX'],
1263
            'argumentCount' => '1+',
1264
        ],
1265
        'MAXA' => [
1266
            'category' => Category::CATEGORY_STATISTICAL,
1267
            'functionCall' => [Statistical::class, 'MAXA'],
1268
            'argumentCount' => '1+',
1269
        ],
1270
        'MAXIF' => [
1271
            'category' => Category::CATEGORY_STATISTICAL,
1272
            'functionCall' => [Statistical::class, 'MAXIF'],
1273
            'argumentCount' => '2+',
1274
        ],
1275
        'MDETERM' => [
1276
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1277
            'functionCall' => [MathTrig::class, 'MDETERM'],
1278
            'argumentCount' => '1',
1279
        ],
1280
        'MDURATION' => [
1281
            'category' => Category::CATEGORY_FINANCIAL,
1282
            'functionCall' => [Functions::class, 'DUMMY'],
1283
            'argumentCount' => '5,6',
1284
        ],
1285
        'MEDIAN' => [
1286
            'category' => Category::CATEGORY_STATISTICAL,
1287
            'functionCall' => [Statistical::class, 'MEDIAN'],
1288
            'argumentCount' => '1+',
1289
        ],
1290
        'MEDIANIF' => [
1291
            'category' => Category::CATEGORY_STATISTICAL,
1292
            'functionCall' => [Functions::class, 'DUMMY'],
1293
            'argumentCount' => '2+',
1294
        ],
1295
        'MID' => [
1296
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1297
            'functionCall' => [TextData::class, 'MID'],
1298
            'argumentCount' => '3',
1299
        ],
1300
        'MIDB' => [
1301
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1302
            'functionCall' => [TextData::class, 'MID'],
1303
            'argumentCount' => '3',
1304
        ],
1305
        'MIN' => [
1306
            'category' => Category::CATEGORY_STATISTICAL,
1307
            'functionCall' => [Statistical::class, 'MIN'],
1308
            'argumentCount' => '1+',
1309
        ],
1310
        'MINA' => [
1311
            'category' => Category::CATEGORY_STATISTICAL,
1312
            'functionCall' => [Statistical::class, 'MINA'],
1313
            'argumentCount' => '1+',
1314
        ],
1315
        'MINIF' => [
1316
            'category' => Category::CATEGORY_STATISTICAL,
1317
            'functionCall' => [Statistical::class, 'MINIF'],
1318
            'argumentCount' => '2+',
1319
        ],
1320
        'MINUTE' => [
1321
            'category' => Category::CATEGORY_DATE_AND_TIME,
1322
            'functionCall' => [DateTime::class, 'MINUTE'],
1323
            'argumentCount' => '1',
1324
        ],
1325
        'MINVERSE' => [
1326
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1327
            'functionCall' => [MathTrig::class, 'MINVERSE'],
1328
            'argumentCount' => '1',
1329
        ],
1330
        'MIRR' => [
1331
            'category' => Category::CATEGORY_FINANCIAL,
1332
            'functionCall' => [Financial::class, 'MIRR'],
1333
            'argumentCount' => '3',
1334
        ],
1335
        'MMULT' => [
1336
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1337
            'functionCall' => [MathTrig::class, 'MMULT'],
1338
            'argumentCount' => '2',
1339
        ],
1340
        'MOD' => [
1341
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1342
            'functionCall' => [MathTrig::class, 'MOD'],
1343
            'argumentCount' => '2',
1344
        ],
1345
        'MODE' => [
1346
            'category' => Category::CATEGORY_STATISTICAL,
1347
            'functionCall' => [Statistical::class, 'MODE'],
1348
            'argumentCount' => '1+',
1349
        ],
1350
        'MONTH' => [
1351
            'category' => Category::CATEGORY_DATE_AND_TIME,
1352
            'functionCall' => [DateTime::class, 'MONTHOFYEAR'],
1353
            'argumentCount' => '1',
1354
        ],
1355
        'MROUND' => [
1356
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1357
            'functionCall' => [MathTrig::class, 'MROUND'],
1358
            'argumentCount' => '2',
1359
        ],
1360
        'MULTINOMIAL' => [
1361
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1362
            'functionCall' => [MathTrig::class, 'MULTINOMIAL'],
1363
            'argumentCount' => '1+',
1364
        ],
1365
        'N' => [
1366
            'category' => Category::CATEGORY_INFORMATION,
1367
            'functionCall' => [Functions::class, 'n'],
1368
            'argumentCount' => '1',
1369
        ],
1370
        'NA' => [
1371
            'category' => Category::CATEGORY_INFORMATION,
1372
            'functionCall' => [Functions::class, 'NA'],
1373
            'argumentCount' => '0',
1374
        ],
1375
        'NEGBINOMDIST' => [
1376
            'category' => Category::CATEGORY_STATISTICAL,
1377
            'functionCall' => [Statistical::class, 'NEGBINOMDIST'],
1378
            'argumentCount' => '3',
1379
        ],
1380
        'NETWORKDAYS' => [
1381
            'category' => Category::CATEGORY_DATE_AND_TIME,
1382
            'functionCall' => [DateTime::class, 'NETWORKDAYS'],
1383
            'argumentCount' => '2+',
1384
        ],
1385
        'NOMINAL' => [
1386
            'category' => Category::CATEGORY_FINANCIAL,
1387
            'functionCall' => [Financial::class, 'NOMINAL'],
1388
            'argumentCount' => '2',
1389
        ],
1390
        'NORMDIST' => [
1391
            'category' => Category::CATEGORY_STATISTICAL,
1392
            'functionCall' => [Statistical::class, 'NORMDIST'],
1393
            'argumentCount' => '4',
1394
        ],
1395
        'NORMINV' => [
1396
            'category' => Category::CATEGORY_STATISTICAL,
1397
            'functionCall' => [Statistical::class, 'NORMINV'],
1398
            'argumentCount' => '3',
1399
        ],
1400
        'NORMSDIST' => [
1401
            'category' => Category::CATEGORY_STATISTICAL,
1402
            'functionCall' => [Statistical::class, 'NORMSDIST'],
1403
            'argumentCount' => '1',
1404
        ],
1405
        'NORMSINV' => [
1406
            'category' => Category::CATEGORY_STATISTICAL,
1407
            'functionCall' => [Statistical::class, 'NORMSINV'],
1408
            'argumentCount' => '1',
1409
        ],
1410
        'NOT' => [
1411
            'category' => Category::CATEGORY_LOGICAL,
1412
            'functionCall' => [Logical::class, 'NOT'],
1413
            'argumentCount' => '1',
1414
        ],
1415
        'NOW' => [
1416
            'category' => Category::CATEGORY_DATE_AND_TIME,
1417
            'functionCall' => [DateTime::class, 'DATETIMENOW'],
1418
            'argumentCount' => '0',
1419
        ],
1420
        'NPER' => [
1421
            'category' => Category::CATEGORY_FINANCIAL,
1422
            'functionCall' => [Financial::class, 'NPER'],
1423
            'argumentCount' => '3-5',
1424
        ],
1425
        'NPV' => [
1426
            'category' => Category::CATEGORY_FINANCIAL,
1427
            'functionCall' => [Financial::class, 'NPV'],
1428
            'argumentCount' => '2+',
1429
        ],
1430
        'OCT2BIN' => [
1431
            'category' => Category::CATEGORY_ENGINEERING,
1432
            'functionCall' => [Engineering::class, 'OCTTOBIN'],
1433
            'argumentCount' => '1,2',
1434
        ],
1435
        'OCT2DEC' => [
1436
            'category' => Category::CATEGORY_ENGINEERING,
1437
            'functionCall' => [Engineering::class, 'OCTTODEC'],
1438
            'argumentCount' => '1',
1439
        ],
1440
        'OCT2HEX' => [
1441
            'category' => Category::CATEGORY_ENGINEERING,
1442
            'functionCall' => [Engineering::class, 'OCTTOHEX'],
1443
            'argumentCount' => '1,2',
1444
        ],
1445
        'ODD' => [
1446
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1447
            'functionCall' => [MathTrig::class, 'ODD'],
1448
            'argumentCount' => '1',
1449
        ],
1450
        'ODDFPRICE' => [
1451
            'category' => Category::CATEGORY_FINANCIAL,
1452
            'functionCall' => [Functions::class, 'DUMMY'],
1453
            'argumentCount' => '8,9',
1454
        ],
1455
        'ODDFYIELD' => [
1456
            'category' => Category::CATEGORY_FINANCIAL,
1457
            'functionCall' => [Functions::class, 'DUMMY'],
1458
            'argumentCount' => '8,9',
1459
        ],
1460
        'ODDLPRICE' => [
1461
            'category' => Category::CATEGORY_FINANCIAL,
1462
            'functionCall' => [Functions::class, 'DUMMY'],
1463
            'argumentCount' => '7,8',
1464
        ],
1465
        'ODDLYIELD' => [
1466
            'category' => Category::CATEGORY_FINANCIAL,
1467
            'functionCall' => [Functions::class, 'DUMMY'],
1468
            'argumentCount' => '7,8',
1469
        ],
1470
        'OFFSET' => [
1471
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1472
            'functionCall' => [LookupRef::class, 'OFFSET'],
1473
            'argumentCount' => '3-5',
1474
            'passCellReference' => true,
1475
            'passByReference' => [true],
1476
        ],
1477
        'OR' => [
1478
            'category' => Category::CATEGORY_LOGICAL,
1479
            'functionCall' => [Logical::class, 'logicalOr'],
1480
            'argumentCount' => '1+',
1481
        ],
1482
        'PEARSON' => [
1483
            'category' => Category::CATEGORY_STATISTICAL,
1484
            'functionCall' => [Statistical::class, 'CORREL'],
1485
            'argumentCount' => '2',
1486
        ],
1487
        'PERCENTILE' => [
1488
            'category' => Category::CATEGORY_STATISTICAL,
1489
            'functionCall' => [Statistical::class, 'PERCENTILE'],
1490
            'argumentCount' => '2',
1491
        ],
1492
        'PERCENTRANK' => [
1493
            'category' => Category::CATEGORY_STATISTICAL,
1494
            'functionCall' => [Statistical::class, 'PERCENTRANK'],
1495
            'argumentCount' => '2,3',
1496
        ],
1497
        'PERMUT' => [
1498
            'category' => Category::CATEGORY_STATISTICAL,
1499
            'functionCall' => [Statistical::class, 'PERMUT'],
1500
            'argumentCount' => '2',
1501
        ],
1502
        'PHONETIC' => [
1503
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1504
            'functionCall' => [Functions::class, 'DUMMY'],
1505
            'argumentCount' => '1',
1506
        ],
1507
        'PI' => [
1508
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1509
            'functionCall' => 'pi',
1510
            'argumentCount' => '0',
1511
        ],
1512
        'PMT' => [
1513
            'category' => Category::CATEGORY_FINANCIAL,
1514
            'functionCall' => [Financial::class, 'PMT'],
1515
            'argumentCount' => '3-5',
1516
        ],
1517
        'POISSON' => [
1518
            'category' => Category::CATEGORY_STATISTICAL,
1519
            'functionCall' => [Statistical::class, 'POISSON'],
1520
            'argumentCount' => '3',
1521
        ],
1522
        'POWER' => [
1523
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1524
            'functionCall' => [MathTrig::class, 'POWER'],
1525
            'argumentCount' => '2',
1526
        ],
1527
        'PPMT' => [
1528
            'category' => Category::CATEGORY_FINANCIAL,
1529
            'functionCall' => [Financial::class, 'PPMT'],
1530
            'argumentCount' => '4-6',
1531
        ],
1532
        'PRICE' => [
1533
            'category' => Category::CATEGORY_FINANCIAL,
1534
            'functionCall' => [Financial::class, 'PRICE'],
1535
            'argumentCount' => '6,7',
1536
        ],
1537
        'PRICEDISC' => [
1538
            'category' => Category::CATEGORY_FINANCIAL,
1539
            'functionCall' => [Financial::class, 'PRICEDISC'],
1540
            'argumentCount' => '4,5',
1541
        ],
1542
        'PRICEMAT' => [
1543
            'category' => Category::CATEGORY_FINANCIAL,
1544
            'functionCall' => [Financial::class, 'PRICEMAT'],
1545
            'argumentCount' => '5,6',
1546
        ],
1547
        'PROB' => [
1548
            'category' => Category::CATEGORY_STATISTICAL,
1549
            'functionCall' => [Functions::class, 'DUMMY'],
1550
            'argumentCount' => '3,4',
1551
        ],
1552
        'PRODUCT' => [
1553
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1554
            'functionCall' => [MathTrig::class, 'PRODUCT'],
1555
            'argumentCount' => '1+',
1556
        ],
1557
        'PROPER' => [
1558
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1559
            'functionCall' => [TextData::class, 'PROPERCASE'],
1560
            'argumentCount' => '1',
1561
        ],
1562
        'PV' => [
1563
            'category' => Category::CATEGORY_FINANCIAL,
1564
            'functionCall' => [Financial::class, 'PV'],
1565
            'argumentCount' => '3-5',
1566
        ],
1567
        'QUARTILE' => [
1568
            'category' => Category::CATEGORY_STATISTICAL,
1569
            'functionCall' => [Statistical::class, 'QUARTILE'],
1570
            'argumentCount' => '2',
1571
        ],
1572
        'QUOTIENT' => [
1573
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1574
            'functionCall' => [MathTrig::class, 'QUOTIENT'],
1575
            'argumentCount' => '2',
1576
        ],
1577
        'RADIANS' => [
1578
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1579
            'functionCall' => 'deg2rad',
1580
            'argumentCount' => '1',
1581
        ],
1582
        'RAND' => [
1583
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1584
            'functionCall' => [MathTrig::class, 'RAND'],
1585
            'argumentCount' => '0',
1586
        ],
1587
        'RANDBETWEEN' => [
1588
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1589
            'functionCall' => [MathTrig::class, 'RAND'],
1590
            'argumentCount' => '2',
1591
        ],
1592
        'RANK' => [
1593
            'category' => Category::CATEGORY_STATISTICAL,
1594
            'functionCall' => [Statistical::class, 'RANK'],
1595
            'argumentCount' => '2,3',
1596
        ],
1597
        'RATE' => [
1598
            'category' => Category::CATEGORY_FINANCIAL,
1599
            'functionCall' => [Financial::class, 'RATE'],
1600
            'argumentCount' => '3-6',
1601
        ],
1602
        'RECEIVED' => [
1603
            'category' => Category::CATEGORY_FINANCIAL,
1604
            'functionCall' => [Financial::class, 'RECEIVED'],
1605
            'argumentCount' => '4-5',
1606
        ],
1607
        'REPLACE' => [
1608
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1609
            'functionCall' => [TextData::class, 'REPLACE'],
1610
            'argumentCount' => '4',
1611
        ],
1612
        'REPLACEB' => [
1613
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1614
            'functionCall' => [TextData::class, 'REPLACE'],
1615
            'argumentCount' => '4',
1616
        ],
1617
        'REPT' => [
1618
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1619
            'functionCall' => 'str_repeat',
1620
            'argumentCount' => '2',
1621
        ],
1622
        'RIGHT' => [
1623
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1624
            'functionCall' => [TextData::class, 'RIGHT'],
1625
            'argumentCount' => '1,2',
1626
        ],
1627
        'RIGHTB' => [
1628
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1629
            'functionCall' => [TextData::class, 'RIGHT'],
1630
            'argumentCount' => '1,2',
1631
        ],
1632
        'ROMAN' => [
1633
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1634
            'functionCall' => [MathTrig::class, 'ROMAN'],
1635
            'argumentCount' => '1,2',
1636
        ],
1637
        'ROUND' => [
1638
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1639
            'functionCall' => 'round',
1640
            'argumentCount' => '2',
1641
        ],
1642
        'ROUNDDOWN' => [
1643
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1644
            'functionCall' => [MathTrig::class, 'ROUNDDOWN'],
1645
            'argumentCount' => '2',
1646
        ],
1647
        'ROUNDUP' => [
1648
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1649
            'functionCall' => [MathTrig::class, 'ROUNDUP'],
1650
            'argumentCount' => '2',
1651
        ],
1652
        'ROW' => [
1653
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1654
            'functionCall' => [LookupRef::class, 'ROW'],
1655
            'argumentCount' => '-1',
1656
            'passByReference' => [true],
1657
        ],
1658
        'ROWS' => [
1659
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1660
            'functionCall' => [LookupRef::class, 'ROWS'],
1661
            'argumentCount' => '1',
1662
        ],
1663
        'RSQ' => [
1664
            'category' => Category::CATEGORY_STATISTICAL,
1665
            'functionCall' => [Statistical::class, 'RSQ'],
1666
            'argumentCount' => '2',
1667
        ],
1668
        'RTD' => [
1669
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1670
            'functionCall' => [Functions::class, 'DUMMY'],
1671
            'argumentCount' => '1+',
1672
        ],
1673
        'SEARCH' => [
1674
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1675
            'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'],
1676
            'argumentCount' => '2,3',
1677
        ],
1678
        'SEARCHB' => [
1679
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1680
            'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'],
1681
            'argumentCount' => '2,3',
1682
        ],
1683
        'SECOND' => [
1684
            'category' => Category::CATEGORY_DATE_AND_TIME,
1685
            'functionCall' => [DateTime::class, 'SECOND'],
1686
            'argumentCount' => '1',
1687
        ],
1688
        'SERIESSUM' => [
1689
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1690
            'functionCall' => [MathTrig::class, 'SERIESSUM'],
1691
            'argumentCount' => '4',
1692
        ],
1693
        'SIGN' => [
1694
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1695
            'functionCall' => [MathTrig::class, 'SIGN'],
1696
            'argumentCount' => '1',
1697
        ],
1698
        'SIN' => [
1699
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1700
            'functionCall' => 'sin',
1701
            'argumentCount' => '1',
1702
        ],
1703
        'SINH' => [
1704
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1705
            'functionCall' => 'sinh',
1706
            'argumentCount' => '1',
1707
        ],
1708
        'SKEW' => [
1709
            'category' => Category::CATEGORY_STATISTICAL,
1710
            'functionCall' => [Statistical::class, 'SKEW'],
1711
            'argumentCount' => '1+',
1712
        ],
1713
        'SLN' => [
1714
            'category' => Category::CATEGORY_FINANCIAL,
1715
            'functionCall' => [Financial::class, 'SLN'],
1716
            'argumentCount' => '3',
1717
        ],
1718
        'SLOPE' => [
1719
            'category' => Category::CATEGORY_STATISTICAL,
1720
            'functionCall' => [Statistical::class, 'SLOPE'],
1721
            'argumentCount' => '2',
1722
        ],
1723
        'SMALL' => [
1724
            'category' => Category::CATEGORY_STATISTICAL,
1725
            'functionCall' => [Statistical::class, 'SMALL'],
1726
            'argumentCount' => '2',
1727
        ],
1728
        'SQRT' => [
1729
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1730
            'functionCall' => 'sqrt',
1731
            'argumentCount' => '1',
1732
        ],
1733
        'SQRTPI' => [
1734
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1735
            'functionCall' => [MathTrig::class, 'SQRTPI'],
1736
            'argumentCount' => '1',
1737
        ],
1738
        'STANDARDIZE' => [
1739
            'category' => Category::CATEGORY_STATISTICAL,
1740
            'functionCall' => [Statistical::class, 'STANDARDIZE'],
1741
            'argumentCount' => '3',
1742
        ],
1743
        'STDEV' => [
1744
            'category' => Category::CATEGORY_STATISTICAL,
1745
            'functionCall' => [Statistical::class, 'STDEV'],
1746
            'argumentCount' => '1+',
1747
        ],
1748
        'STDEVA' => [
1749
            'category' => Category::CATEGORY_STATISTICAL,
1750
            'functionCall' => [Statistical::class, 'STDEVA'],
1751
            'argumentCount' => '1+',
1752
        ],
1753
        'STDEVP' => [
1754
            'category' => Category::CATEGORY_STATISTICAL,
1755
            'functionCall' => [Statistical::class, 'STDEVP'],
1756
            'argumentCount' => '1+',
1757
        ],
1758
        'STDEVPA' => [
1759
            'category' => Category::CATEGORY_STATISTICAL,
1760
            'functionCall' => [Statistical::class, 'STDEVPA'],
1761
            'argumentCount' => '1+',
1762
        ],
1763
        'STEYX' => [
1764
            'category' => Category::CATEGORY_STATISTICAL,
1765
            'functionCall' => [Statistical::class, 'STEYX'],
1766
            'argumentCount' => '2',
1767
        ],
1768
        'SUBSTITUTE' => [
1769
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1770
            'functionCall' => [TextData::class, 'SUBSTITUTE'],
1771
            'argumentCount' => '3,4',
1772
        ],
1773
        'SUBTOTAL' => [
1774
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1775
            'functionCall' => [MathTrig::class, 'SUBTOTAL'],
1776
            'argumentCount' => '2+',
1777
        ],
1778
        'SUM' => [
1779
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1780
            'functionCall' => [MathTrig::class, 'SUM'],
1781
            'argumentCount' => '1+',
1782
        ],
1783
        'SUMIF' => [
1784
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1785
            'functionCall' => [MathTrig::class, 'SUMIF'],
1786
            'argumentCount' => '2,3',
1787
        ],
1788
        'SUMIFS' => [
1789
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1790
            'functionCall' => [MathTrig::class, 'SUMIFS'],
1791
            'argumentCount' => '3+',
1792
        ],
1793
        'SUMPRODUCT' => [
1794
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1795
            'functionCall' => [MathTrig::class, 'SUMPRODUCT'],
1796
            'argumentCount' => '1+',
1797
        ],
1798
        'SUMSQ' => [
1799
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1800
            'functionCall' => [MathTrig::class, 'SUMSQ'],
1801
            'argumentCount' => '1+',
1802
        ],
1803
        'SUMX2MY2' => [
1804
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1805
            'functionCall' => [MathTrig::class, 'SUMX2MY2'],
1806
            'argumentCount' => '2',
1807
        ],
1808
        'SUMX2PY2' => [
1809
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1810
            'functionCall' => [MathTrig::class, 'SUMX2PY2'],
1811
            'argumentCount' => '2',
1812
        ],
1813
        'SUMXMY2' => [
1814
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1815
            'functionCall' => [MathTrig::class, 'SUMXMY2'],
1816
            'argumentCount' => '2',
1817
        ],
1818
        'SYD' => [
1819
            'category' => Category::CATEGORY_FINANCIAL,
1820
            'functionCall' => [Financial::class, 'SYD'],
1821
            'argumentCount' => '4',
1822
        ],
1823
        'T' => [
1824
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1825
            'functionCall' => [TextData::class, 'RETURNSTRING'],
1826
            'argumentCount' => '1',
1827
        ],
1828
        'TAN' => [
1829
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1830
            'functionCall' => 'tan',
1831
            'argumentCount' => '1',
1832
        ],
1833
        'TANH' => [
1834
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1835
            'functionCall' => 'tanh',
1836
            'argumentCount' => '1',
1837
        ],
1838
        'TBILLEQ' => [
1839
            'category' => Category::CATEGORY_FINANCIAL,
1840
            'functionCall' => [Financial::class, 'TBILLEQ'],
1841
            'argumentCount' => '3',
1842
        ],
1843
        'TBILLPRICE' => [
1844
            'category' => Category::CATEGORY_FINANCIAL,
1845
            'functionCall' => [Financial::class, 'TBILLPRICE'],
1846
            'argumentCount' => '3',
1847
        ],
1848
        'TBILLYIELD' => [
1849
            'category' => Category::CATEGORY_FINANCIAL,
1850
            'functionCall' => [Financial::class, 'TBILLYIELD'],
1851
            'argumentCount' => '3',
1852
        ],
1853
        'TDIST' => [
1854
            'category' => Category::CATEGORY_STATISTICAL,
1855
            'functionCall' => [Statistical::class, 'TDIST'],
1856
            'argumentCount' => '3',
1857
        ],
1858
        'TEXT' => [
1859
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1860
            'functionCall' => [TextData::class, 'TEXTFORMAT'],
1861
            'argumentCount' => '2',
1862
        ],
1863
        'TIME' => [
1864
            'category' => Category::CATEGORY_DATE_AND_TIME,
1865
            'functionCall' => [DateTime::class, 'TIME'],
1866
            'argumentCount' => '3',
1867
        ],
1868
        'TIMEVALUE' => [
1869
            'category' => Category::CATEGORY_DATE_AND_TIME,
1870
            'functionCall' => [DateTime::class, 'TIMEVALUE'],
1871
            'argumentCount' => '1',
1872
        ],
1873
        'TINV' => [
1874
            'category' => Category::CATEGORY_STATISTICAL,
1875
            'functionCall' => [Statistical::class, 'TINV'],
1876
            'argumentCount' => '2',
1877
        ],
1878
        'TODAY' => [
1879
            'category' => Category::CATEGORY_DATE_AND_TIME,
1880
            'functionCall' => [DateTime::class, 'DATENOW'],
1881
            'argumentCount' => '0',
1882
        ],
1883
        'TRANSPOSE' => [
1884
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1885
            'functionCall' => [LookupRef::class, 'TRANSPOSE'],
1886
            'argumentCount' => '1',
1887
        ],
1888
        'TREND' => [
1889
            'category' => Category::CATEGORY_STATISTICAL,
1890
            'functionCall' => [Statistical::class, 'TREND'],
1891
            'argumentCount' => '1-4',
1892
        ],
1893
        'TRIM' => [
1894
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1895
            'functionCall' => [TextData::class, 'TRIMSPACES'],
1896
            'argumentCount' => '1',
1897
        ],
1898
        'TRIMMEAN' => [
1899
            'category' => Category::CATEGORY_STATISTICAL,
1900
            'functionCall' => [Statistical::class, 'TRIMMEAN'],
1901
            'argumentCount' => '2',
1902
        ],
1903
        'TRUE' => [
1904
            'category' => Category::CATEGORY_LOGICAL,
1905
            'functionCall' => [Logical::class, 'TRUE'],
1906
            'argumentCount' => '0',
1907
        ],
1908
        'TRUNC' => [
1909
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1910
            'functionCall' => [MathTrig::class, 'TRUNC'],
1911
            'argumentCount' => '1,2',
1912
        ],
1913
        'TTEST' => [
1914
            'category' => Category::CATEGORY_STATISTICAL,
1915
            'functionCall' => [Functions::class, 'DUMMY'],
1916
            'argumentCount' => '4',
1917
        ],
1918
        'TYPE' => [
1919
            'category' => Category::CATEGORY_INFORMATION,
1920
            'functionCall' => [Functions::class, 'TYPE'],
1921
            'argumentCount' => '1',
1922
        ],
1923
        'UPPER' => [
1924
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1925
            'functionCall' => [TextData::class, 'UPPERCASE'],
1926
            'argumentCount' => '1',
1927
        ],
1928
        'USDOLLAR' => [
1929
            'category' => Category::CATEGORY_FINANCIAL,
1930
            'functionCall' => [Functions::class, 'DUMMY'],
1931
            'argumentCount' => '2',
1932
        ],
1933
        'VALUE' => [
1934
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1935
            'functionCall' => [TextData::class, 'VALUE'],
1936
            'argumentCount' => '1',
1937
        ],
1938
        'VAR' => [
1939
            'category' => Category::CATEGORY_STATISTICAL,
1940
            'functionCall' => [Statistical::class, 'VARFunc'],
1941
            'argumentCount' => '1+',
1942
        ],
1943
        'VARA' => [
1944
            'category' => Category::CATEGORY_STATISTICAL,
1945
            'functionCall' => [Statistical::class, 'VARA'],
1946
            'argumentCount' => '1+',
1947
        ],
1948
        'VARP' => [
1949
            'category' => Category::CATEGORY_STATISTICAL,
1950
            'functionCall' => [Statistical::class, 'VARP'],
1951
            'argumentCount' => '1+',
1952
        ],
1953
        'VARPA' => [
1954
            'category' => Category::CATEGORY_STATISTICAL,
1955
            'functionCall' => [Statistical::class, 'VARPA'],
1956
            'argumentCount' => '1+',
1957
        ],
1958
        'VDB' => [
1959
            'category' => Category::CATEGORY_FINANCIAL,
1960
            'functionCall' => [Functions::class, 'DUMMY'],
1961
            'argumentCount' => '5-7',
1962
        ],
1963
        'VLOOKUP' => [
1964
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1965
            'functionCall' => [LookupRef::class, 'VLOOKUP'],
1966
            'argumentCount' => '3,4',
1967
        ],
1968
        'WEEKDAY' => [
1969
            'category' => Category::CATEGORY_DATE_AND_TIME,
1970
            'functionCall' => [DateTime::class, 'WEEKDAY'],
1971
            'argumentCount' => '1,2',
1972
        ],
1973
        'WEEKNUM' => [
1974
            'category' => Category::CATEGORY_DATE_AND_TIME,
1975
            'functionCall' => [DateTime::class, 'WEEKNUM'],
1976
            'argumentCount' => '1,2',
1977
        ],
1978
        'WEIBULL' => [
1979
            'category' => Category::CATEGORY_STATISTICAL,
1980
            'functionCall' => [Statistical::class, 'WEIBULL'],
1981
            'argumentCount' => '4',
1982
        ],
1983
        'WORKDAY' => [
1984
            'category' => Category::CATEGORY_DATE_AND_TIME,
1985
            'functionCall' => [DateTime::class, 'WORKDAY'],
1986
            'argumentCount' => '2+',
1987
        ],
1988
        'XIRR' => [
1989
            'category' => Category::CATEGORY_FINANCIAL,
1990
            'functionCall' => [Financial::class, 'XIRR'],
1991
            'argumentCount' => '2,3',
1992
        ],
1993
        'XNPV' => [
1994
            'category' => Category::CATEGORY_FINANCIAL,
1995
            'functionCall' => [Financial::class, 'XNPV'],
1996
            'argumentCount' => '3',
1997
        ],
1998
        'YEAR' => [
1999
            'category' => Category::CATEGORY_DATE_AND_TIME,
2000
            'functionCall' => [DateTime::class, 'YEAR'],
2001
            'argumentCount' => '1',
2002
        ],
2003
        'YEARFRAC' => [
2004
            'category' => Category::CATEGORY_DATE_AND_TIME,
2005
            'functionCall' => [DateTime::class, 'YEARFRAC'],
2006
            'argumentCount' => '2,3',
2007
        ],
2008
        'YIELD' => [
2009
            'category' => Category::CATEGORY_FINANCIAL,
2010
            'functionCall' => [Functions::class, 'DUMMY'],
2011
            'argumentCount' => '6,7',
2012
        ],
2013
        'YIELDDISC' => [
2014
            'category' => Category::CATEGORY_FINANCIAL,
2015
            'functionCall' => [Financial::class, 'YIELDDISC'],
2016
            'argumentCount' => '4,5',
2017
        ],
2018
        'YIELDMAT' => [
2019
            'category' => Category::CATEGORY_FINANCIAL,
2020
            'functionCall' => [Financial::class, 'YIELDMAT'],
2021
            'argumentCount' => '5,6',
2022
        ],
2023
        'ZTEST' => [
2024
            'category' => Category::CATEGORY_STATISTICAL,
2025
            'functionCall' => [Statistical::class, 'ZTEST'],
2026
            'argumentCount' => '2-3',
2027
        ],
2028
    ];
2029
2030
    //    Internal functions used for special control purposes
2031
    private static $controlFunctions = [
2032
        'MKMATRIX' => [
2033
            'argumentCount' => '*',
2034
            'functionCall' => 'self::mkMatrix',
2035
        ],
2036
    ];
2037
2038 62
    public function __construct(Spreadsheet $spreadsheet = null)
2039
    {
2040 62
        $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...
2041
2042 62
        $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...
2043 62
        $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...
2044 62
        $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...
2045 62
    }
2046
2047
    private static function loadLocales()
2048
    {
2049
        $localeFileDirectory = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet/locale/';
2050
        foreach (glob($localeFileDirectory . '/*', GLOB_ONLYDIR) as $filename) {
2051
            $filename = substr($filename, strlen($localeFileDirectory) + 1);
2052
            if ($filename != 'en') {
2053
                self::$validLocaleLanguages[] = $filename;
2054
            }
2055
        }
2056
    }
2057
2058
    /**
2059
     * Get an instance of this class.
2060
     *
2061
     * @param   Spreadsheet $spreadsheet  Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object,
2062
     *                                    or NULL to create a standalone claculation engine
2063
     *
2064
     * @return Calculation
2065
     */
2066 118
    public static function getInstance(Spreadsheet $spreadsheet = null)
2067
    {
2068 118
        if ($spreadsheet !== null) {
2069 60
            $instance = $spreadsheet->getCalculationEngine();
2070 60
            if (isset($instance)) {
2071 60
                return $instance;
2072
            }
2073
        }
2074
2075 61
        if (!isset(self::$instance) || (self::$instance === null)) {
2076 3
            self::$instance = new \PhpOffice\PhpSpreadsheet\Calculation();
2077
        }
2078
2079 61
        return self::$instance;
2080
    }
2081
2082
    /**
2083
     * Unset an instance of this class.
2084
     *
2085
     * @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...
2086
     */
2087 1
    public function __destruct()
2088
    {
2089 1
        $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...
2090 1
    }
2091
2092
    /**
2093
     * Flush the calculation cache for any existing instance of this class
2094
     *        but only if a \PhpOffice\PhpSpreadsheet\Calculation instance exists.
2095
     */
2096
    public function flushInstance()
2097
    {
2098
        $this->clearCalculationCache();
2099
    }
2100
2101
    /**
2102
     * Get the debuglog for this claculation engine instance.
2103
     *
2104
     * @return CalcEngine\Logger
2105
     */
2106 59
    public function getDebugLog()
2107
    {
2108 59
        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...
2109
    }
2110
2111
    /**
2112
     * __clone implementation. Cloning should not be allowed in a Singleton!
2113
     *
2114
     * @throws    Calculation\Exception
2115
     */
2116
    final public function __clone()
2117
    {
2118
        throw new Calculation\Exception('Cloning the calculation engine is not allowed!');
2119
    }
2120
2121
    /**
2122
     * Return the locale-specific translation of TRUE.
2123
     *
2124
     * @return     string        locale-specific translation of TRUE
2125
     */
2126 36
    public static function getTRUE()
2127
    {
2128 36
        return self::$localeBoolean['TRUE'];
2129
    }
2130
2131
    /**
2132
     * Return the locale-specific translation of FALSE.
2133
     *
2134
     * @return     string        locale-specific translation of FALSE
2135
     */
2136 29
    public static function getFALSE()
2137
    {
2138 29
        return self::$localeBoolean['FALSE'];
2139
    }
2140
2141
    /**
2142
     * Set the Array Return Type (Array or Value of first element in the array).
2143
     *
2144
     * @param     string    $returnType            Array return type
2145
     *
2146
     * @return     bool                    Success or failure
2147
     */
2148 15
    public static function setArrayReturnType($returnType)
2149
    {
2150 15
        if (($returnType == self::RETURN_ARRAY_AS_VALUE) ||
2151 11
            ($returnType == self::RETURN_ARRAY_AS_ERROR) ||
2152 15
            ($returnType == self::RETURN_ARRAY_AS_ARRAY)) {
2153 15
            self::$returnArrayAsType = $returnType;
2154
2155 15
            return true;
2156
        }
2157
2158
        return false;
2159
    }
2160
2161
    /**
2162
     * Return the Array Return Type (Array or Value of first element in the array).
2163
     *
2164
     * @return     string        $returnType            Array return type
2165
     */
2166 4
    public static function getArrayReturnType()
2167
    {
2168 4
        return self::$returnArrayAsType;
2169
    }
2170
2171
    /**
2172
     * Is calculation caching enabled?
2173
     *
2174
     * @return bool
2175
     */
2176
    public function getCalculationCacheEnabled()
2177
    {
2178
        return $this->calculationCacheEnabled;
2179
    }
2180
2181
    /**
2182
     * Enable/disable calculation cache.
2183
     *
2184
     * @param bool $pValue
2185
     */
2186
    public function setCalculationCacheEnabled($pValue = true)
2187
    {
2188
        $this->calculationCacheEnabled = $pValue;
2189
        $this->clearCalculationCache();
2190
    }
2191
2192
    /**
2193
     * Enable calculation cache.
2194
     */
2195
    public function enableCalculationCache()
2196
    {
2197
        $this->setCalculationCacheEnabled(true);
2198
    }
2199
2200
    /**
2201
     * Disable calculation cache.
2202
     */
2203
    public function disableCalculationCache()
2204
    {
2205
        $this->setCalculationCacheEnabled(false);
2206
    }
2207
2208
    /**
2209
     * Clear calculation cache.
2210
     */
2211
    public function clearCalculationCache()
2212
    {
2213
        $this->calculationCache = [];
2214
    }
2215
2216
    /**
2217
     * Clear calculation cache for a specified worksheet.
2218
     *
2219
     * @param string $worksheetName
2220
     */
2221 1
    public function clearCalculationCacheForWorksheet($worksheetName)
2222
    {
2223 1
        if (isset($this->calculationCache[$worksheetName])) {
2224
            unset($this->calculationCache[$worksheetName]);
2225
        }
2226 1
    }
2227
2228
    /**
2229
     * Rename calculation cache for a specified worksheet.
2230
     *
2231
     * @param string $fromWorksheetName
2232
     * @param string $toWorksheetName
2233
     */
2234 62
    public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName)
2235
    {
2236 62
        if (isset($this->calculationCache[$fromWorksheetName])) {
2237
            $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName];
2238
            unset($this->calculationCache[$fromWorksheetName]);
2239
        }
2240 62
    }
2241
2242
    /**
2243
     * Get the currently defined locale code.
2244
     *
2245
     * @return string
2246
     */
2247
    public function getLocale()
2248
    {
2249
        return self::$localeLanguage;
2250
    }
2251
2252
    /**
2253
     * Set the locale code.
2254
     *
2255
     * @param string $locale  The locale to use for formula translation
2256
     *
2257
     * @return bool
2258
     */
2259
    public function setLocale($locale = 'en_us')
2260
    {
2261
        //    Identify our locale and language
2262
        $language = $locale = strtolower($locale);
2263
        if (strpos($locale, '_') !== false) {
2264
            list($language) = explode('_', $locale);
2265
        }
2266
2267
        if (count(self::$validLocaleLanguages) == 1) {
2268
            self::loadLocales();
2269
        }
2270
        //    Test whether we have any language data for this language (any locale)
2271
        if (in_array($language, self::$validLocaleLanguages)) {
2272
            //    initialise language/locale settings
2273
            self::$localeFunctions = [];
2274
            self::$localeArgumentSeparator = ',';
2275
            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...
2276
            //    Default is English, if user isn't requesting english, then read the necessary data from the locale files
2277
            if ($locale != 'en_us') {
2278
                //    Search for a file with a list of function names for locale
2279
                $functionNamesFile = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'functions';
2280
                if (!file_exists($functionNamesFile)) {
2281
                    //    If there isn't a locale specific function file, look for a language specific function file
2282
                    $functionNamesFile = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . 'functions';
2283
                    if (!file_exists($functionNamesFile)) {
2284
                        return false;
2285
                    }
2286
                }
2287
                //    Retrieve the list of locale or language specific function names
2288
                $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2289
                foreach ($localeFunctions as $localeFunction) {
2290
                    list($localeFunction) = explode('##', $localeFunction); //    Strip out comments
2291
                    if (strpos($localeFunction, '=') !== false) {
2292
                        list($fName, $lfName) = explode('=', $localeFunction);
2293
                        $fName = trim($fName);
2294
                        $lfName = trim($lfName);
2295
                        if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
2296
                            self::$localeFunctions[$fName] = $lfName;
2297
                        }
2298
                    }
2299
                }
2300
                //    Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions
2301
                if (isset(self::$localeFunctions['TRUE'])) {
2302
                    self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE'];
2303
                }
2304
                if (isset(self::$localeFunctions['FALSE'])) {
2305
                    self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE'];
2306
                }
2307
2308
                $configFile = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'config';
2309
                if (!file_exists($configFile)) {
2310
                    $configFile = PHPSPREADSHEET_ROOT . 'PhpSpreadsheet' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . 'config';
2311
                }
2312
                if (file_exists($configFile)) {
2313
                    $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2314
                    foreach ($localeSettings as $localeSetting) {
2315
                        list($localeSetting) = explode('##', $localeSetting); //    Strip out comments
2316
                        if (strpos($localeSetting, '=') !== false) {
2317
                            list($settingName, $settingValue) = explode('=', $localeSetting);
2318
                            $settingName = strtoupper(trim($settingName));
2319
                            switch ($settingName) {
2320
                                case 'ARGUMENTSEPARATOR':
2321
                                    self::$localeArgumentSeparator = trim($settingValue);
2322
                                    break;
2323
                            }
2324
                        }
2325
                    }
2326
                }
2327
            }
2328
2329
            self::$functionReplaceFromExcel = self::$functionReplaceToExcel =
2330
            self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null;
2331
            self::$localeLanguage = $locale;
2332
2333
            return true;
2334
        }
2335
2336
        return false;
2337
    }
2338
2339 1
    public static function translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
2340
    {
2341 1
        $strlen = mb_strlen($formula);
2342 1
        for ($i = 0; $i < $strlen; ++$i) {
2343 1
            $chr = mb_substr($formula, $i, 1);
2344
            switch ($chr) {
2345 1
                case '{':
2346
                    $inBraces = true;
2347
                    break;
2348 1
                case '}':
2349
                    $inBraces = false;
2350
                    break;
2351 1
                case $fromSeparator:
2352
                    if (!$inBraces) {
2353
                        $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
2354
                    }
2355
            }
2356
        }
2357
2358 1
        return $formula;
2359
    }
2360
2361
    /**
2362
     * @param string $fromSeparator
2363
     * @param string $toSeparator
2364
     * @param mixed $from
2365
     * @param mixed $to
2366
     * @param mixed $formula
2367
     */
2368
    private static function translateFormula($from, $to, $formula, $fromSeparator, $toSeparator)
2369
    {
2370
        //    Convert any Excel function names to the required language
2371
        if (self::$localeLanguage !== 'en_us') {
2372
            $inBraces = false;
2373
            //    If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
2374
            if (strpos($formula, '"') !== false) {
2375
                //    So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
2376
                //        the formula
2377
                $temp = explode('"', $formula);
2378
                $i = false;
2379
                foreach ($temp as &$value) {
2380
                    //    Only count/replace in alternating array entries
2381 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...
2382
                        $value = preg_replace($from, $to, $value);
2383
                        $value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces);
2384
                    }
2385
                }
2386
                unset($value);
2387
                //    Then rebuild the formula string
2388
                $formula = implode('"', $temp);
2389 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...
2390
                //    If there's no quoted strings, then we do a simple count/replace
2391
                $formula = preg_replace($from, $to, $formula);
2392
                $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces);
2393
            }
2394
        }
2395
2396
        return $formula;
2397
    }
2398
2399
    private static $functionReplaceFromExcel = null;
2400
    private static $functionReplaceToLocale = null;
2401
2402 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...
2403
    {
2404
        if (self::$functionReplaceFromExcel === null) {
2405
            self::$functionReplaceFromExcel = [];
2406
            foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
2407
                self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName) . '([\s]*\()/Ui';
2408
            }
2409
            foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
2410
                self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean) . '([^\w\.])/Ui';
2411
            }
2412
        }
2413
2414
        if (self::$functionReplaceToLocale === null) {
2415
            self::$functionReplaceToLocale = [];
2416
            foreach (array_values(self::$localeFunctions) as $localeFunctionName) {
2417
                self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2';
2418
            }
2419
            foreach (array_values(self::$localeBoolean) as $localeBoolean) {
2420
                self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2';
2421
            }
2422
        }
2423
2424
        return self::translateFormula(self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula, ',', self::$localeArgumentSeparator);
2425
    }
2426
2427
    private static $functionReplaceFromLocale = null;
2428
    private static $functionReplaceToExcel = null;
2429
2430 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...
2431
    {
2432
        if (self::$functionReplaceFromLocale === null) {
2433
            self::$functionReplaceFromLocale = [];
2434
            foreach (array_values(self::$localeFunctions) as $localeFunctionName) {
2435
                self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName) . '([\s]*\()/Ui';
2436
            }
2437
            foreach (array_values(self::$localeBoolean) as $excelBoolean) {
2438
                self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean) . '([^\w\.])/Ui';
2439
            }
2440
        }
2441
2442
        if (self::$functionReplaceToExcel === null) {
2443
            self::$functionReplaceToExcel = [];
2444
            foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
2445
                self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2';
2446
            }
2447
            foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
2448
                self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2';
2449
            }
2450
        }
2451
2452
        return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ',');
2453
    }
2454
2455 21
    public static function localeFunc($function)
2456
    {
2457 21
        if (self::$localeLanguage !== 'en_us') {
2458
            $functionName = trim($function, '(');
2459
            if (isset(self::$localeFunctions[$functionName])) {
2460
                $brace = ($functionName != $function);
2461
                $function = self::$localeFunctions[$functionName];
2462
                if ($brace) {
2463
                    $function .= '(';
2464
                }
2465
            }
2466
        }
2467
2468 21
        return $function;
2469
    }
2470
2471
    /**
2472
     * Wrap string values in quotes.
2473
     *
2474
     * @param mixed $value
2475
     *
2476
     * @return mixed
2477
     */
2478 55
    public static function wrapResult($value)
2479
    {
2480 55
        if (is_string($value)) {
2481
            //    Error values cannot be "wrapped"
2482 45
            if (preg_match('/^' . self::CALCULATION_REGEXP_ERROR . '$/i', $value, $match)) {
2483
                //    Return Excel errors "as is"
2484 1
                return $value;
2485
            }
2486
            //    Return strings wrapped in quotes
2487 45
            return '"' . $value . '"';
2488
        //    Convert numeric errors to NaN error
2489 20
        } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
2490
            return Calculation\Functions::NAN();
2491
        }
2492
2493 20
        return $value;
2494
    }
2495
2496
    /**
2497
     * Remove quotes used as a wrapper to identify string values.
2498
     *
2499
     * @param mixed $value
2500
     *
2501
     * @return mixed
2502
     */
2503 67
    public static function unwrapResult($value)
2504
    {
2505 67
        if (is_string($value)) {
2506 49
            if ((isset($value[0])) && ($value[0] == '"') && (substr($value, -1) == '"')) {
2507 49
                return substr($value, 1, -1);
2508
            }
2509
        //    Convert numeric errors to NAN error
2510 33
        } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
2511
            return Calculation\Functions::NAN();
2512
        }
2513
2514 33
        return $value;
2515
    }
2516
2517
    /**
2518
     * Calculate cell value (using formula from a cell ID)
2519
     * Retained for backward compatibility.
2520
     *
2521
     * @param    Cell    $pCell    Cell to calculate
2522
     *
2523
     * @throws    Calculation\Exception
2524
     *
2525
     * @return    mixed
2526
     */
2527
    public function calculate(Cell $pCell = null)
2528
    {
2529
        try {
2530
            return $this->calculateCellValue($pCell);
2531
        } catch (Exception $e) {
2532
            throw new Calculation\Exception($e->getMessage());
2533
        }
2534
    }
2535
2536
    /**
2537
     * Calculate the value of a cell formula.
2538
     *
2539
     * @param    Cell    $pCell        Cell to calculate
2540
     * @param    bool            $resetLog    Flag indicating whether the debug log should be reset or not
2541
     *
2542
     * @throws    Calculation\Exception
2543
     *
2544
     * @return    mixed
2545
     */
2546 22
    public function calculateCellValue(Cell $pCell = null, $resetLog = true)
2547
    {
2548 22
        if ($pCell === null) {
2549
            return null;
2550
        }
2551
2552 22
        $returnArrayAsType = self::$returnArrayAsType;
2553 22
        if ($resetLog) {
2554
            //    Initialise the logging settings if requested
2555 22
            $this->formulaError = null;
2556 22
            $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...
2557 22
            $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...
2558 22
            $this->cyclicFormulaCounter = 1;
2559
2560 22
            self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY;
2561
        }
2562
2563
        //    Execute the calculation for the cell formula
2564 22
        $this->cellStack[] = [
2565 22
            'sheet' => $pCell->getWorksheet()->getTitle(),
2566 22
            'cell' => $pCell->getCoordinate(),
2567
        ];
2568
        try {
2569 22
            $result = self::unwrapResult($this->_calculateFormulaValue($pCell->getValue(), $pCell->getCoordinate(), $pCell));
2570 22
            $cellAddress = array_pop($this->cellStack);
2571 22
            $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
2572
        } catch (Exception $e) {
2573
            $cellAddress = array_pop($this->cellStack);
2574
            $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
2575
            throw new Calculation\Exception($e->getMessage());
2576
        }
2577
2578 22
        if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
2579 1
            self::$returnArrayAsType = $returnArrayAsType;
2580 1
            $testResult = Calculation\Functions::flattenArray($result);
2581 1
            if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
2582
                return Calculation\Functions::VALUE();
2583
            }
2584
            //    If there's only a single cell in the array, then we allow it
2585 1
            if (count($testResult) != 1) {
2586
                //    If keys are numeric, then it's a matrix result rather than a cell range result, so we permit it
2587
                $r = array_keys($result);
2588
                $r = array_shift($r);
2589
                if (!is_numeric($r)) {
2590
                    return Calculation\Functions::VALUE();
2591
                }
2592
                if (is_array($result[$r])) {
2593
                    $c = array_keys($result[$r]);
2594
                    $c = array_shift($c);
2595
                    if (!is_numeric($c)) {
2596
                        return Calculation\Functions::VALUE();
2597
                    }
2598
                }
2599
            }
2600 1
            $result = array_shift($testResult);
2601
        }
2602 22
        self::$returnArrayAsType = $returnArrayAsType;
2603
2604 22
        if ($result === null) {
2605
            return 0;
2606 22
        } elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) {
2607
            return Calculation\Functions::NAN();
2608
        }
2609
2610 22
        return $result;
2611
    }
2612
2613
    /**
2614
     * Validate and parse a formula string.
2615
     *
2616
     * @param    string        $formula        Formula to parse
2617
     *
2618
     * @throws    Calculation\Exception
2619
     *
2620
     * @return    array
2621
     */
2622
    public function parseFormula($formula)
2623
    {
2624
        //    Basic validation that this is indeed a formula
2625
        //    We return an empty array if not
2626
        $formula = trim($formula);
2627
        if ((!isset($formula[0])) || ($formula[0] != '=')) {
2628
            return [];
2629
        }
2630
        $formula = ltrim(substr($formula, 1));
2631
        if (!isset($formula[0])) {
2632
            return [];
2633
        }
2634
2635
        //    Parse the formula and return the token stack
2636
        return $this->_parseFormula($formula);
2637
    }
2638
2639
    /**
2640
     * Calculate the value of a formula.
2641
     *
2642
     * @param    string            $formula    Formula to parse
2643
     * @param    string            $cellID        Address of the cell to calculate
2644
     * @param    Cell    $pCell        Cell to calculate
2645
     *
2646
     * @throws    Calculation\Exception
2647
     *
2648
     * @return    mixed
2649
     */
2650
    public function calculateFormula($formula, $cellID = null, Cell $pCell = null)
2651
    {
2652
        //    Initialise the logging settings
2653
        $this->formulaError = null;
2654
        $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...
2655
        $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...
2656
2657
        if ($this->spreadsheet !== null && $cellID === null && $pCell === null) {
2658
            $cellID = 'A1';
2659
            $pCell = $this->spreadsheet->getActiveSheet()->getCell($cellID);
2660
        } else {
2661
            //    Disable calculation cacheing because it only applies to cell calculations, not straight formulae
2662
            //    But don't actually flush any cache
2663
            $resetCache = $this->getCalculationCacheEnabled();
2664
            $this->calculationCacheEnabled = false;
2665
        }
2666
2667
        //    Execute the calculation
2668
        try {
2669
            $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $pCell));
2670
        } catch (Exception $e) {
2671
            throw new Calculation\Exception($e->getMessage());
2672
        }
2673
2674
        if ($this->spreadsheet === null) {
2675
            //    Reset calculation cacheing to its previous state
2676
            $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...
2677
        }
2678
2679
        return $result;
2680
    }
2681
2682 22
    public function getValueFromCache($cellReference, &$cellValue)
2683
    {
2684
        // Is calculation cacheing enabled?
2685
        // Is the value present in calculation cache?
2686 22
        $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...
2687 22
        if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) {
2688 21
            $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...
2689
            // Return the cached result
2690 21
            $cellValue = $this->calculationCache[$cellReference];
2691
2692 21
            return true;
2693
        }
2694
2695 22
        return false;
2696
    }
2697
2698
    /**
2699
     * @param string $cellReference
2700
     * @param mixed $cellValue
2701
     */
2702 22
    public function saveValueToCache($cellReference, $cellValue)
2703
    {
2704 22
        if ($this->calculationCacheEnabled) {
2705 22
            $this->calculationCache[$cellReference] = $cellValue;
2706
        }
2707 22
    }
2708
2709
    /**
2710
     * Parse a cell formula and calculate its value.
2711
     *
2712
     * @param    string            $formula    The formula to parse and calculate
2713
     * @param    string            $cellID        The ID (e.g. A3) of the cell that we are calculating
2714
     * @param    Cell    $pCell        Cell to calculate
2715
     *
2716
     * @throws   Calculation\Exception
2717
     *
2718
     * @return   mixed
2719
     */
2720 91
    public function _calculateFormulaValue($formula, $cellID = null, Cell $pCell = null)
2721
    {
2722 91
        $cellValue = null;
2723
2724
        //    Basic validation that this is indeed a formula
2725
        //    We simply return the cell value if not
2726 91
        $formula = trim($formula);
2727 91
        if ($formula[0] != '=') {
2728
            return self::wrapResult($formula);
2729
        }
2730 91
        $formula = ltrim(substr($formula, 1));
2731 91
        if (!isset($formula[0])) {
2732
            return self::wrapResult($formula);
2733
        }
2734
2735 91
        $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
2736 91
        $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk";
2737 91
        $wsCellReference = $wsTitle . '!' . $cellID;
2738
2739 91
        if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) {
2740 21
            return $cellValue;
2741
        }
2742
2743 91
        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...
2744
            if ($this->cyclicFormulaCount <= 0) {
2745
                $this->cyclicFormulaCell = '';
2746
2747
                return $this->raiseFormulaError('Cyclic Reference in Formula');
2748
            } elseif ($this->cyclicFormulaCell === $wsCellReference) {
2749
                ++$this->cyclicFormulaCounter;
2750
                if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
2751
                    $this->cyclicFormulaCell = '';
2752
2753
                    return $cellValue;
2754
                }
2755
            } elseif ($this->cyclicFormulaCell == '') {
2756
                if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
2757
                    return $cellValue;
2758
                }
2759
                $this->cyclicFormulaCell = $wsCellReference;
2760
            }
2761
        }
2762
2763
        //    Parse the formula onto the token stack and calculate the value
2764 91
        $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...
2765 91
        $cellValue = $this->processTokenStack($this->_parseFormula($formula, $pCell), $cellID, $pCell);
2766 91
        $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...
2767
2768
        // Save to calculation cache
2769 91
        if ($cellID !== null) {
2770 22
            $this->saveValueToCache($wsCellReference, $cellValue);
2771
        }
2772
2773
        //    Return the calculated value
2774 91
        return $cellValue;
2775
    }
2776
2777
    /**
2778
     * Ensure that paired matrix operands are both matrices and of the same size.
2779
     *
2780
     * @param    mixed        &$operand1    First matrix operand
2781
     * @param    mixed        &$operand2    Second matrix operand
2782
     * @param    int        $resize        Flag indicating whether the matrices should be resized to match
2783
     *                                        and (if so), whether the smaller dimension should grow or the
2784
     *                                        larger should shrink.
2785
     *                                            0 = no resize
2786
     *                                            1 = shrink to fit
2787
     *                                            2 = extend to fit
2788
     */
2789 5
    private static function checkMatrixOperands(&$operand1, &$operand2, $resize = 1)
2790
    {
2791
        //    Examine each of the two operands, and turn them into an array if they aren't one already
2792
        //    Note that this function should only be called if one or both of the operand is already an array
2793 5
        if (!is_array($operand1)) {
2794
            list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand2);
2795
            $operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1));
2796
            $resize = 0;
2797 5
        } elseif (!is_array($operand2)) {
2798 2
            list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand1);
2799 2
            $operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2));
2800 2
            $resize = 0;
2801
        }
2802
2803 5
        list($matrix1Rows, $matrix1Columns) = self::getMatrixDimensions($operand1);
2804 5
        list($matrix2Rows, $matrix2Columns) = self::getMatrixDimensions($operand2);
2805 5
        if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
2806 5
            $resize = 1;
2807
        }
2808
2809 5
        if ($resize == 2) {
2810
            //    Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger
2811
            self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
2812 5
        } elseif ($resize == 1) {
2813
            //    Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller
2814 5
            self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
2815
        }
2816
2817 5
        return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns];
2818
    }
2819
2820
    /**
2821
     * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0.
2822
     *
2823
     * @param    mixed        &$matrix        matrix operand
2824
     *
2825
     * @return    int[]        An array comprising the number of rows, and number of columns
2826
     */
2827 5
    private static function getMatrixDimensions(&$matrix)
2828
    {
2829 5
        $matrixRows = count($matrix);
2830 5
        $matrixColumns = 0;
2831 5
        foreach ($matrix as $rowKey => $rowValue) {
2832 5
            $matrixColumns = max(count($rowValue), $matrixColumns);
2833 5
            if (!is_array($rowValue)) {
2834
                $matrix[$rowKey] = [$rowValue];
2835
            } else {
2836 5
                $matrix[$rowKey] = array_values($rowValue);
2837
            }
2838
        }
2839 5
        $matrix = array_values($matrix);
2840
2841 5
        return [$matrixRows, $matrixColumns];
2842
    }
2843
2844
    /**
2845
     * Ensure that paired matrix operands are both matrices of the same size.
2846
     *
2847
     * @param    mixed        &$matrix1        First matrix operand
2848
     * @param    mixed        &$matrix2        Second matrix operand
2849
     * @param    int        $matrix1Rows    Row size of first matrix operand
2850
     * @param    int        $matrix1Columns    Column size of first matrix operand
2851
     * @param    int        $matrix2Rows    Row size of second matrix operand
2852
     * @param    int        $matrix2Columns    Column size of second matrix operand
2853
     */
2854 5
    private static function resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
2855
    {
2856 5 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...
2857
            if ($matrix2Rows < $matrix1Rows) {
2858
                for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
2859
                    unset($matrix1[$i]);
2860
                }
2861
            }
2862
            if ($matrix2Columns < $matrix1Columns) {
2863
                for ($i = 0; $i < $matrix1Rows; ++$i) {
2864
                    for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
2865
                        unset($matrix1[$i][$j]);
2866
                    }
2867
                }
2868
            }
2869
        }
2870
2871 5 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...
2872
            if ($matrix1Rows < $matrix2Rows) {
2873
                for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
2874
                    unset($matrix2[$i]);
2875
                }
2876
            }
2877
            if ($matrix1Columns < $matrix2Columns) {
2878
                for ($i = 0; $i < $matrix2Rows; ++$i) {
2879
                    for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
2880
                        unset($matrix2[$i][$j]);
2881
                    }
2882
                }
2883
            }
2884
        }
2885 5
    }
2886
2887
    /**
2888
     * Ensure that paired matrix operands are both matrices of the same size.
2889
     *
2890
     * @param    mixed        &$matrix1    First matrix operand
2891
     * @param    mixed        &$matrix2    Second matrix operand
2892
     * @param    int        $matrix1Rows    Row size of first matrix operand
2893
     * @param    int        $matrix1Columns    Column size of first matrix operand
2894
     * @param    int        $matrix2Rows    Row size of second matrix operand
2895
     * @param    int        $matrix2Columns    Column size of second matrix operand
2896
     */
2897
    private static function resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
2898
    {
2899 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...
2900
            if ($matrix2Columns < $matrix1Columns) {
2901
                for ($i = 0; $i < $matrix2Rows; ++$i) {
2902
                    $x = $matrix2[$i][$matrix2Columns - 1];
2903
                    for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
2904
                        $matrix2[$i][$j] = $x;
2905
                    }
2906
                }
2907
            }
2908
            if ($matrix2Rows < $matrix1Rows) {
2909
                $x = $matrix2[$matrix2Rows - 1];
2910
                for ($i = 0; $i < $matrix1Rows; ++$i) {
2911
                    $matrix2[$i] = $x;
2912
                }
2913
            }
2914
        }
2915
2916 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...
2917
            if ($matrix1Columns < $matrix2Columns) {
2918
                for ($i = 0; $i < $matrix1Rows; ++$i) {
2919
                    $x = $matrix1[$i][$matrix1Columns - 1];
2920
                    for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
2921
                        $matrix1[$i][$j] = $x;
2922
                    }
2923
                }
2924
            }
2925
            if ($matrix1Rows < $matrix2Rows) {
2926
                $x = $matrix1[$matrix1Rows - 1];
2927
                for ($i = 0; $i < $matrix2Rows; ++$i) {
2928
                    $matrix1[$i] = $x;
2929
                }
2930
            }
2931
        }
2932
    }
2933
2934
    /**
2935
     * Format details of an operand for display in the log (based on operand type).
2936
     *
2937
     * @param    mixed        $value    First matrix operand
2938
     *
2939
     * @return    mixed
2940
     */
2941 90
    private function showValue($value)
2942
    {
2943 90
        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...
2944
            $testArray = Calculation\Functions::flattenArray($value);
2945
            if (count($testArray) == 1) {
2946
                $value = array_pop($testArray);
2947
            }
2948
2949
            if (is_array($value)) {
2950
                $returnMatrix = [];
2951
                $pad = $rpad = ', ';
2952
                foreach ($value as $row) {
2953
                    if (is_array($row)) {
2954
                        $returnMatrix[] = implode($pad, array_map([$this, 'showValue'], $row));
2955
                        $rpad = '; ';
2956
                    } else {
2957
                        $returnMatrix[] = $this->showValue($row);
2958
                    }
2959
                }
2960
2961
                return '{ ' . implode($rpad, $returnMatrix) . ' }';
2962
            } elseif (is_string($value) && (trim($value, '"') == $value)) {
2963
                return '"' . $value . '"';
2964
            } elseif (is_bool($value)) {
2965
                return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
2966
            }
2967
        }
2968
2969 90
        return Calculation\Functions::flattenSingleValue($value);
2970
    }
2971
2972
    /**
2973
     * Format type and details of an operand for display in the log (based on operand type).
2974
     *
2975
     * @param    mixed        $value    First matrix operand
2976
     *
2977
     * @return    string|null
2978
     */
2979 90
    private function showTypeDetails($value)
2980
    {
2981 90
        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...
2982
            $testArray = Calculation\Functions::flattenArray($value);
2983
            if (count($testArray) == 1) {
2984
                $value = array_pop($testArray);
2985
            }
2986
2987
            if ($value === null) {
2988
                return 'a NULL value';
2989
            } elseif (is_float($value)) {
2990
                $typeString = 'a floating point number';
2991
            } elseif (is_int($value)) {
2992
                $typeString = 'an integer number';
2993
            } elseif (is_bool($value)) {
2994
                $typeString = 'a boolean';
2995
            } elseif (is_array($value)) {
2996
                $typeString = 'a matrix';
2997
            } else {
2998
                if ($value == '') {
2999
                    return 'an empty string';
3000
                } elseif ($value[0] == '#') {
3001
                    return 'a ' . $value . ' error';
3002
                }
3003
                $typeString = 'a string';
3004
            }
3005
3006
            return $typeString . ' with a value of ' . $this->showValue($value);
3007
        }
3008 90
    }
3009
3010 91
    private function convertMatrixReferences($formula)
3011
    {
3012 91
        static $matrixReplaceFrom = ['{', ';', '}'];
3013 91
        static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
3014
3015
        //    Convert any Excel matrix references to the MKMATRIX() function
3016 91
        if (strpos($formula, '{') !== false) {
3017
            //    If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
3018
            if (strpos($formula, '"') !== false) {
3019
                //    So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
3020
                //        the formula
3021
                $temp = explode('"', $formula);
3022
                //    Open and Closed counts used for trapping mismatched braces in the formula
3023
                $openCount = $closeCount = 0;
3024
                $i = false;
3025
                foreach ($temp as &$value) {
3026
                    //    Only count/replace in alternating array entries
3027
                    if ($i = !$i) {
3028
                        $openCount += substr_count($value, '{');
3029
                        $closeCount += substr_count($value, '}');
3030
                        $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
3031
                    }
3032
                }
3033
                unset($value);
3034
                //    Then rebuild the formula string
3035
                $formula = implode('"', $temp);
3036
            } else {
3037
                //    If there's no quoted strings, then we do a simple count/replace
3038
                $openCount = substr_count($formula, '{');
3039
                $closeCount = substr_count($formula, '}');
3040
                $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
3041
            }
3042
            //    Trap for mismatched braces and trigger an appropriate error
3043
            if ($openCount < $closeCount) {
3044
                if ($openCount > 0) {
3045
                    return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '}'");
3046
                }
3047
3048
                return $this->raiseFormulaError("Formula Error: Unexpected '}' encountered");
3049
            } elseif ($openCount > $closeCount) {
3050
                if ($closeCount > 0) {
3051
                    return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '{'");
3052
                }
3053
3054
                return $this->raiseFormulaError("Formula Error: Unexpected '{' encountered");
3055
            }
3056
        }
3057
3058 91
        return $formula;
3059
    }
3060
3061
    private static function mkMatrix()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
3062
    {
3063
        return func_get_args();
3064
    }
3065
3066
    //    Binary Operators
3067
    //    These operators always work on two values
3068
    //    Array key is the operator, the value indicates whether this is a left or right associative operator
3069
    private static $operatorAssociativity = [
3070
        '^' => 0, //    Exponentiation
3071
        '*' => 0, '/' => 0, //    Multiplication and Division
3072
        '+' => 0, '-' => 0, //    Addition and Subtraction
3073
        '&' => 0, //    Concatenation
3074
        '|' => 0, ':' => 0, //    Intersect and Range
3075
        '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, //    Comparison
3076
    ];
3077
3078
    //    Comparison (Boolean) Operators
3079
    //    These operators work on two values, but always return a boolean result
3080
    private static $comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true];
3081
3082
    //    Operator Precedence
3083
    //    This list includes all valid operators, whether binary (including boolean) or unary (such as %)
3084
    //    Array key is the operator, the value is its precedence
3085
    private static $operatorPrecedence = [
3086
        ':' => 8, //    Range
3087
        '|' => 7, //    Intersect
3088
        '~' => 6, //    Negation
3089
        '%' => 5, //    Percentage
3090
        '^' => 4, //    Exponentiation
3091
        '*' => 3, '/' => 3, //    Multiplication and Division
3092
        '+' => 2, '-' => 2, //    Addition and Subtraction
3093
        '&' => 1, //    Concatenation
3094
        '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, //    Comparison
3095
    ];
3096
3097
    // Convert infix to postfix notation
3098 91
    private function _parseFormula($formula, Cell $pCell = null)
3099
    {
3100 91
        if (($formula = $this->convertMatrixReferences(trim($formula))) === false) {
3101
            return false;
3102
        }
3103
3104
        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet),
3105
        //        so we store the parent worksheet so that we can re-attach it when necessary
3106 91
        $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
3107
3108 91
        $regexpMatchString = '/^(' . self::CALCULATION_REGEXP_FUNCTION .
3109 91
                                '|' . self::CALCULATION_REGEXP_CELLREF .
3110 91
                                '|' . self::CALCULATION_REGEXP_NUMBER .
3111 91
                                '|' . self::CALCULATION_REGEXP_STRING .
3112 91
                                '|' . self::CALCULATION_REGEXP_OPENBRACE .
3113 91
                                '|' . self::CALCULATION_REGEXP_NAMEDRANGE .
3114 91
                                '|' . self::CALCULATION_REGEXP_ERROR .
3115 91
                                ')/si';
3116
3117
        //    Start with initialisation
3118 91
        $index = 0;
3119 91
        $stack = new Calculation\Token\Stack();
3120 91
        $output = [];
3121 91
        $expectingOperator = false; //    We use this test in syntax-checking the expression to determine when a
3122
                                                    //        - is a negation or + is a positive operator rather than an operation
3123 91
        $expectingOperand = false; //    We use this test in syntax-checking the expression to determine whether an operand
3124
                                                    //        should be null in a function call
3125
        //    The guts of the lexical parser
3126
        //    Loop through the formula extracting each operator and operand in turn
3127 91
        while (true) {
3128 91
            $opCharacter = $formula[$index];    //    Get the first character of the value at the current index position
3129 91
            if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) {
3130 31
                $opCharacter .= $formula[++$index];
3131
            }
3132
3133
            //    Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand
3134 91
            $isOperandOrFunction = preg_match($regexpMatchString, substr($formula, $index), $match);
3135
3136 91
            if ($opCharacter == '-' && !$expectingOperator) {                //    Is it a negation instead of a minus?
3137 1
                $stack->push('Unary Operator', '~'); //    Put a negation on the stack
3138 1
                ++$index; //        and drop the negation symbol
3139 91
            } elseif ($opCharacter == '%' && $expectingOperator) {
3140
                $stack->push('Unary Operator', '%'); //    Put a percentage on the stack
3141
                ++$index;
3142 91
            } elseif ($opCharacter == '+' && !$expectingOperator) {            //    Positive (unary plus rather than binary operator plus) can be discarded?
3143
                ++$index; //    Drop the redundant plus symbol
3144 91
            } elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) {    //    We have to explicitly deny a tilde or pipe, because they are legal
3145
                return $this->raiseFormulaError("Formula Error: Illegal character '~'"); //        on the stack but not in the input expression
3146 91
            } 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...
3147 89 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...
3148 89
                    ($o2 = $stack->last()) &&
3149 89
                    isset(self::$operators[$o2['value']]) &&
3150 89
                    @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) {
3151 1
                    $output[] = $stack->pop(); //    Swap operands and higher precedence operators from the stack to the output
3152
                }
3153 89
                $stack->push('Binary Operator', $opCharacter); //    Finally put our current operator onto the stack
3154 89
                ++$index;
3155 89
                $expectingOperator = false;
3156 91
            } elseif ($opCharacter == ')' && $expectingOperator) {            //    Are we expecting to close a parenthesis?
3157 21
                $expectingOperand = false;
3158 21 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...
3159 20
                    if ($o2 === null) {
3160
                        return $this->raiseFormulaError('Formula Error: Unexpected closing brace ")"');
3161
                    }
3162 20
                    $output[] = $o2;
3163
                }
3164 21
                $d = $stack->last(2);
3165 21
                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {    //    Did this parenthesis just close a function?
3166 21
                    $functionName = $matches[1]; //    Get the function name
3167 21
                    $d = $stack->pop();
3168 21
                    $argumentCount = $d['value']; //    See how many arguments there were (argument count is the next value stored on the stack)
3169 21
                    $output[] = $d; //    Dump the argument count on the output
3170 21
                    $output[] = $stack->pop(); //    Pop the function and push onto the output
3171 21
                    if (isset(self::$controlFunctions[$functionName])) {
3172
                        $expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount'];
3173
                        $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...
3174 21
                    } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
3175 21
                        $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount'];
3176 21
                        $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...
3177
                    } else {    // did we somehow push a non-function on the stack? this should never happen
3178
                        return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack');
3179
                    }
3180
                    //    Check the argument count
3181 21
                    $argumentCountError = false;
3182 21
                    if (is_numeric($expectedArgumentCount)) {
3183 5
                        if ($expectedArgumentCount < 0) {
3184
                            if ($argumentCount > abs($expectedArgumentCount)) {
3185
                                $argumentCountError = true;
3186
                                $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount);
3187
                            }
3188
                        } else {
3189 5
                            if ($argumentCount != $expectedArgumentCount) {
3190
                                $argumentCountError = true;
3191 5
                                $expectedArgumentCountString = $expectedArgumentCount;
3192
                            }
3193
                        }
3194 20
                    } elseif ($expectedArgumentCount != '*') {
3195 20
                        $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...
3196 20
                        switch ($argMatch[2]) {
3197 20
                            case '+':
3198 19
                                if ($argumentCount < $argMatch[1]) {
3199
                                    $argumentCountError = true;
3200
                                    $expectedArgumentCountString = $argMatch[1] . ' or more ';
3201
                                }
3202 19
                                break;
3203 11 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...
3204 9
                                if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) {
3205
                                    $argumentCountError = true;
3206
                                    $expectedArgumentCountString = 'between ' . $argMatch[1] . ' and ' . $argMatch[3];
3207
                                }
3208 9
                                break;
3209 2 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...
3210 2
                                if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) {
3211
                                    $argumentCountError = true;
3212
                                    $expectedArgumentCountString = 'either ' . $argMatch[1] . ' or ' . $argMatch[3];
3213
                                }
3214 2
                                break;
3215
                        }
3216
                    }
3217 21
                    if ($argumentCountError) {
3218
                        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...
3219
                    }
3220
                }
3221 21
                ++$index;
3222 91
            } elseif ($opCharacter == ',') {            //    Is this the separator for function arguments?
3223 12 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...
3224 10
                    if ($o2 === null) {
3225
                        return $this->raiseFormulaError('Formula Error: Unexpected ,');
3226
                    }
3227 10
                    $output[] = $o2; // pop the argument expression stuff and push onto the output
3228
                }
3229
                //    If we've a comma when we're expecting an operand, then what we actually have is a null operand;
3230
                //        so push a null onto the stack
3231 12
                if (($expectingOperand) || (!$expectingOperator)) {
3232
                    $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
3233
                }
3234
                // make sure there was a function
3235 12
                $d = $stack->last(2);
3236 12
                if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {
3237
                    return $this->raiseFormulaError('Formula Error: Unexpected ,');
3238
                }
3239 12
                $d = $stack->pop();
3240 12
                $stack->push($d['type'], ++$d['value'], $d['reference']); // increment the argument count
3241 12
                $stack->push('Brace', '('); // put the ( back on, we'll need to pop back to it again
3242 12
                $expectingOperator = false;
3243 12
                $expectingOperand = true;
3244 12
                ++$index;
3245 91
            } elseif ($opCharacter == '(' && !$expectingOperator) {
3246 2
                $stack->push('Brace', '(');
3247 2
                ++$index;
3248 91
            } elseif ($isOperandOrFunction && !$expectingOperator) {    // do we now have a function/variable/number?
3249 91
                $expectingOperator = true;
3250 91
                $expectingOperand = false;
3251 91
                $val = $match[1];
3252 91
                $length = strlen($val);
3253
3254 91
                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $val, $matches)) {
3255 21
                    $val = preg_replace('/\s/u', '', $val);
3256 21
                    if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) {    // it's a function
3257 21
                        $stack->push('Function', strtoupper($val));
3258 21
                        $ax = preg_match('/^\s*(\s*\))/ui', substr($formula, $index + $length), $amatch);
3259 21
                        if ($ax) {
3260 4
                            $stack->push('Operand Count for Function ' . strtoupper($val) . ')', 0);
3261 4
                            $expectingOperator = true;
3262
                        } else {
3263 21
                            $stack->push('Operand Count for Function ' . strtoupper($val) . ')', 1);
3264 21
                            $expectingOperator = false;
3265
                        }
3266 21
                        $stack->push('Brace', '(');
3267
                    } else {    // it's a var w/ implicit multiplication
3268 21
                        $output[] = ['type' => 'Value', 'value' => $matches[1], 'reference' => null];
3269
                    }
3270 91
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $val, $matches)) {
3271
                    //    Watch for this case-change when modifying to allow cell references in different worksheets...
3272
                    //    Should only be applied to the actual cell column, not the worksheet name
3273
3274
                    //    If the last entry on the stack was a : operator, then we have a cell range reference
3275 33
                    $testPrevOp = $stack->last(1);
3276 33
                    if ($testPrevOp['value'] == ':') {
3277
                        //    If we have a worksheet reference, then we're playing with a 3D reference
3278 32
                        if ($matches[2] == '') {
3279
                            //    Otherwise, we 'inherit' the worksheet reference from the start cell reference
3280
                            //    The start of the cell range reference should be the last entry in $output
3281 32
                            $startCellRef = $output[count($output) - 1]['value'];
3282 32
                            preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $startCellRef, $startMatches);
3283 32
                            if ($startMatches[2] > '') {
3284 32
                                $val = $startMatches[2] . '!' . $val;
3285
                            }
3286
                        } else {
3287
                            return $this->raiseFormulaError('3D Range references are not yet supported');
3288
                        }
3289
                    }
3290
3291 33
                    $output[] = ['type' => 'Cell Reference', 'value' => $val, 'reference' => $val];
3292
                } else {    // it's a variable, constant, string, number or boolean
3293
                    //    If the last entry on the stack was a : operator, then we may have a row or column range reference
3294 74
                    $testPrevOp = $stack->last(1);
3295 74
                    if ($testPrevOp['value'] == ':') {
3296
                        $startRowColRef = $output[count($output) - 1]['value'];
3297
                        $rangeWS1 = '';
3298
                        if (strpos('!', $startRowColRef) !== false) {
3299
                            list($rangeWS1, $startRowColRef) = explode('!', $startRowColRef);
3300
                        }
3301
                        if ($rangeWS1 != '') {
3302
                            $rangeWS1 .= '!';
3303
                        }
3304
                        $rangeWS2 = $rangeWS1;
3305
                        if (strpos('!', $val) !== false) {
3306
                            list($rangeWS2, $val) = explode('!', $val);
3307
                        }
3308
                        if ($rangeWS2 != '') {
3309
                            $rangeWS2 .= '!';
3310
                        }
3311
                        if ((is_int($startRowColRef)) && (ctype_digit($val)) &&
3312
                            ($startRowColRef <= 1048576) && ($val <= 1048576)) {
3313
                            //    Row range
3314
                            $endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestColumn() : 'XFD'; //    Max 16,384 columns for Excel2007
3315
                            $output[count($output) - 1]['value'] = $rangeWS1 . 'A' . $startRowColRef;
3316
                            $val = $rangeWS2 . $endRowColRef . $val;
3317
                        } elseif ((ctype_alpha($startRowColRef)) && (ctype_alpha($val)) &&
3318
                            (strlen($startRowColRef) <= 3) && (strlen($val) <= 3)) {
3319
                            //    Column range
3320
                            $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...
3321
                            $output[count($output) - 1]['value'] = $rangeWS1 . strtoupper($startRowColRef) . '1';
3322
                            $val = $rangeWS2 . $val . $endRowColRef;
3323
                        }
3324
                    }
3325
3326 74
                    $localeConstant = false;
3327 74
                    if ($opCharacter == '"') {
3328
                        //    UnEscape any quotes within the string
3329 45
                        $val = self::wrapResult(str_replace('""', '"', self::unwrapResult($val)));
3330 42
                    } elseif (is_numeric($val)) {
3331 40
                        if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
3332 16
                            $val = (float) $val;
3333
                        } else {
3334 40
                            $val = (int) $val;
3335
                        }
3336 5
                    } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) {
3337 1
                        $excelConstant = trim(strtoupper($val));
3338 1
                        $val = self::$excelConstants[$excelConstant];
3339 4
                    } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) {
3340
                        $val = self::$excelConstants[$localeConstant];
3341
                    }
3342 74
                    $details = ['type' => 'Value', 'value' => $val, 'reference' => null];
3343 74
                    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...
3344
                        $details['localeValue'] = $localeConstant;
3345
                    }
3346 74
                    $output[] = $details;
3347
                }
3348 91
                $index += $length;
3349
            } elseif ($opCharacter == '$') {    // absolute row or column range
3350
                ++$index;
3351
            } elseif ($opCharacter == ')') {    // miscellaneous error checking
3352
                if ($expectingOperand) {
3353
                    $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
3354
                    $expectingOperand = false;
3355
                    $expectingOperator = true;
3356
                } else {
3357
                    return $this->raiseFormulaError("Formula Error: Unexpected ')'");
3358
                }
3359
            } elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) {
3360
                return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
3361
            } else {    // I don't even want to know what you did to get here
3362
                return $this->raiseFormulaError('Formula Error: An unexpected error occured');
3363
            }
3364
            //    Test for end of formula string
3365 91
            if ($index == strlen($formula)) {
3366
                //    Did we end with an operator?.
3367
                //    Only valid for the % unary operator
3368 91
                if ((isset(self::$operators[$opCharacter])) && ($opCharacter != '%')) {
3369
                    return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
3370
                }
3371 91
                break;
3372
            }
3373
            //    Ignore white space
3374 90
            while (($formula[$index] == "\n") || ($formula[$index] == "\r")) {
3375
                ++$index;
3376
            }
3377 90
            if ($formula[$index] == ' ') {
3378 53
                while ($formula[$index] == ' ') {
3379 53
                    ++$index;
3380
                }
3381
                //    If we're expecting an operator, but only have a space between the previous and next operands (and both are
3382
                //        Cell References) then we have an INTERSECTION operator
3383 53
                if (($expectingOperator) && (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) &&
3384 53
                    ($output[count($output) - 1]['type'] == 'Cell Reference')) {
3385 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...
3386
                        ($o2 = $stack->last()) &&
3387
                        isset(self::$operators[$o2['value']]) &&
3388
                        @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) {
3389
                        $output[] = $stack->pop(); //    Swap operands and higher precedence operators from the stack to the output
3390
                    }
3391
                    $stack->push('Binary Operator', '|'); //    Put an Intersect Operator on the stack
3392
                    $expectingOperator = false;
3393
                }
3394
            }
3395
        }
3396
3397 91 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...
3398 85
            if ((is_array($op) && $op['value'] == '(') || ($op === '(')) {
3399
                return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
3400
            }
3401 85
            $output[] = $op;
3402
        }
3403
3404 91
        return $output;
3405
    }
3406
3407 89
    private static function dataTestReference(&$operandData)
3408
    {
3409 89
        $operand = $operandData['value'];
3410 89
        if (($operandData['reference'] === null) && (is_array($operand))) {
3411 1
            $rKeys = array_keys($operand);
3412 1
            $rowKey = array_shift($rKeys);
3413 1
            $cKeys = array_keys(array_keys($operand[$rowKey]));
3414 1
            $colKey = array_shift($cKeys);
3415 1
            if (ctype_upper($colKey)) {
3416
                $operandData['reference'] = $colKey . $rowKey;
3417
            }
3418
        }
3419
3420 89
        return $operand;
3421
    }
3422
3423
    // evaluate postfix notation
3424
3425
    /**
3426
     * @param string $cellID
3427
     * @param mixed $tokens
3428
     */
3429 91
    private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
3430
    {
3431 91
        if ($tokens == false) {
3432
            return false;
3433
        }
3434
3435
        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
3436
        //        so we store the parent cell collection so that we can re-attach it when necessary
3437 91
        $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null;
3438 91
        $pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
3439 91
        $stack = new Calculation\Token\Stack();
3440
3441
        //    Loop through each token in turn
3442 91
        foreach ($tokens as $tokenData) {
3443 91
            $token = $tokenData['value'];
3444
            // 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
3445 91
            if (isset(self::$binaryOperators[$token])) {
3446
                //    We must have two operands, error if we don't
3447 89
                if (($operand2Data = $stack->pop()) === null) {
3448
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
3449
                }
3450 89
                if (($operand1Data = $stack->pop()) === null) {
3451
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
3452
                }
3453
3454 89
                $operand1 = self::dataTestReference($operand1Data);
3455 89
                $operand2 = self::dataTestReference($operand2Data);
3456
3457
                //    Log what we're doing
3458 89
                if ($token == ':') {
3459 32
                    $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...
3460
                } else {
3461 73
                    $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...
3462
                }
3463
3464
                //    Process the operation in the appropriate manner
3465
                switch ($token) {
3466
                    //    Comparison (Boolean) Operators
3467 89
                    case '>':            //    Greater than
3468 75
                    case '<':            //    Less than
3469 68
                    case '>=':            //    Greater than or Equal to
3470 60
                    case '<=':            //    Less than or Equal to
3471 53
                    case '=':            //    Equality
3472 44
                    case '<>':            //    Inequality
3473 61
                        $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
3474 61
                        break;
3475
                    //    Binary Operators
3476 37
                    case ':':            //    Range
3477 32
                        $sheet1 = $sheet2 = '';
3478 32
                        if (strpos($operand1Data['reference'], '!') !== false) {
3479 32
                            list($sheet1, $operand1Data['reference']) = explode('!', $operand1Data['reference']);
3480
                        } else {
3481
                            $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
3482
                        }
3483 32
                        if (strpos($operand2Data['reference'], '!') !== false) {
3484 32
                            list($sheet2, $operand2Data['reference']) = explode('!', $operand2Data['reference']);
3485
                        } else {
3486 9
                            $sheet2 = $sheet1;
3487
                        }
3488 32
                        if ($sheet1 == $sheet2) {
3489 32 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...
3490
                                if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
3491
                                    $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...
3492
                                } elseif (trim($operand1Data['reference']) == '') {
3493
                                    $operand1Data['reference'] = $pCell->getCoordinate();
3494
                                } else {
3495
                                    $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow();
3496
                                }
3497
                            }
3498 32 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...
3499
                                if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
3500
                                    $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value'];
3501
                                } elseif (trim($operand2Data['reference']) == '') {
3502
                                    $operand2Data['reference'] = $pCell->getCoordinate();
3503
                                } else {
3504
                                    $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow();
3505
                                }
3506
                            }
3507
3508 32
                            $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
3509 32
                            $oCol = $oRow = [];
3510 32
                            foreach ($oData as $oDatum) {
3511 32
                                $oCR = Cell::coordinateFromString($oDatum);
3512 32
                                $oCol[] = Cell::columnIndexFromString($oCR[0]) - 1;
3513 32
                                $oRow[] = $oCR[1];
3514
                            }
3515 32
                            $cellRef = Cell::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . Cell::stringFromColumnIndex(max($oCol)) . max($oRow);
3516 32 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...
3517 32
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
3518
                            } else {
3519
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3520
                            }
3521 32
                            $stack->push('Cell Reference', $cellValue, $cellRef);
3522
                        } else {
3523
                            $stack->push('Error', Calculation\Functions::REF(), null);
3524
                        }
3525 32
                        break;
3526 21
                    case '+':            //    Addition
3527 17
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'plusEquals', $stack);
3528 17
                        break;
3529 19
                    case '-':            //    Subtraction
3530 6
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'minusEquals', $stack);
3531 6
                        break;
3532 17
                    case '*':            //    Multiplication
3533 15
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'arrayTimesEquals', $stack);
3534 15
                        break;
3535 6
                    case '/':            //    Division
3536 3
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'arrayRightDivide', $stack);
3537 3
                        break;
3538 6
                    case '^':            //    Exponential
3539
                        $this->executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'power', $stack);
3540
                        break;
3541 6
                    case '&':            //    Concatenation
3542
                        //    If either of the operands is a matrix, we need to treat them both as matrices
3543
                        //        (converting the other operand to a matrix if need be); then perform the required
3544
                        //        matrix operation
3545 6
                        if (is_bool($operand1)) {
3546
                            $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3547
                        }
3548 6
                        if (is_bool($operand2)) {
3549
                            $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3550
                        }
3551 6
                        if ((is_array($operand1)) || (is_array($operand2))) {
3552
                            //    Ensure that both operands are arrays/matrices
3553 5
                            self::checkMatrixOperands($operand1, $operand2, 2);
3554
                            try {
3555
                                //    Convert operand 1 from a PHP array to a matrix
3556 5
                                $matrix = new Shared\JAMA\Matrix($operand1);
3557
                                //    Perform the required operation against the operand 1 matrix, passing in operand 2
3558 5
                                $matrixResult = $matrix->concat($operand2);
3559 5
                                $result = $matrixResult->getArray();
3560
                            } catch (Exception $ex) {
3561
                                $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...
3562 5
                                $result = '#VALUE!';
3563
                            }
3564
                        } else {
3565 1
                            $result = '"' . str_replace('""', '"', self::unwrapResult($operand1) . self::unwrapResult($operand2)) . '"';
3566
                        }
3567 6
                        $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...
3568 6
                        $stack->push('Value', $result);
3569 6
                        break;
3570
                    case '|':            //    Intersect
3571
                        $rowIntersect = array_intersect_key($operand1, $operand2);
3572
                        $cellIntersect = $oCol = $oRow = [];
3573
                        foreach (array_keys($rowIntersect) as $row) {
3574
                            $oRow[] = $row;
3575
                            foreach ($rowIntersect[$row] as $col => $data) {
3576
                                $oCol[] = Cell::columnIndexFromString($col) - 1;
3577
                                $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
3578
                            }
3579
                        }
3580
                        $cellRef = Cell::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . Cell::stringFromColumnIndex(max($oCol)) . max($oRow);
3581
                        $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...
3582
                        $stack->push('Value', $cellIntersect, $cellRef);
3583 89
                        break;
3584
                }
3585
3586
            // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
3587 91
            } elseif (($token === '~') || ($token === '%')) {
3588 1
                if (($arg = $stack->pop()) === null) {
3589
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
3590
                }
3591 1
                $arg = $arg['value'];
3592 1
                if ($token === '~') {
3593 1
                    $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...
3594 1
                    $multiplier = -1;
3595
                } else {
3596
                    $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...
3597
                    $multiplier = 0.01;
3598
                }
3599 1
                if (is_array($arg)) {
3600
                    self::checkMatrixOperands($arg, $multiplier, 2);
3601
                    try {
3602
                        $matrix1 = new Shared\JAMA\Matrix($arg);
3603
                        $matrixResult = $matrix1->arrayTimesEquals($multiplier);
3604
                        $result = $matrixResult->getArray();
3605
                    } catch (Exception $ex) {
3606
                        $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...
3607
                        $result = '#VALUE!';
3608
                    }
3609
                    $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...
3610
                    $stack->push('Value', $result);
3611
                } else {
3612 1
                    $this->executeNumericBinaryOperation($cellID, $multiplier, $arg, '*', 'arrayTimesEquals', $stack);
3613
                }
3614 91
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) {
3615 33
                $cellRef = null;
3616 33
                if (isset($matches[8])) {
3617
                    if ($pCell === null) {
3618
                        //                        We can't access the range, so return a REF error
3619
                        $cellValue = Calculation\Functions::REF();
3620
                    } else {
3621
                        $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
3622
                        if ($matches[2] > '') {
3623
                            $matches[2] = trim($matches[2], "\"'");
3624 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...
3625
                                //    It's a Reference to an external spreadsheet (not currently supported)
3626
                                return $this->raiseFormulaError('Unable to access External Workbook');
3627
                            }
3628
                            $matches[2] = trim($matches[2], "\"'");
3629
                            $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...
3630 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...
3631
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
3632
                            } else {
3633
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3634
                            }
3635
                            $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...
3636
                        } else {
3637
                            $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...
3638
                            if ($pCellParent !== null) {
3639
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
3640
                            } else {
3641
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3642
                            }
3643
                            $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...
3644
                        }
3645
                    }
3646
                } else {
3647 33
                    if ($pCell === null) {
3648
                        //                        We can't access the cell, so return a REF error
3649
                        $cellValue = Calculation\Functions::REF();
3650
                    } else {
3651 33
                        $cellRef = $matches[6] . $matches[7];
3652 33
                        if ($matches[2] > '') {
3653 13
                            $matches[2] = trim($matches[2], "\"'");
3654 13 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...
3655
                                //    It's a Reference to an external spreadsheet (not currently supported)
3656
                                return $this->raiseFormulaError('Unable to access External Workbook');
3657
                            }
3658 13
                            $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...
3659 13
                            if ($pCellParent !== null) {
3660 13
                                $cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
3661 13
                                if ($cellSheet && $cellSheet->cellExists($cellRef)) {
3662 13
                                    $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
3663 13
                                    $pCell->attach($pCellParent);
3664
                                } else {
3665 13
                                    $cellValue = null;
3666
                                }
3667
                            } else {
3668
                                return $this->raiseFormulaError('Unable to access Cell Reference');
3669
                            }
3670 13
                            $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...
3671
                        } else {
3672 21
                            $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...
3673 21
                            if ($pCellParent->isDataSet($cellRef)) {
3674 21
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
3675 21
                                $pCell->attach($pCellParent);
0 ignored issues
show
Bug introduced by
It seems like $pCellParent defined by $pCell !== null ? $pCell->getParent() : null on line 3438 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...
3676
                            } else {
3677 1
                                $cellValue = null;
3678
                            }
3679 21
                            $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...
3680
                        }
3681
                    }
3682
                }
3683 33
                $stack->push('Value', $cellValue, $cellRef);
3684
3685
            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
3686 79
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
3687 21
                $functionName = $matches[1];
3688 21
                $argCount = $stack->pop();
3689 21
                $argCount = $argCount['value'];
3690 21
                if ($functionName != 'MKMATRIX') {
3691 21
                    $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...
3692
                }
3693 21
                if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) {    // function
3694 21
                    if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
3695 21
                        $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
3696 21
                        $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']);
3697 21
                        $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']);
3698
                    } elseif (isset(self::$controlFunctions[$functionName])) {
3699
                        $functionCall = self::$controlFunctions[$functionName]['functionCall'];
3700
                        $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']);
3701
                        $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']);
3702
                    }
3703
                    // get the arguments for this function
3704 21
                    $args = $argArrayVals = [];
3705 21
                    for ($i = 0; $i < $argCount; ++$i) {
3706 21
                        $arg = $stack->pop();
3707 21
                        $a = $argCount - $i - 1;
3708 21
                        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...
3709 21
                            (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) &&
3710 21
                            (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) {
3711
                            if ($arg['reference'] === null) {
3712
                                $args[] = $cellID;
3713
                                if ($functionName != 'MKMATRIX') {
3714
                                    $argArrayVals[] = $this->showValue($cellID);
3715
                                }
3716
                            } else {
3717
                                $args[] = $arg['reference'];
3718
                                if ($functionName != 'MKMATRIX') {
3719
                                    $argArrayVals[] = $this->showValue($arg['reference']);
3720
                                }
3721
                            }
3722
                        } else {
3723 21
                            $args[] = self::unwrapResult($arg['value']);
3724 21
                            if ($functionName != 'MKMATRIX') {
3725 21
                                $argArrayVals[] = $this->showValue($arg['value']);
3726
                            }
3727
                        }
3728
                    }
3729
                    //    Reverse the order of the arguments
3730 21
                    krsort($args);
3731 21
                    if (($passByReference) && ($argCount == 0)) {
3732
                        $args[] = $cellID;
3733
                        $argArrayVals[] = $this->showValue($cellID);
3734
                    }
3735
3736 21
                    if ($functionName != 'MKMATRIX') {
3737 21
                        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...
3738
                            krsort($argArrayVals);
3739
                            $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...
3740
                        }
3741
                    }
3742
3743
                    //    Process the argument with the appropriate function call
3744 21
                    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...
3745 1
                        $args[] = $pCell;
3746
                    }
3747
3748 21
                    if (!is_array($functionCall)) {
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...
3749 1
                        foreach ($args as &$arg) {
3750
                            $arg = Calculation\Functions::flattenSingleValue($arg);
3751
                        }
3752 1
                        unset($arg);
3753
                    }
3754
3755 21
                    $result = call_user_func_array($functionCall, $args);
3756
3757 21
                    if ($functionName != 'MKMATRIX') {
3758 21
                        $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...
3759
                    }
3760 21
                    $stack->push('Value', self::wrapResult($result));
3761
                }
3762
            } else {
3763
                // if the token is a number, boolean, string or an Excel error, push it onto the stack
3764 79
                if (isset(self::$excelConstants[strtoupper($token)])) {
3765
                    $excelConstant = strtoupper($token);
3766
                    $stack->push('Constant Value', self::$excelConstants[$excelConstant]);
3767
                    $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...
3768 79
                } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == '"') || ($token[0] == '#')) {
3769 79
                    $stack->push('Value', $token);
3770
                // if the token is a named range, push the named range name onto the stack
3771 4
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
3772 4
                    $namedRange = $matches[6];
3773 4
                    $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...
3774 4
                    $cellValue = $this->extractNamedRange($namedRange, ((null !== $pCell) ? $pCellWorksheet : null), false);
3775 4
                    $pCell->attach($pCellParent);
0 ignored issues
show
Bug introduced by
It seems like $pCellParent defined by $pCell !== null ? $pCell->getParent() : null on line 3438 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...
3776 4
                    $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...
3777 4
                    $stack->push('Named Range', $cellValue, $namedRange);
3778
                } else {
3779 91
                    return $this->raiseFormulaError("undefined variable '$token'");
3780
                }
3781
            }
3782
        }
3783
        // when we're out of tokens, the stack should have a single element, the final result
3784 91
        if ($stack->count() != 1) {
3785
            return $this->raiseFormulaError('internal error');
3786
        }
3787 91
        $output = $stack->pop();
3788 91
        $output = $output['value'];
3789
3790
//        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...
3791
//            return array_shift(Calculation\Functions::flattenArray($output));
3792
//        }
3793 91
        return $output;
3794
    }
3795
3796 20
    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...
3797
    {
3798 20
        if (is_array($operand)) {
3799 16
            if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) {
3800
                do {
3801 16
                    $operand = array_pop($operand);
3802 16
                } while (is_array($operand));
3803
            }
3804
        }
3805
        //    Numbers, matrices and booleans can pass straight through, as they're already valid
3806 20
        if (is_string($operand)) {
3807
            //    We only need special validations for the operand if it is a string
3808
            //    Start by stripping off the quotation marks we use to identify true excel string values internally
3809 1
            if ($operand > '' && $operand[0] == '"') {
3810
                $operand = self::unwrapResult($operand);
3811
            }
3812
            //    If the string is a numeric value, we treat it as a numeric, so no further testing
3813 1
            if (!is_numeric($operand)) {
3814
                //    If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations
3815 1
                if ($operand > '' && $operand[0] == '#') {
3816
                    $stack->push('Value', $operand);
3817
                    $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...
3818
3819
                    return false;
3820 1
                } elseif (!Shared\StringHelper::convertToNumberIfFraction($operand)) {
3821
                    //    If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations
3822 1
                    $stack->push('Value', '#VALUE!');
3823 1
                    $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...
3824
3825 1
                    return false;
3826
                }
3827
            }
3828
        }
3829
3830
        //    return a true if the value of the operand is one that we can use in normal binary operations
3831 20
        return true;
3832
    }
3833
3834 61
    private function executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, &$stack, $recursingArrays = false)
3835
    {
3836
        //    If we're dealing with matrix operations, we want a matrix result
3837 61
        if ((is_array($operand1)) || (is_array($operand2))) {
3838 9
            $result = [];
3839 9
            if ((is_array($operand1)) && (!is_array($operand2))) {
3840 9 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...
3841 9
                    $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...
3842 9
                    $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack);
3843 9
                    $r = $stack->pop();
3844 9
                    $result[$x] = $r['value'];
3845
                }
3846
            } elseif ((!is_array($operand1)) && (is_array($operand2))) {
3847 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...
3848
                    $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...
3849
                    $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack);
3850
                    $r = $stack->pop();
3851
                    $result[$x] = $r['value'];
3852
                }
3853
            } else {
3854
                if (!$recursingArrays) {
3855
                    self::checkMatrixOperands($operand1, $operand2, 2);
3856
                }
3857
                foreach ($operand1 as $x => $operandData) {
3858
                    $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...
3859
                    $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true);
3860
                    $r = $stack->pop();
3861
                    $result[$x] = $r['value'];
3862
                }
3863
            }
3864
            //    Log the result details
3865 9
            $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...
3866
            //    And push the result onto the stack
3867 9
            $stack->push('Array', $result);
3868
3869 9
            return true;
3870
        }
3871
3872
        //    Simple validate the two operands if they are string values
3873 61
        if (is_string($operand1) && $operand1 > '' && $operand1[0] == '"') {
3874 33
            $operand1 = self::unwrapResult($operand1);
3875
        }
3876 61
        if (is_string($operand2) && $operand2 > '' && $operand2[0] == '"') {
3877 42
            $operand2 = self::unwrapResult($operand2);
3878
        }
3879
3880
        // Use case insensitive comparaison if not OpenOffice mode
3881 61
        if (Calculation\Functions::getCompatibilityMode() != Calculation\Functions::COMPATIBILITY_OPENOFFICE) {
3882 61
            if (is_string($operand1)) {
3883 33
                $operand1 = strtoupper($operand1);
3884
            }
3885 61
            if (is_string($operand2)) {
3886 42
                $operand2 = strtoupper($operand2);
3887
            }
3888
        }
3889
3890 61
        $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && Calculation\Functions::getCompatibilityMode() == Calculation\Functions::COMPATIBILITY_OPENOFFICE;
3891
3892
        //    execute the necessary operation
3893
        switch ($operation) {
3894
            //    Greater than
3895 61 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...
3896 14
                if ($useLowercaseFirstComparison) {
3897 9
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) > 0;
3898
                } else {
3899 14
                    $result = ($operand1 > $operand2);
3900
                }
3901 14
                break;
3902
            //    Less than
3903 47 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...
3904 7
                if ($useLowercaseFirstComparison) {
3905 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) < 0;
3906
                } else {
3907 7
                    $result = ($operand1 < $operand2);
3908
                }
3909 7
                break;
3910
            //    Equality
3911 40 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...
3912 9
                if (is_numeric($operand1) && is_numeric($operand2)) {
3913 3
                    $result = (abs($operand1 - $operand2) < $this->delta);
3914
                } else {
3915 6
                    $result = strcmp($operand1, $operand2) == 0;
3916
                }
3917 9
                break;
3918
            //    Greater than or equal
3919 31
            case '>=':
3920 8
                if (is_numeric($operand1) && is_numeric($operand2)) {
3921 4
                    $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 > $operand2));
3922 4
                } elseif ($useLowercaseFirstComparison) {
3923 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0;
3924
                } else {
3925 4
                    $result = strcmp($operand1, $operand2) >= 0;
3926
                }
3927 8
                break;
3928
            //    Less than or equal
3929 23
            case '<=':
3930 7
                if (is_numeric($operand1) && is_numeric($operand2)) {
3931 3
                    $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 < $operand2));
3932 4
                } elseif ($useLowercaseFirstComparison) {
3933 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0;
3934
                } else {
3935 4
                    $result = strcmp($operand1, $operand2) <= 0;
3936
                }
3937 7
                break;
3938
            //    Inequality
3939 16 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...
3940 16
                if (is_numeric($operand1) && is_numeric($operand2)) {
3941 3
                    $result = (abs($operand1 - $operand2) > 1E-14);
3942
                } else {
3943 13
                    $result = strcmp($operand1, $operand2) != 0;
3944
                }
3945 16
                break;
3946
        }
3947
3948
        //    Log the result details
3949 61
        $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
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...
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...
3950
        //    And push the result onto the stack
3951 61
        $stack->push('Value', $result);
3952
3953 61
        return true;
3954
    }
3955
3956
    /**
3957
     * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
3958
     *
3959
     * @param    string    $str1    First string value for the comparison
3960
     * @param    string    $str2    Second string value for the comparison
3961
     *
3962
     * @return   int
3963
     */
3964 21
    private function strcmpLowercaseFirst($str1, $str2)
3965
    {
3966 21
        $inversedStr1 = Shared\StringHelper::strCaseReverse($str1);
3967 21
        $inversedStr2 = Shared\StringHelper::strCaseReverse($str2);
3968
3969 21
        return strcmp($inversedStr1, $inversedStr2);
3970
    }
3971
3972
    /**
3973
     * @param string $matrixFunction
3974
     * @param mixed $cellID
3975
     * @param mixed $operand1
3976
     * @param mixed $operand2
3977
     * @param mixed $operation
3978
     */
3979 20
    private function executeNumericBinaryOperation($cellID, $operand1, $operand2, $operation, $matrixFunction, &$stack)
3980
    {
3981
        //    Validate the two operands
3982 20
        if (!$this->validateBinaryOperand($cellID, $operand1, $stack)) {
3983 1
            return false;
3984
        }
3985 20
        if (!$this->validateBinaryOperand($cellID, $operand2, $stack)) {
3986 1
            return false;
3987
        }
3988
3989
        //    If either of the operands is a matrix, we need to treat them both as matrices
3990
        //        (converting the other operand to a matrix if need be); then perform the required
3991
        //        matrix operation
3992 20
        if ((is_array($operand1)) || (is_array($operand2))) {
3993
            //    Ensure that both operands are arrays/matrices of the same size
3994
            self::checkMatrixOperands($operand1, $operand2, 2);
3995
3996
            try {
3997
                //    Convert operand 1 from a PHP array to a matrix
3998
                $matrix = new Shared\JAMA\Matrix($operand1);
3999
                //    Perform the required operation against the operand 1 matrix, passing in operand 2
4000
                $matrixResult = $matrix->$matrixFunction($operand2);
4001
                $result = $matrixResult->getArray();
4002
            } catch (Exception $ex) {
4003
                $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...
4004
                $result = '#VALUE!';
4005
            }
4006
        } else {
4007 20
            if ((Calculation\Functions::getCompatibilityMode() != Calculation\Functions::COMPATIBILITY_OPENOFFICE) &&
4008 20
                ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) ||
4009 20
                 (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))) {
4010
                $result = Calculation\Functions::VALUE();
4011
            } else {
4012
                //    If we're dealing with non-matrix operations, execute the necessary operation
4013
                switch ($operation) {
4014
                    //    Addition
4015 20
                    case '+':
4016 17
                        $result = $operand1 + $operand2;
4017 17
                        break;
4018
                    //    Subtraction
4019 17
                    case '-':
4020 6
                        $result = $operand1 - $operand2;
4021 6
                        break;
4022
                    //    Multiplication
4023 15
                    case '*':
4024 15
                        $result = $operand1 * $operand2;
4025 15
                        break;
4026
                    //    Division
4027 3
                    case '/':
4028 3
                        if ($operand2 == 0) {
4029
                            //    Trap for Divide by Zero error
4030 3
                            $stack->push('Value', '#DIV/0!');
4031 3
                            $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...
4032
4033 3
                            return false;
4034
                        }
4035
                            $result = $operand1 / $operand2;
4036
4037
                        break;
4038
                    //    Power
4039
                    case '^':
4040
                        $result = pow($operand1, $operand2);
4041
                        break;
4042
                }
4043
            }
4044
        }
4045
4046
        //    Log the result details
4047 20
        $this->_debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
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...
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...
4048
        //    And push the result onto the stack
4049 20
        $stack->push('Value', $result);
4050
4051 20
        return true;
4052
    }
4053
4054
    // trigger an error, but nicely, if need be
4055
    protected function raiseFormulaError($errorMessage)
4056
    {
4057
        $this->formulaError = $errorMessage;
4058
        $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...
4059
        if (!$this->suppressFormulaErrors) {
4060
            throw new Calculation\Exception($errorMessage);
4061
        }
4062
        trigger_error($errorMessage, E_USER_ERROR);
4063
    }
4064
4065
    /**
4066
     * Extract range values.
4067
     *
4068
     * @param    string      &$pRange    String based range representation
4069
     * @param    Worksheet   $pSheet        Worksheet
4070
     * @param    bool     $resetLog    Flag indicating whether calculation log should be reset or not
4071
     *
4072
     * @throws   Calculation\Exception
4073
     *
4074
     * @return   mixed       Array of values in range if range contains more than one element. Otherwise, a single value is returned.
4075
     */
4076 33
    public function extractCellRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true)
4077
    {
4078
        // Return value
4079 33
        $returnValue = [];
4080
4081 33
        if ($pSheet !== null) {
4082 33
            $pSheetName = $pSheet->getTitle();
4083 33 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...
4084
                list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
4085
                $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
4086
            }
4087
4088
            // Extract range
4089 33
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
4090 33
            $pRange = $pSheetName . '!' . $pRange;
4091 33
            if (!isset($aReferences[1])) {
4092
                //    Single cell in range
4093 33
                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...
4094 33
                $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...
4095 33 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...
4096 33
                    $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
4097
                } else {
4098 33
                    $returnValue[$currentRow][$currentCol] = null;
4099
                }
4100
            } else {
4101
                // Extract cell data for all cells in the range
4102 32
                foreach ($aReferences as $reference) {
4103
                    // Extract range
4104 32
                    sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
4105 32
                    $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...
4106 32 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...
4107 32
                        $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
4108
                    } else {
4109 32
                        $returnValue[$currentRow][$currentCol] = null;
4110
                    }
4111
                }
4112
            }
4113
        }
4114
4115 33
        return $returnValue;
4116
    }
4117
4118
    /**
4119
     * Extract range values.
4120
     *
4121
     * @param    string       &$pRange    String based range representation
4122
     * @param    Worksheet    $pSheet        Worksheet
4123
     * @param    bool      $resetLog    Flag indicating whether calculation log should be reset or not
4124
     *
4125
     * @throws   Calculation\Exception
4126
     *
4127
     * @return   mixed        Array of values in range if range contains more than one element. Otherwise, a single value is returned.
4128
     */
4129 4
    public function extractNamedRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true)
4130
    {
4131
        // Return value
4132 4
        $returnValue = [];
4133
4134 4
        if ($pSheet !== null) {
4135 4
            $pSheetName = $pSheet->getTitle();
4136 4 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...
4137
                list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
4138
                $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
4139
            }
4140
4141
            // Named range?
4142 4
            $namedRange = NamedRange::resolveRange($pRange, $pSheet);
4143 4
            if ($namedRange !== null) {
4144 2
                $pSheet = $namedRange->getWorksheet();
4145 2
                $pRange = $namedRange->getRange();
4146 2
                $splitRange = Cell::splitRange($pRange);
4147
                //    Convert row and column references
4148 2
                if (ctype_alpha($splitRange[0][0])) {
4149
                    $pRange = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow();
4150 2
                } elseif (ctype_digit($splitRange[0][0])) {
4151 2
                    $pRange = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1];
4152
                }
4153
            } else {
4154 2
                return Calculation\Functions::REF();
4155
            }
4156
4157
            // Extract range
4158 2
            $aReferences = Cell::extractAllCellReferencesInRange($pRange);
4159 2
            if (!isset($aReferences[1])) {
4160
                //    Single cell (or single column or row) in range
4161 1
                list($currentCol, $currentRow) = Cell::coordinateFromString($aReferences[0]);
4162 1
                $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...
4163 1 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...
4164 1
                    $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
4165
                } else {
4166 1
                    $returnValue[$currentRow][$currentCol] = null;
4167
                }
4168
            } else {
4169
                // Extract cell data for all cells in the range
4170 1
                foreach ($aReferences as $reference) {
4171
                    // Extract range
4172 1
                    list($currentCol, $currentRow) = Cell::coordinateFromString($reference);
4173 1
                    $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...
4174 1 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...
4175 1
                        $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
4176
                    } else {
4177 1
                        $returnValue[$currentRow][$currentCol] = null;
4178
                    }
4179
                }
4180
            }
4181
        }
4182
4183 2
        return $returnValue;
4184
    }
4185
4186
    /**
4187
     * Is a specific function implemented?
4188
     *
4189
     * @param    string    $pFunction    Function Name
4190
     *
4191
     * @return    bool
4192
     */
4193 3
    public function isImplemented($pFunction)
4194
    {
4195 3
        $pFunction = strtoupper($pFunction);
4196 3
        $notImplemented = !isset(self::$phpSpreadsheetFunctions[$pFunction]) || (is_array(self::$phpSpreadsheetFunctions[$pFunction]['functionCall']) && self::$phpSpreadsheetFunctions[$pFunction]['functionCall'][1] === 'DUMMY');
4197
4198 3
        return !$notImplemented;
4199
    }
4200
4201
    /**
4202
     * Get a list of all implemented functions as an array of function objects.
4203
     *
4204
     * @return    array of Calculation\Category
4205
     */
4206
    public function getFunctions()
4207
    {
4208
        return self::$phpSpreadsheetFunctions;
4209
    }
4210
4211
    /**
4212
     * Get a list of implemented Excel function names.
4213
     *
4214
     * @return    array
4215
     */
4216 2
    public function getImplementedFunctionNames()
4217
    {
4218 2
        $returnValue = [];
4219 2
        foreach (self::$phpSpreadsheetFunctions as $functionName => $function) {
4220 2
            if ($this->isImplemented($functionName)) {
4221 2
                $returnValue[] = $functionName;
4222
            }
4223
        }
4224
4225 2
        return $returnValue;
4226
    }
4227
}
4228