Completed
Push — develop ( 56245d...8ce610 )
by Adrien
18:12
created

Calculation::getLocale()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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