1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpOffice\PhpSpreadsheet\Calculation; |
4
|
|
|
|
5
|
|
|
use PhpOffice\PhpSpreadsheet\Cell\Cell; |
6
|
|
|
use PhpOffice\PhpSpreadsheet\Shared\Date; |
7
|
|
|
|
8
|
|
|
class Functions |
9
|
|
|
{ |
10
|
|
|
const PRECISION = 8.88E-016; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* 2 / PI. |
14
|
|
|
*/ |
15
|
|
|
const M_2DIVPI = 0.63661977236758134307553505349006; |
16
|
|
|
|
17
|
|
|
const COMPATIBILITY_EXCEL = 'Excel'; |
18
|
|
|
const COMPATIBILITY_GNUMERIC = 'Gnumeric'; |
19
|
|
|
const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc'; |
20
|
|
|
|
21
|
|
|
/** Use of RETURNDATE_PHP_NUMERIC is discouraged - not 32-bit Y2038-safe, no timezone. */ |
22
|
|
|
const RETURNDATE_PHP_NUMERIC = 'P'; |
23
|
|
|
/** Use of RETURNDATE_UNIX_TIMESTAMP is discouraged - not 32-bit Y2038-safe, no timezone. */ |
24
|
|
|
const RETURNDATE_UNIX_TIMESTAMP = 'P'; |
25
|
|
|
const RETURNDATE_PHP_OBJECT = 'O'; |
26
|
|
|
const RETURNDATE_PHP_DATETIME_OBJECT = 'O'; |
27
|
|
|
const RETURNDATE_EXCEL = 'E'; |
28
|
|
|
|
29
|
|
|
public const NOT_YET_IMPLEMENTED = '#Not Yet Implemented'; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Compatibility mode to use for error checking and responses. |
33
|
|
|
*/ |
34
|
|
|
protected static string $compatibilityMode = self::COMPATIBILITY_EXCEL; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Data Type to use when returning date values. |
38
|
|
|
*/ |
39
|
|
|
protected static string $returnDateType = self::RETURNDATE_EXCEL; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Set the Compatibility Mode. |
43
|
|
|
* |
44
|
|
|
* @param string $compatibilityMode Compatibility Mode |
45
|
|
|
* Permitted values are: |
46
|
|
|
* Functions::COMPATIBILITY_EXCEL 'Excel' |
47
|
|
|
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' |
48
|
|
|
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' |
49
|
|
|
* |
50
|
|
|
* @return bool (Success or Failure) |
51
|
|
|
*/ |
52
|
8211 |
|
public static function setCompatibilityMode(string $compatibilityMode): bool |
53
|
|
|
{ |
54
|
|
|
if ( |
55
|
8211 |
|
($compatibilityMode == self::COMPATIBILITY_EXCEL) |
56
|
8211 |
|
|| ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) |
57
|
8211 |
|
|| ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE) |
58
|
|
|
) { |
59
|
8211 |
|
self::$compatibilityMode = $compatibilityMode; |
60
|
|
|
|
61
|
8211 |
|
return true; |
62
|
|
|
} |
63
|
|
|
|
64
|
1 |
|
return false; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Return the current Compatibility Mode. |
69
|
|
|
* |
70
|
|
|
* @return string Compatibility Mode |
71
|
|
|
* Possible Return values are: |
72
|
|
|
* Functions::COMPATIBILITY_EXCEL 'Excel' |
73
|
|
|
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' |
74
|
|
|
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' |
75
|
|
|
*/ |
76
|
9472 |
|
public static function getCompatibilityMode(): string |
77
|
|
|
{ |
78
|
9472 |
|
return self::$compatibilityMode; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP DateTime Object). |
83
|
|
|
* |
84
|
|
|
* @param string $returnDateType Return Date Format |
85
|
|
|
* Permitted values are: |
86
|
|
|
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P' |
87
|
|
|
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' |
88
|
|
|
* Functions::RETURNDATE_EXCEL 'E' |
89
|
|
|
* |
90
|
|
|
* @return bool Success or failure |
91
|
|
|
*/ |
92
|
3221 |
|
public static function setReturnDateType(string $returnDateType): bool |
93
|
|
|
{ |
94
|
|
|
if ( |
95
|
3221 |
|
($returnDateType == self::RETURNDATE_UNIX_TIMESTAMP) |
96
|
3221 |
|
|| ($returnDateType == self::RETURNDATE_PHP_DATETIME_OBJECT) |
97
|
3221 |
|
|| ($returnDateType == self::RETURNDATE_EXCEL) |
98
|
|
|
) { |
99
|
3221 |
|
self::$returnDateType = $returnDateType; |
100
|
|
|
|
101
|
3221 |
|
return true; |
102
|
|
|
} |
103
|
|
|
|
104
|
1 |
|
return false; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object). |
109
|
|
|
* |
110
|
|
|
* @return string Return Date Format |
111
|
|
|
* Possible Return values are: |
112
|
|
|
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P' |
113
|
|
|
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' |
114
|
|
|
* Functions::RETURNDATE_EXCEL ' 'E' |
115
|
|
|
*/ |
116
|
3334 |
|
public static function getReturnDateType(): string |
117
|
|
|
{ |
118
|
3334 |
|
return self::$returnDateType; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* DUMMY. |
123
|
|
|
* |
124
|
|
|
* @return string #Not Yet Implemented |
125
|
|
|
*/ |
126
|
15 |
|
public static function DUMMY(): string |
127
|
|
|
{ |
128
|
15 |
|
return self::NOT_YET_IMPLEMENTED; |
129
|
|
|
} |
130
|
|
|
|
131
|
5 |
|
public static function isMatrixValue(mixed $idx): bool |
132
|
|
|
{ |
133
|
5 |
|
return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0); |
134
|
|
|
} |
135
|
|
|
|
136
|
1 |
|
public static function isValue(mixed $idx): bool |
137
|
|
|
{ |
138
|
1 |
|
return substr_count($idx, '.') === 0; |
139
|
|
|
} |
140
|
|
|
|
141
|
102 |
|
public static function isCellValue(mixed $idx): bool |
142
|
|
|
{ |
143
|
102 |
|
return substr_count($idx, '.') > 1; |
144
|
|
|
} |
145
|
|
|
|
146
|
209 |
|
public static function ifCondition(mixed $condition): string |
147
|
|
|
{ |
148
|
209 |
|
$condition = self::flattenSingleValue($condition); |
149
|
|
|
|
150
|
209 |
|
if ($condition === '' || $condition === null) { |
151
|
3 |
|
return '=""'; |
152
|
|
|
} |
153
|
208 |
|
if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='], true)) { |
154
|
118 |
|
$condition = self::operandSpecialHandling($condition); |
155
|
118 |
|
if (is_bool($condition)) { |
156
|
3 |
|
return '=' . ($condition ? 'TRUE' : 'FALSE'); |
157
|
115 |
|
} elseif (!is_numeric($condition)) { |
158
|
95 |
|
if ($condition !== '""') { // Not an empty string |
159
|
|
|
// Escape any quotes in the string value |
160
|
94 |
|
$condition = (string) preg_replace('/"/ui', '""', $condition); |
161
|
|
|
} |
162
|
95 |
|
$condition = Calculation::wrapResult(strtoupper($condition)); |
163
|
|
|
} |
164
|
|
|
|
165
|
115 |
|
return str_replace('""""', '""', '=' . $condition); |
166
|
|
|
} |
167
|
126 |
|
$operator = $operand = ''; |
168
|
126 |
|
if (1 === preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches)) { |
169
|
126 |
|
[, $operator, $operand] = $matches; |
170
|
|
|
} |
171
|
|
|
|
172
|
126 |
|
$operand = self::operandSpecialHandling($operand); |
173
|
126 |
|
if (is_numeric(trim($operand, '"'))) { |
174
|
73 |
|
$operand = trim($operand, '"'); |
175
|
80 |
|
} elseif (!is_numeric($operand) && $operand !== 'FALSE' && $operand !== 'TRUE') { |
176
|
76 |
|
$operand = str_replace('"', '""', $operand); |
177
|
76 |
|
$operand = Calculation::wrapResult(strtoupper($operand)); |
178
|
|
|
} |
179
|
|
|
|
180
|
126 |
|
return str_replace('""""', '""', $operator . $operand); |
181
|
|
|
} |
182
|
|
|
|
183
|
208 |
|
private static function operandSpecialHandling(mixed $operand): mixed |
184
|
|
|
{ |
185
|
208 |
|
if (is_numeric($operand) || is_bool($operand)) { |
186
|
111 |
|
return $operand; |
187
|
171 |
|
} elseif (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) { |
188
|
4 |
|
return strtoupper($operand); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
// Check for percentage |
192
|
167 |
|
if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $operand)) { |
193
|
4 |
|
return ((float) rtrim($operand, '%')) / 100; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
// Check for dates |
197
|
167 |
|
if (($dateValueOperand = Date::stringToExcel($operand)) !== false) { |
198
|
4 |
|
return $dateValueOperand; |
199
|
|
|
} |
200
|
|
|
|
201
|
167 |
|
return $operand; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Convert a multi-dimensional array to a simple 1-dimensional array. |
206
|
|
|
* |
207
|
|
|
* @param mixed $array Array to be flattened |
208
|
|
|
* |
209
|
|
|
* @return array Flattened array |
210
|
|
|
*/ |
211
|
5999 |
|
public static function flattenArray(mixed $array): array |
212
|
|
|
{ |
213
|
5999 |
|
if (!is_array($array)) { |
214
|
24 |
|
return (array) $array; |
215
|
|
|
} |
216
|
|
|
|
217
|
5989 |
|
$flattened = []; |
218
|
5989 |
|
$stack = array_values($array); |
219
|
|
|
|
220
|
5989 |
|
while (!empty($stack)) { |
221
|
5884 |
|
$value = array_shift($stack); |
222
|
|
|
|
223
|
5884 |
|
if (is_array($value)) { |
224
|
2512 |
|
array_unshift($stack, ...array_values($value)); |
225
|
|
|
} else { |
226
|
5881 |
|
$flattened[] = $value; |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
5989 |
|
return $flattened; |
231
|
|
|
} |
232
|
|
|
|
233
|
724 |
|
/** |
234
|
|
|
* Convert a multi-dimensional array to a simple 1-dimensional array. |
235
|
724 |
|
* Same as above but argument is specified in ... format. |
236
|
723 |
|
* |
237
|
|
|
* @param mixed $array Array to be flattened |
238
|
|
|
* |
239
|
|
|
* @return array Flattened array |
240
|
1 |
|
*/ |
241
|
1 |
|
public static function flattenArray2(mixed ...$array): array |
242
|
|
|
{ |
243
|
1 |
|
$flattened = []; |
244
|
|
|
$stack = array_values($array); |
245
|
|
|
|
246
|
|
|
while (!empty($stack)) { |
247
|
|
|
$value = array_shift($stack); |
248
|
|
|
|
249
|
|
|
if (is_array($value)) { |
250
|
|
|
array_unshift($stack, ...array_values($value)); |
251
|
|
|
} else { |
252
|
|
|
$flattened[] = $value; |
253
|
756 |
|
} |
254
|
|
|
} |
255
|
756 |
|
|
256
|
12 |
|
return $flattened; |
257
|
|
|
} |
258
|
|
|
|
259
|
745 |
|
public static function scalar(mixed $value): mixed |
260
|
745 |
|
{ |
261
|
745 |
|
if (!is_array($value)) { |
262
|
547 |
|
return $value; |
263
|
543 |
|
} |
264
|
497 |
|
|
265
|
490 |
|
do { |
266
|
|
|
$value = array_pop($value); |
267
|
|
|
} while (is_array($value)); |
268
|
63 |
|
|
269
|
|
|
return $value; |
270
|
|
|
} |
271
|
|
|
|
272
|
314 |
|
/** |
273
|
|
|
* Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing. |
274
|
|
|
* |
275
|
|
|
* @param array|mixed $array Array to be flattened |
276
|
745 |
|
* |
277
|
|
|
* @return array Flattened array |
278
|
|
|
*/ |
279
|
|
|
public static function flattenArrayIndexed($array): array |
280
|
|
|
{ |
281
|
|
|
if (!is_array($array)) { |
282
|
|
|
return (array) $array; |
283
|
|
|
} |
284
|
12363 |
|
|
285
|
|
|
$arrayValues = []; |
286
|
12363 |
|
foreach ($array as $k1 => $value) { |
287
|
7233 |
|
if (is_array($value)) { |
288
|
|
|
foreach ($value as $k2 => $val) { |
289
|
|
|
if (is_array($val)) { |
290
|
12363 |
|
foreach ($val as $k3 => $v) { |
291
|
|
|
$arrayValues[$k1 . '.' . $k2 . '.' . $k3] = $v; |
292
|
|
|
} |
293
|
10 |
|
} else { |
294
|
|
|
$arrayValues[$k1 . '.' . $k2] = $val; |
295
|
10 |
|
} |
296
|
10 |
|
} |
297
|
|
|
} else { |
298
|
10 |
|
$arrayValues[$k1] = $value; |
299
|
|
|
} |
300
|
10 |
|
} |
301
|
10 |
|
|
302
|
10 |
|
return $arrayValues; |
303
|
2 |
|
} |
304
|
2 |
|
|
305
|
2 |
|
/** |
306
|
2 |
|
* Convert an array to a single scalar value by extracting the first element. |
307
|
|
|
* |
308
|
|
|
* @param mixed $value Array or scalar value |
309
|
|
|
*/ |
310
|
10 |
|
public static function flattenSingleValue(mixed $value): mixed |
311
|
|
|
{ |
312
|
|
|
while (is_array($value)) { |
313
|
5 |
|
$value = array_shift($value); |
314
|
|
|
} |
315
|
5 |
|
|
316
|
|
|
return $value; |
317
|
|
|
} |
318
|
9996 |
|
|
319
|
|
|
public static function expandDefinedName(string $coordinate, Cell $cell): string |
320
|
9996 |
|
{ |
321
|
10 |
|
$worksheet = $cell->getWorksheet(); |
322
|
|
|
$spreadsheet = $worksheet->getParentOrThrow(); |
323
|
|
|
// Uppercase coordinate |
324
|
9996 |
|
$pCoordinatex = strtoupper($coordinate); |
325
|
|
|
// Eliminate leading equal sign |
326
|
|
|
$pCoordinatex = (string) preg_replace('/^=/', '', $pCoordinatex); |
327
|
|
|
$defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet); |
328
|
|
|
if ($defined !== null) { |
329
|
|
|
$worksheet2 = $defined->getWorkSheet(); |
330
|
|
|
if (!$defined->isFormula() && $worksheet2 !== null) { |
331
|
|
|
$coordinate = "'" . $worksheet2->getTitle() . "'!" |
332
|
|
|
. (string) preg_replace('/^=/', '', str_replace('$', '', $defined->getValue())); |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
return $coordinate; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
public static function trimTrailingRange(string $coordinate): string |
340
|
|
|
{ |
341
|
|
|
return (string) preg_replace('/:[\\w\$]+$/', '', $coordinate); |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
public static function trimSheetFromCellReference(string $coordinate): string |
345
|
|
|
{ |
346
|
|
|
if (str_contains($coordinate, '!')) { |
347
|
|
|
$coordinate = substr($coordinate, strrpos($coordinate, '!') + 1); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
return $coordinate; |
351
|
|
|
} |
352
|
|
|
} |
353
|
|
|
|