Total Complexity | 222 |
Total Lines | 1781 |
Duplicated Lines | 0 % |
Coverage | 91.13% |
Changes | 11 | ||
Bugs | 0 | Features | 0 |
Complex classes like Utility often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Utility, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
73 | class Utility |
||
74 | { |
||
75 | /** |
||
76 | * Encoding to use for multibyte-based functions. |
||
77 | * |
||
78 | * @var string Encoding |
||
79 | */ |
||
80 | private static string $encoding = 'UTF-8'; |
||
81 | |||
82 | /** |
||
83 | * Returns current encoding. |
||
84 | * |
||
85 | * @return string |
||
86 | */ |
||
87 | 2 | public static function getEncoding(): string |
|
88 | { |
||
89 | 2 | return self::$encoding; |
|
90 | } |
||
91 | |||
92 | /** |
||
93 | * Sets the encoding to use for multibyte-based functions. |
||
94 | * |
||
95 | * @param string $newEncoding Charset |
||
96 | * @param bool $iniUpdate Update php.ini's default_charset? |
||
97 | */ |
||
98 | 1 | public static function setEncoding(string $newEncoding = '', bool $iniUpdate = false): void |
|
99 | { |
||
100 | 1 | if ($newEncoding !== '') { |
|
101 | 1 | self::$encoding = $newEncoding; |
|
102 | } |
||
103 | |||
104 | 1 | if ($iniUpdate) { |
|
105 | 1 | static::iniSet('default_charset', self::$encoding); |
|
106 | 1 | static::iniSet('internal_encoding', self::$encoding); |
|
107 | } |
||
108 | } |
||
109 | |||
110 | /** array related functions **/ |
||
111 | |||
112 | /** |
||
113 | * arrayFlatten() |
||
114 | * |
||
115 | * Flattens a multi-dimensional array. |
||
116 | * |
||
117 | * Keys are preserved based on $separator. |
||
118 | * |
||
119 | * @param array<mixed> $array Array to flatten. |
||
120 | * @param string $separator The new keys are a list of original keys separated by $separator. |
||
121 | * |
||
122 | * @since 1.2.0 |
||
123 | * @param string $prepend A string to prepend to resulting array keys. |
||
124 | * |
||
125 | * @return array<mixed> The flattened array. |
||
126 | */ |
||
127 | 2 | public static function arrayFlatten(array $array, string $separator = '.', string $prepend = ''): array |
|
128 | { |
||
129 | 2 | $result = []; |
|
130 | |||
131 | 2 | foreach ($array as $key => $value) { |
|
132 | 2 | if (is_array($value) && $value !== []) { |
|
133 | 2 | $result[] = static::arrayFlatten($value, $separator, $prepend . $key . $separator); |
|
134 | } else { |
||
135 | 2 | $result[] = [$prepend . $key => $value]; |
|
136 | } |
||
137 | } |
||
138 | |||
139 | 2 | if (count($result) === 0) { |
|
140 | 1 | return []; |
|
141 | } |
||
142 | 2 | return array_merge_recursive([], ...$result); |
|
143 | } |
||
144 | |||
145 | /** |
||
146 | * arrayMapDeep() |
||
147 | * |
||
148 | * Recursively applies a callback to all non-iterable elements of an array or an object. |
||
149 | * |
||
150 | * @since 1.2.0 - updated with inspiration from the WordPress map_deep() function. |
||
151 | * @see https://developer.wordpress.org/reference/functions/map_deep/ |
||
152 | * |
||
153 | * @param mixed $array The array to apply $callback to. |
||
154 | * @param callable $callback The callback function to apply. |
||
155 | * @return mixed |
||
156 | */ |
||
157 | 2 | public static function arrayMapDeep(mixed $array, callable $callback): mixed |
|
158 | { |
||
159 | 2 | if (is_array($array)) { |
|
160 | 2 | foreach ($array as $key => $value) { |
|
161 | 2 | $array[$key] = static::arrayMapDeep($value, $callback); |
|
162 | } |
||
163 | 2 | } elseif (is_object($array)) { |
|
164 | 1 | foreach (get_object_vars($array) as $key => $value) { |
|
165 | 1 | $array->$key = static::arrayMapDeep($value, $callback); |
|
166 | } |
||
167 | } else { |
||
168 | 2 | $array = call_user_func($callback, $array); |
|
169 | } |
||
170 | 2 | return $array; |
|
171 | } |
||
172 | |||
173 | /** |
||
174 | * arrayInterlace() |
||
175 | * |
||
176 | * Interlaces one or more arrays' values (not preserving keys). |
||
177 | * |
||
178 | * Example: |
||
179 | * |
||
180 | * var_dump(Utility::arrayInterlace( |
||
181 | * [1, 2, 3], |
||
182 | * ['a', 'b', 'c'] |
||
183 | * )); |
||
184 | * |
||
185 | * Result: |
||
186 | * Array ( |
||
187 | * [0] => 1 |
||
188 | * [1] => a |
||
189 | * [2] => 2 |
||
190 | * [3] => b |
||
191 | * [4] => 3 |
||
192 | * [5] => c |
||
193 | * ) |
||
194 | * |
||
195 | * @since 1.2.0 |
||
196 | * |
||
197 | * @param array<mixed> ...$args |
||
198 | * @return array<mixed>|false |
||
199 | */ |
||
200 | 1 | public static function arrayInterlace(array ...$args): array | false |
|
201 | { |
||
202 | 1 | $numArgs = count($args); |
|
203 | |||
204 | 1 | if ($numArgs === 0) { |
|
205 | 1 | return false; |
|
206 | } |
||
207 | |||
208 | 1 | if ($numArgs === 1) { |
|
209 | 1 | return $args[0]; |
|
210 | } |
||
211 | |||
212 | 1 | $totalElements = 0; |
|
213 | |||
214 | 1 | for ($i = 0; $i < $numArgs; $i++) { |
|
215 | 1 | $totalElements += count($args[$i]); |
|
216 | } |
||
217 | |||
218 | 1 | $newArray = []; |
|
219 | |||
220 | 1 | for ($i = 0; $i < $totalElements; $i++) { |
|
221 | 1 | for ($a = 0; $a < $numArgs; $a++) { |
|
222 | 1 | if (isset($args[$a][$i])) { |
|
223 | 1 | $newArray[] = $args[$a][$i]; |
|
224 | } |
||
225 | } |
||
226 | } |
||
227 | 1 | return $newArray; |
|
228 | } |
||
229 | |||
230 | /** string related functions **/ |
||
231 | |||
232 | /** |
||
233 | * title() |
||
234 | * |
||
235 | * Convert the given string to title case. |
||
236 | * |
||
237 | * @param string $value Value to convert. |
||
238 | * @return string |
||
239 | */ |
||
240 | 1 | public static function title(string $value): string |
|
241 | { |
||
242 | 1 | return mb_convert_case($value, MB_CASE_TITLE, (self::$encoding ?? null)); |
|
243 | } |
||
244 | |||
245 | /** |
||
246 | * lower() |
||
247 | * |
||
248 | * Convert the given string to lower case. |
||
249 | * |
||
250 | * @param string $value Value to convert. |
||
251 | * @return string |
||
252 | */ |
||
253 | 10 | public static function lower(string $value): string |
|
254 | { |
||
255 | 10 | return mb_convert_case($value, MB_CASE_LOWER, (self::$encoding ?? null)); |
|
256 | } |
||
257 | |||
258 | /** |
||
259 | * upper() |
||
260 | * |
||
261 | * Convert the given string to upper case. |
||
262 | * |
||
263 | * @param string $value Value to convert. |
||
264 | * @return string |
||
265 | */ |
||
266 | 3 | public static function upper(string $value): string |
|
267 | { |
||
268 | 3 | return mb_convert_case($value, MB_CASE_UPPER, (self::$encoding ?? null)); |
|
269 | } |
||
270 | |||
271 | /** |
||
272 | * substr() |
||
273 | * |
||
274 | * Returns the portion of string specified by the start and length parameters. |
||
275 | * |
||
276 | * @param string $string The input string. |
||
277 | * @param int $start Start position. |
||
278 | * @param int|null $length Characters from $start. |
||
279 | * @return string Extracted part of the string. |
||
280 | */ |
||
281 | 5 | public static function substr(string $string, int $start, ?int $length = null): string |
|
282 | { |
||
283 | 5 | return mb_substr($string, $start, $length, (self::$encoding ?? null)); |
|
284 | } |
||
285 | |||
286 | /** |
||
287 | * lcfirst() |
||
288 | * |
||
289 | * Convert the first character of a given string to lower case. |
||
290 | * |
||
291 | * @since 1.0.1 |
||
292 | * |
||
293 | * @param string $string The input string. |
||
294 | * @return string |
||
295 | */ |
||
296 | 1 | public static function lcfirst(string $string): string |
|
297 | { |
||
298 | 1 | return self::lower(self::substr($string, 0, 1)) . self::substr($string, 1); |
|
299 | } |
||
300 | |||
301 | /** |
||
302 | * ucfirst() |
||
303 | * |
||
304 | * Convert the first character of a given string to upper case. |
||
305 | * |
||
306 | * @since 1.0.1 |
||
307 | * |
||
308 | * @param string $string The input string. |
||
309 | * @return string |
||
310 | */ |
||
311 | 1 | public static function ucfirst(string $string): string |
|
312 | { |
||
313 | 1 | return self::upper(self::substr($string, 0, 1)) . self::substr($string, 1); |
|
314 | } |
||
315 | |||
316 | /** |
||
317 | * Compares multibyte input strings in a binary safe case-insensitive manner. |
||
318 | * |
||
319 | * @since 1.0.1 |
||
320 | * |
||
321 | * @param string $str1 The first string. |
||
322 | * @param string $str2 The second string. |
||
323 | * @return int Returns < 0 if $str1 is less than $str2; > 0 if $str1 |
||
324 | * is greater than $str2, and 0 if they are equal. |
||
325 | */ |
||
326 | 1 | public static function strcasecmp(string $str1, string $str2): int |
|
327 | { |
||
328 | 1 | return strcmp(static::upper($str1), static::upper($str2)); |
|
329 | } |
||
330 | |||
331 | /** |
||
332 | * beginsWith() |
||
333 | * |
||
334 | * Determine if a string begins with another string. |
||
335 | * |
||
336 | * @param string $haystack String to search in. |
||
337 | * @param string $needle String to check for. |
||
338 | * @param bool $insensitive True to do a case-insensitive search. |
||
339 | * @param bool $multibyte True to perform checks via mbstring, false otherwise. |
||
340 | * @return bool |
||
341 | */ |
||
342 | 1 | public static function beginsWith(string $haystack, string $needle, bool $insensitive = false, bool $multibyte = false): bool |
|
343 | { |
||
344 | 1 | if ($multibyte === true) { |
|
345 | 1 | if ($insensitive) { |
|
346 | 1 | return mb_stripos($haystack, $needle) === 0; |
|
347 | } |
||
348 | return mb_strpos($haystack, $needle) === 0; |
||
349 | } |
||
350 | |||
351 | 1 | if ($insensitive) { |
|
352 | 1 | $haystack = static::lower($haystack); |
|
353 | 1 | $needle = static::lower($needle); |
|
354 | } |
||
355 | 1 | return str_starts_with($haystack, $needle); |
|
356 | } |
||
357 | |||
358 | /** |
||
359 | * endsWith() |
||
360 | * |
||
361 | * Determine if a string ends with another string. |
||
362 | * |
||
363 | * @param string $haystack String to search in. |
||
364 | * @param string $needle String to check for. |
||
365 | * @param bool $insensitive True to do a case-insensitive search. |
||
366 | * @param bool $multibyte True to perform checks via mbstring, false otherwise. |
||
367 | * @return bool |
||
368 | */ |
||
369 | 1 | public static function endsWith(string $haystack, string $needle, bool $insensitive = false, bool $multibyte = false): bool |
|
370 | { |
||
371 | 1 | if ($insensitive) { |
|
372 | 1 | $haystack = static::lower($haystack); |
|
373 | 1 | $needle = static::lower($needle); |
|
374 | } |
||
375 | 1 | return ( |
|
376 | 1 | $multibyte |
|
377 | 1 | ? static::substr($haystack, -static::length($needle)) === $needle |
|
378 | 1 | : str_ends_with($haystack, $needle) |
|
379 | 1 | ); |
|
380 | } |
||
381 | |||
382 | /** |
||
383 | * doesContain() |
||
384 | * |
||
385 | * Determine if a string exists within another string. |
||
386 | * |
||
387 | * @param string $haystack String to search in. |
||
388 | * @param string $needle String to check for. |
||
389 | * @param bool $insensitive True to do a case-insensitive search. |
||
390 | * @param bool $multibyte True to perform checks via mbstring, false otherwise. |
||
391 | * @return bool |
||
392 | */ |
||
393 | 2 | public static function doesContain(string $haystack, string $needle, bool $insensitive = false, bool $multibyte = false): bool |
|
394 | { |
||
395 | 2 | if ($insensitive) { |
|
396 | 1 | $haystack = static::lower($haystack); |
|
397 | 1 | $needle = static::lower($needle); |
|
398 | } |
||
399 | 2 | return ( |
|
400 | 2 | $multibyte |
|
401 | 1 | ? mb_strpos($haystack, $needle) !== false |
|
402 | 2 | : str_contains($haystack, $needle) !== false |
|
403 | 2 | ); |
|
404 | } |
||
405 | |||
406 | /** |
||
407 | * doesNotContain() |
||
408 | * |
||
409 | * Determine if a string does not exist within another string. |
||
410 | * |
||
411 | * @param string $haystack String to search in. |
||
412 | * @param string $needle String to check for. |
||
413 | * @param bool $insensitive True to do a case-insensitive search. |
||
414 | * @param bool $multibyte True to perform checks via mbstring, false otherwise. |
||
415 | * @return bool |
||
416 | */ |
||
417 | 5 | public static function doesNotContain(string $haystack, string $needle, bool $insensitive = false, bool $multibyte = false): bool |
|
418 | { |
||
419 | 5 | if ($insensitive) { |
|
420 | 1 | $haystack = static::lower($haystack); |
|
421 | 1 | $needle = static::lower($needle); |
|
422 | } |
||
423 | 5 | return ( |
|
424 | 5 | $multibyte |
|
425 | 1 | ? mb_strpos($haystack, $needle) === false |
|
426 | 5 | : str_contains($haystack, $needle) === false |
|
427 | 5 | ); |
|
428 | } |
||
429 | |||
430 | /** |
||
431 | * length() |
||
432 | * |
||
433 | * Get string length. |
||
434 | * |
||
435 | * @param string $string The string being checked for length. |
||
436 | * @param bool $binarySafe Forces '8bit' encoding so that the length check is binary safe. |
||
437 | * @return int |
||
438 | */ |
||
439 | 3 | public static function length(string $string, bool $binarySafe = false): int |
|
440 | { |
||
441 | 3 | return mb_strlen($string, ($binarySafe ? '8bit' : self::$encoding)); |
|
442 | } |
||
443 | |||
444 | /** |
||
445 | * ascii() |
||
446 | * |
||
447 | * Transliterate a UTF-8 value to ASCII. |
||
448 | * |
||
449 | * Note: Adapted from Illuminate/Support/Str |
||
450 | * |
||
451 | * @see https://packagist.org/packages/laravel/lumen-framework < v5.5 |
||
452 | * @see http://opensource.org/licenses/MIT |
||
453 | * |
||
454 | * @param string $value Value to transliterate. |
||
455 | * @return string |
||
456 | */ |
||
457 | 2 | public static function ascii(string $value): string |
|
458 | { |
||
459 | 2 | foreach (static::charMap() as $key => $val) { |
|
460 | 2 | $value = str_replace($key, $val, $value); |
|
461 | } |
||
462 | // preg_replace can return null if it encounters an error, so we return |
||
463 | // the passed $value in that instance. |
||
464 | 2 | return preg_replace('/[^\x20-\x7E]/u', '', $value) ?? $value; |
|
465 | } |
||
466 | |||
467 | /** |
||
468 | * charMap() |
||
469 | * |
||
470 | * Returns the replacements for the ascii method. |
||
471 | * |
||
472 | * @return array<string, string> |
||
473 | */ |
||
474 | 2 | protected static function charMap(): array |
|
475 | { |
||
476 | 2 | static $charMap; |
|
477 | |||
478 | 2 | if (isset($charMap)) { |
|
479 | 1 | return $charMap; |
|
480 | } |
||
481 | 1 | return $charMap = [ |
|
482 | 1 | 'Ǎ' => 'A', 'А' => 'A', 'Ā' => 'A', 'Ă' => 'A', 'Ą' => 'A', 'Å' => 'A', |
|
483 | 1 | 'Ǻ' => 'A', 'Ä' => 'Ae', 'Á' => 'A', 'À' => 'A', 'Ã' => 'A', 'Â' => 'A', |
|
484 | 1 | 'Æ' => 'AE', 'Ǽ' => 'AE', 'Б' => 'B', 'Ç' => 'C', 'Ć' => 'C', 'Ĉ' => 'C', |
|
485 | 1 | 'Č' => 'C', 'Ċ' => 'C', 'Ц' => 'C', 'Ч' => 'Ch', 'Ð' => 'Dj', 'Đ' => 'Dj', |
|
486 | 1 | 'Ď' => 'Dj', 'Д' => 'Dj', 'É' => 'E', 'Ę' => 'E', 'Ё' => 'E', 'Ė' => 'E', |
|
487 | 1 | 'Ê' => 'E', 'Ě' => 'E', 'Ē' => 'E', 'È' => 'E', 'Е' => 'E', 'Э' => 'E', |
|
488 | 1 | 'Ë' => 'E', 'Ĕ' => 'E', 'Ф' => 'F', 'Г' => 'G', 'Ģ' => 'G', 'Ġ' => 'G', |
|
489 | 1 | 'Ĝ' => 'G', 'Ğ' => 'G', 'Х' => 'H', 'Ĥ' => 'H', 'Ħ' => 'H', 'Ï' => 'I', |
|
490 | 1 | 'Ĭ' => 'I', 'İ' => 'I', 'Į' => 'I', 'Ī' => 'I', 'Í' => 'I', 'Ì' => 'I', |
|
491 | 1 | 'И' => 'I', 'Ǐ' => 'I', 'Ĩ' => 'I', 'Î' => 'I', 'IJ' => 'IJ', 'Ĵ' => 'J', |
|
492 | 1 | 'Й' => 'J', 'Я' => 'Ja', 'Ю' => 'Ju', 'К' => 'K', 'Ķ' => 'K', 'Ĺ' => 'L', |
|
493 | 1 | 'Л' => 'L', 'Ł' => 'L', 'Ŀ' => 'L', 'Ļ' => 'L', 'Ľ' => 'L', 'М' => 'M', |
|
494 | 1 | 'Н' => 'N', 'Ń' => 'N', 'Ñ' => 'N', 'Ņ' => 'N', 'Ň' => 'N', 'Ō' => 'O', |
|
495 | 1 | 'О' => 'O', 'Ǿ' => 'O', 'Ǒ' => 'O', 'Ơ' => 'O', 'Ŏ' => 'O', 'Ő' => 'O', |
|
496 | 1 | 'Ø' => 'O', 'Ö' => 'Oe', 'Õ' => 'O', 'Ó' => 'O', 'Ò' => 'O', 'Ô' => 'O', |
|
497 | 1 | 'Œ' => 'OE', 'П' => 'P', 'Ŗ' => 'R', 'Р' => 'R', 'Ř' => 'R', 'Ŕ' => 'R', |
|
498 | 1 | 'Ŝ' => 'S', 'Ş' => 'S', 'Š' => 'S', 'Ș' => 'S', 'Ś' => 'S', 'С' => 'S', |
|
499 | 1 | 'Ш' => 'Sh', 'Щ' => 'Shch', 'Ť' => 'T', 'Ŧ' => 'T', 'Ţ' => 'T', 'Ț' => 'T', |
|
500 | 1 | 'Т' => 'T', 'Ů' => 'U', 'Ű' => 'U', 'Ŭ' => 'U', 'Ũ' => 'U', 'Ų' => 'U', |
|
501 | 1 | 'Ū' => 'U', 'Ǜ' => 'U', 'Ǚ' => 'U', 'Ù' => 'U', 'Ú' => 'U', 'Ü' => 'Ue', |
|
502 | 1 | 'Ǘ' => 'U', 'Ǖ' => 'U', 'У' => 'U', 'Ư' => 'U', 'Ǔ' => 'U', 'Û' => 'U', |
|
503 | 1 | 'В' => 'V', 'Ŵ' => 'W', 'Ы' => 'Y', 'Ŷ' => 'Y', 'Ý' => 'Y', 'Ÿ' => 'Y', |
|
504 | 1 | 'Ź' => 'Z', 'З' => 'Z', 'Ż' => 'Z', 'Ž' => 'Z', 'Ж' => 'Zh', 'á' => 'a', |
|
505 | 1 | 'ă' => 'a', 'â' => 'a', 'à' => 'a', 'ā' => 'a', 'ǻ' => 'a', 'å' => 'a', |
|
506 | 1 | 'ä' => 'ae', 'ą' => 'a', 'ǎ' => 'a', 'ã' => 'a', 'а' => 'a', 'ª' => 'a', |
|
507 | 1 | 'æ' => 'ae', 'ǽ' => 'ae', 'б' => 'b', 'č' => 'c', 'ç' => 'c', 'ц' => 'c', |
|
508 | 1 | 'ċ' => 'c', 'ĉ' => 'c', 'ć' => 'c', 'ч' => 'ch', 'ð' => 'dj', 'ď' => 'dj', |
|
509 | 1 | 'д' => 'dj', 'đ' => 'dj', 'э' => 'e', 'é' => 'e', 'ё' => 'e', 'ë' => 'e', |
|
510 | 1 | 'ê' => 'e', 'е' => 'e', 'ĕ' => 'e', 'è' => 'e', 'ę' => 'e', 'ě' => 'e', |
|
511 | 1 | 'ė' => 'e', 'ē' => 'e', 'ƒ' => 'f', 'ф' => 'f', 'ġ' => 'g', 'ĝ' => 'g', |
|
512 | 1 | 'ğ' => 'g', 'г' => 'g', 'ģ' => 'g', 'х' => 'h', 'ĥ' => 'h', 'ħ' => 'h', |
|
513 | 1 | 'ǐ' => 'i', 'ĭ' => 'i', 'и' => 'i', 'ī' => 'i', 'ĩ' => 'i', 'į' => 'i', |
|
514 | 1 | 'ı' => 'i', 'ì' => 'i', 'î' => 'i', 'í' => 'i', 'ï' => 'i', 'ij' => 'ij', |
|
515 | 1 | 'ĵ' => 'j', 'й' => 'j', 'я' => 'ja', 'ю' => 'ju', 'ķ' => 'k', 'к' => 'k', |
|
516 | 1 | 'ľ' => 'l', 'ł' => 'l', 'ŀ' => 'l', 'ĺ' => 'l', 'ļ' => 'l', 'л' => 'l', |
|
517 | 1 | 'м' => 'm', 'ņ' => 'n', 'ñ' => 'n', 'ń' => 'n', 'н' => 'n', 'ň' => 'n', |
|
518 | 1 | 'ʼn' => 'n', 'ó' => 'o', 'ò' => 'o', 'ǒ' => 'o', 'ő' => 'o', 'о' => 'o', |
|
519 | 1 | 'ō' => 'o', 'º' => 'o', 'ơ' => 'o', 'ŏ' => 'o', 'ô' => 'o', 'ö' => 'oe', |
|
520 | 1 | 'õ' => 'o', 'ø' => 'o', 'ǿ' => 'o', 'œ' => 'oe', 'п' => 'p', 'р' => 'r', |
|
521 | 1 | 'ř' => 'r', 'ŕ' => 'r', 'ŗ' => 'r', 'ſ' => 's', 'ŝ' => 's', 'ș' => 's', |
|
522 | 1 | 'š' => 's', 'ś' => 's', 'с' => 's', 'ş' => 's', 'ш' => 'sh', 'щ' => 'shch', |
|
523 | 1 | 'ß' => 'ss', 'ţ' => 't', 'т' => 't', 'ŧ' => 't', 'ť' => 't', 'ț' => 't', |
|
524 | 1 | 'у' => 'u', 'ǘ' => 'u', 'ŭ' => 'u', 'û' => 'u', 'ú' => 'u', 'ų' => 'u', |
|
525 | 1 | 'ù' => 'u', 'ű' => 'u', 'ů' => 'u', 'ư' => 'u', 'ū' => 'u', 'ǚ' => 'u', |
|
526 | 1 | 'ǜ' => 'u', 'ǔ' => 'u', 'ǖ' => 'u', 'ũ' => 'u', 'ü' => 'ue', 'в' => 'v', |
|
527 | 1 | 'ŵ' => 'w', 'ы' => 'y', 'ÿ' => 'y', 'ý' => 'y', 'ŷ' => 'y', 'ź' => 'z', |
|
528 | 1 | 'ž' => 'z', 'з' => 'z', 'ż' => 'z', 'ж' => 'zh', 'ь' => '', 'ъ' => '', |
|
529 | 1 | "\xC2\xA0" => ' ', "\xE2\x80\x80" => ' ', "\xE2\x80\x81" => ' ', |
|
530 | 1 | "\xE2\x80\x82" => ' ', "\xE2\x80\x83" => ' ', "\xE2\x80\x84" => ' ', |
|
531 | 1 | "\xE2\x80\x85" => ' ', "\xE2\x80\x86" => ' ', "\xE2\x80\x87" => ' ', |
|
532 | 1 | "\xE2\x80\x88" => ' ', "\xE2\x80\x89" => ' ', "\xE2\x80\x8A" => ' ', |
|
533 | 1 | "\xE2\x80\xAF" => ' ', "\xE2\x81\x9F" => ' ', "\xE3\x80\x80" => ' ' |
|
534 | |||
535 | 1 | ]; |
|
536 | } |
||
537 | |||
538 | /** |
||
539 | * slugify() |
||
540 | * |
||
541 | * Transforms a string into a URL or filesystem-friendly string. |
||
542 | * |
||
543 | * Note: Adapted from Illuminate/Support/Str::slug |
||
544 | * |
||
545 | * @see https://packagist.org/packages/laravel/lumen-framework < v5.5 |
||
546 | * @see http://opensource.org/licenses/MIT |
||
547 | * |
||
548 | * @param string $title String to convert. |
||
549 | * @param string $separator Separator used to separate words in $title. |
||
550 | * @return string |
||
551 | */ |
||
552 | 1 | public static function slugify(string $title, string $separator = '-'): string |
|
553 | { |
||
554 | 1 | $title = static::ascii($title); |
|
555 | |||
556 | // preg_replace can return null if an error occurs. It shouldn't happen, but if it does, |
||
557 | // we return what we have processed thus far |
||
558 | 1 | $title = ( |
|
559 | 1 | preg_replace('![' . preg_quote(($separator === '-' ? '_' : '-')) . ']+!u', $separator, $title) |
|
560 | ?? $title |
||
561 | 1 | ); |
|
562 | |||
563 | // Replace @ with the word 'at' |
||
564 | 1 | $title = str_replace('@', $separator . 'at' . $separator, $title); |
|
565 | |||
566 | // Remove all characters that are not the separator, letters, numbers, or whitespace. |
||
567 | 1 | $title = ( |
|
568 | 1 | preg_replace('![^' . preg_quote($separator) . '\pL\pN\s]+!u', '', static::lower($title)) |
|
569 | ?? $title |
||
570 | 1 | ); |
|
571 | |||
572 | // Replace all separator characters and whitespace by a single separator |
||
573 | 1 | $title = ( |
|
574 | 1 | preg_replace('![' . preg_quote($separator) . '\s]+!u', $separator, $title) |
|
575 | ?? $title |
||
576 | 1 | ); |
|
577 | |||
578 | // Cleanup $title |
||
579 | 1 | return trim($title, $separator); |
|
580 | } |
||
581 | |||
582 | /** |
||
583 | * randomBytes() |
||
584 | * |
||
585 | * Generate cryptographically secure pseudo-random bytes. |
||
586 | * |
||
587 | * @param int $length Length of the random string that should be returned in bytes. |
||
588 | * @return string |
||
589 | * |
||
590 | * @throws \Random\RandomException If an invalid length is specified. |
||
591 | * If the random_bytes() function somehow fails. |
||
592 | */ |
||
593 | 2 | public static function randomBytes(int $length): string |
|
594 | { |
||
595 | // Sanity check |
||
596 | 2 | if ($length < 1 || $length > PHP_INT_MAX) { |
|
597 | 1 | throw new \Random\RandomException('Invalid $length specified.'); |
|
1 ignored issue
–
show
|
|||
598 | } |
||
599 | |||
600 | // Generate bytes |
||
601 | try { |
||
602 | 2 | $bytes = random_bytes($length); |
|
603 | } catch (\Random\RandomException $e) { |
||
604 | throw new \Random\RandomException( |
||
605 | 'Utility was unable to generate random bytes: ' . $e->getMessage(), $e->getCode(), $e->getPrevious() |
||
606 | ); |
||
607 | } |
||
608 | 2 | return $bytes; |
|
609 | } |
||
610 | |||
611 | /** |
||
612 | * randomInt() |
||
613 | * |
||
614 | * Generate a cryptographically secure pseudo-random integer. |
||
615 | * |
||
616 | * @param int $min The lowest value to be returned, which must be PHP_INT_MIN or higher. |
||
617 | * @param int $max The highest value to be returned, which must be less than or equal to PHP_INT_MAX. |
||
618 | * @return int |
||
619 | * |
||
620 | * @throws \Random\RandomException |
||
621 | */ |
||
622 | 2 | public static function randomInt(int $min, int $max): int |
|
623 | { |
||
624 | // Sanity check |
||
625 | 2 | if ($min < PHP_INT_MIN || $max > PHP_INT_MAX) { |
|
626 | throw new \Random\RandomException('$min and $max values must be within the \PHP_INT_MIN, \PHP_INT_MAX range'); |
||
627 | } |
||
628 | |||
629 | 2 | if ($min >= $max) { |
|
630 | 1 | throw new \Random\RandomException('$min value must be less than $max.'); |
|
631 | } |
||
632 | |||
633 | // Generate random int |
||
634 | try { |
||
635 | 2 | $int = random_int($min, $max); |
|
636 | } catch (\Random\RandomException $e) { |
||
637 | throw new \Random\RandomException( |
||
638 | 'Utility was unable to generate random int: ' . $e->getMessage(), $e->getCode(), $e->getPrevious() |
||
639 | ); |
||
640 | } |
||
641 | 2 | return $int; |
|
642 | } |
||
643 | |||
644 | /** |
||
645 | * randomString() |
||
646 | * |
||
647 | * Generates a secure random string, based on {@see static::randomBytes()}. |
||
648 | * |
||
649 | * @todo A better implementation. Could be done better. |
||
650 | * |
||
651 | * @param int $length Length the random string should be. |
||
652 | * @return string |
||
653 | * |
||
654 | * @throws \Random\RandomException |
||
655 | */ |
||
656 | 1 | public static function randomString(int $length = 8): string |
|
657 | { |
||
658 | // Sanity check |
||
659 | 1 | if ($length <= 0) { |
|
660 | 1 | throw new \Random\RandomException('$length must be greater than 0.'); |
|
661 | } |
||
662 | |||
663 | // Attempt to get random bytes |
||
664 | try { |
||
665 | 1 | $bytes = static::randomBytes($length * 2); |
|
666 | |||
667 | 1 | if ($bytes === '') { |
|
668 | 1 | throw new \Random\RandomException('Random bytes generator failure.'); |
|
669 | } |
||
670 | } catch (\Random\RandomException $e) { |
||
671 | throw new \Random\RandomException($e->getMessage(), 0, $e); |
||
672 | } |
||
673 | 1 | return static::substr(bin2hex($bytes), 0, $length); |
|
674 | } |
||
675 | |||
676 | /** directory/file related functions **/ |
||
677 | |||
678 | /** |
||
679 | * lineCounter() |
||
680 | * |
||
681 | * Parse a project directory for approximate line count for a project's |
||
682 | * codebase. |
||
683 | * |
||
684 | * @param string $directory Directory to parse. |
||
685 | * @param array<string> $ignore Subdirectories of $directory you wish |
||
686 | * to not include in the line count. |
||
687 | * @param array<string> $extensions An array of file types/extensions of |
||
688 | * files you want included in the line count. |
||
689 | * @param bool $skipEmpty If set to true, will not include empty |
||
690 | * lines in the line count. |
||
691 | * @param bool $onlyLineCount If set to true, only returns an array |
||
692 | * of line counts without directory/filenames. |
||
693 | * @return array<mixed> |
||
694 | * |
||
695 | * @throws InvalidArgumentException |
||
696 | */ |
||
697 | 1 | public static function lineCounter(string $directory, array $ignore = [], array $extensions = [], bool $skipEmpty = true, bool $onlyLineCount = false): array |
|
698 | { |
||
699 | // Sanity check |
||
700 | 1 | if (!is_dir($directory) || !is_readable($directory)) { |
|
701 | 1 | throw new InvalidArgumentException('Invalid $directory specified'); |
|
702 | } |
||
703 | |||
704 | // Initialize |
||
705 | 1 | $lines = []; |
|
706 | |||
707 | // Flags passed to \file(). |
||
708 | 1 | $flags = FILE_IGNORE_NEW_LINES; |
|
709 | |||
710 | 1 | if ($skipEmpty) { |
|
711 | 1 | $flags |= FILE_SKIP_EMPTY_LINES; |
|
712 | } |
||
713 | |||
714 | // Directory names we wish to ignore |
||
715 | 1 | if (count($ignore) > 0) { |
|
716 | 1 | $ignore = preg_quote(implode('|', $ignore), '#'); |
|
717 | } |
||
718 | |||
719 | // Traverse the directory |
||
720 | 1 | $iterator = new RecursiveIteratorIterator( |
|
721 | 1 | new RecursiveDirectoryIterator( |
|
722 | 1 | $directory, |
|
723 | 1 | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS |
|
724 | 1 | ) |
|
725 | 1 | ); |
|
726 | |||
727 | // Build the actual contents of the directory |
||
728 | /** @var RecursiveDirectoryIterator $val **/ |
||
729 | 1 | foreach ($iterator as $key => $val) { |
|
730 | 1 | if ($val->isFile()) { |
|
731 | if ( |
||
732 | 1 | (is_string($ignore) && preg_match("#($ignore)#i", $val->getPath()) === 1) |
|
733 | 1 | || (count($extensions) > 0 && !in_array($val->getExtension(), $extensions, true)) |
|
734 | ) { |
||
735 | 1 | continue; |
|
736 | } |
||
737 | |||
738 | 1 | $content = file($val->getPath() . DIRECTORY_SEPARATOR . $val->getFilename(), $flags); |
|
739 | |||
740 | 1 | if ($content === false) { |
|
741 | continue; |
||
742 | } |
||
743 | /** @var int<0, max> $content **/ |
||
744 | 1 | $content = count(/** @scrutinizer ignore-type */$content); |
|
745 | |||
746 | 1 | $lines[$val->getPath()][$val->getFilename()] = $content; |
|
747 | } |
||
748 | } |
||
749 | 1 | unset($iterator); |
|
750 | |||
751 | 1 | return ($onlyLineCount ? static::arrayFlatten($lines) : $lines); |
|
752 | } |
||
753 | |||
754 | /** |
||
755 | * directorySize() |
||
756 | * |
||
757 | * Retrieves size of a directory (in bytes). |
||
758 | * |
||
759 | * @param string $directory Directory to parse. |
||
760 | * @param array<string> $ignore Subdirectories of $directory you wish to not include. |
||
761 | * @return int |
||
762 | * |
||
763 | * @throws InvalidArgumentException |
||
764 | */ |
||
765 | 1 | public static function directorySize(string $directory, array $ignore = []): int |
|
766 | { |
||
767 | // Sanity checks |
||
768 | 1 | if (!is_dir($directory) || !is_readable($directory)) { |
|
769 | 1 | throw new InvalidArgumentException('Invalid $directory specified'); |
|
770 | } |
||
771 | |||
772 | // Initialize |
||
773 | 1 | $size = 0; |
|
774 | |||
775 | // Directories we wish to ignore, if any |
||
776 | 1 | $ignore = (count($ignore) > 0) ? preg_quote(implode('|', $ignore), '#') : ''; |
|
777 | |||
778 | // Traverse the directory |
||
779 | 1 | $iterator = new RecursiveIteratorIterator( |
|
780 | 1 | new RecursiveDirectoryIterator( |
|
781 | 1 | $directory, |
|
782 | 1 | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS |
|
783 | 1 | ) |
|
784 | 1 | ); |
|
785 | |||
786 | // Determine directory size by checking file sizes |
||
787 | /** @var RecursiveDirectoryIterator $val **/ |
||
788 | 1 | foreach ($iterator as $key => $val) { |
|
789 | 1 | if ($ignore !== '' && preg_match("#($ignore)#i", $val->getPath()) === 1) { |
|
790 | 1 | continue; |
|
791 | } |
||
792 | |||
793 | 1 | if ($val->isFile()) { |
|
794 | 1 | $size += $val->getSize(); |
|
795 | } |
||
796 | } |
||
797 | 1 | unset($iterator); |
|
798 | |||
799 | 1 | return $size; |
|
800 | } |
||
801 | |||
802 | /** |
||
803 | * Retrieves contents of a directory. |
||
804 | * |
||
805 | * @param string $directory Directory to parse. |
||
806 | * @param array<string> $ignore Subdirectories of $directory you wish to not include. |
||
807 | * @return array<mixed> |
||
808 | * |
||
809 | * @throws InvalidArgumentException |
||
810 | */ |
||
811 | 1 | public static function directoryList(string $directory, array $ignore = []): array |
|
812 | { |
||
813 | // Sanity checks |
||
814 | 1 | if (!is_dir($directory) || !is_readable($directory)) { |
|
815 | 1 | throw new InvalidArgumentException('Invalid $directory specified'); |
|
816 | } |
||
817 | |||
818 | // Initialize |
||
819 | 1 | $contents = []; |
|
820 | |||
821 | // Directories to ignore, if any |
||
822 | 1 | $ignore = (count($ignore) > 0) ? preg_quote(implode('|', $ignore), '#') : ''; |
|
823 | |||
824 | // Traverse the directory |
||
825 | 1 | $iterator = new RecursiveIteratorIterator( |
|
826 | 1 | new RecursiveDirectoryIterator( |
|
827 | 1 | $directory, |
|
828 | 1 | FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS |
|
829 | 1 | ) |
|
830 | 1 | ); |
|
831 | |||
832 | // Build the actual contents of the directory |
||
833 | /** @var RecursiveDirectoryIterator $val **/ |
||
834 | 1 | foreach ($iterator AS $key => $val) { |
|
835 | 1 | if ($ignore !== '' && preg_match("#($ignore)#i", $val->getPath()) === 1) { |
|
836 | 1 | continue; |
|
837 | } |
||
838 | 1 | $contents[] = $key; |
|
839 | } |
||
840 | 1 | natsort($contents); |
|
841 | |||
842 | 1 | return $contents; |
|
843 | } |
||
844 | |||
845 | /** |
||
846 | * normalizeFilePath() |
||
847 | * |
||
848 | * Normalizes a file or directory path. |
||
849 | * |
||
850 | * @param string $path The file or directory path. |
||
851 | * @param string $separator The directory separator. Defaults to DIRECTORY_SEPARATOR. |
||
852 | * @return string The normalized file or directory path |
||
853 | */ |
||
854 | 1 | public static function normalizeFilePath(string $path, string $separator = DIRECTORY_SEPARATOR): string |
|
855 | { |
||
856 | // Clean up our path |
||
857 | 1 | $separator = ($separator === '' ? DIRECTORY_SEPARATOR : $separator); |
|
858 | |||
859 | 1 | $path = rtrim(strtr($path, '/\\', $separator . $separator), $separator); |
|
860 | |||
861 | if ( |
||
862 | 1 | static::doesNotContain($separator . $path, "{$separator}.") |
|
863 | 1 | && static::doesNotContain($path, $separator . $separator) |
|
864 | ) { |
||
865 | 1 | return $path; |
|
866 | } |
||
867 | |||
868 | // Initialize |
||
869 | 1 | $parts = []; |
|
870 | |||
871 | // Grab file path parts |
||
872 | 1 | foreach (explode($separator, $path) as $part) { |
|
873 | 1 | if ($part === '..' && count($parts) > 0 && end($parts) !== '..') { |
|
874 | 1 | array_pop($parts); |
|
875 | 1 | } elseif ($part === '.' || $part === '' && count($parts) > 0) { |
|
876 | 1 | continue; |
|
877 | } else { |
||
878 | 1 | $parts[] = $part; |
|
879 | } |
||
880 | } |
||
881 | |||
882 | // Build |
||
883 | 1 | $path = implode($separator, $parts); |
|
884 | 1 | return ($path === '' ? '.' : $path); |
|
885 | } |
||
886 | |||
887 | /** |
||
888 | * isReallyWritable() |
||
889 | * |
||
890 | * Checks to see if a file or directory is really writable. |
||
891 | * |
||
892 | * @param string $file File or directory to check. |
||
893 | * @return bool |
||
894 | * |
||
895 | * @throws \Random\RandomException If unable to generate random string for the temp file |
||
896 | * @throws RuntimeException If the file or directory does not exist |
||
897 | */ |
||
898 | 6 | public static function isReallyWritable(string $file): bool |
|
899 | { |
||
900 | 6 | clearstatcache(); |
|
901 | |||
902 | 6 | if (!file_exists($file)) { |
|
903 | 1 | throw new RuntimeException('Invalid file or directory specified'); |
|
904 | } |
||
905 | |||
906 | // If we are on Unix/Linux just run is_writable() |
||
907 | 6 | if (PHP_OS_FAMILY !== 'Windows') { |
|
908 | 6 | return is_writable($file); |
|
909 | } |
||
910 | |||
911 | // Otherwise, if on Windows... |
||
912 | $tmpFile = rtrim($file, '\\/') . DIRECTORY_SEPARATOR . hash('md5', static::randomString()) . '.txt'; |
||
913 | $tmpData = 'tmpData'; |
||
914 | |||
915 | $directoryOrFile = (is_dir($file)); |
||
916 | |||
917 | if ($directoryOrFile) { |
||
918 | $data = file_put_contents($tmpFile, $tmpData, FILE_APPEND); |
||
919 | } else { |
||
920 | $data = file_get_contents($file); |
||
921 | } |
||
922 | |||
923 | if (file_exists($tmpFile)) { |
||
924 | unlink($tmpFile); |
||
925 | } |
||
926 | return ($data !== false ? true : false); |
||
927 | } |
||
928 | |||
929 | /** |
||
930 | * fileRead() |
||
931 | * |
||
932 | * Perform a read operation on a pre-existing file. |
||
933 | * |
||
934 | * @param string $file Filename |
||
935 | * @return string|false |
||
936 | * |
||
937 | * @throws InvalidArgumentException |
||
938 | */ |
||
939 | 1 | public static function fileRead(string $file): string | false |
|
940 | { |
||
941 | // Sanity check |
||
942 | 1 | if (!is_readable($file)) { |
|
943 | 1 | throw new InvalidArgumentException(sprintf("File '%s' does not exist or is not readable.", $file)); |
|
944 | } |
||
945 | 1 | return file_get_contents($file); |
|
946 | } |
||
947 | |||
948 | /** |
||
949 | * fileWrite() |
||
950 | * |
||
951 | * Perform a write operation on a pre-existing file. |
||
952 | * |
||
953 | * @param string $file Filename |
||
954 | * @param string $data If writing to the file, the data to write. |
||
955 | * @param int $flags Bitwise OR'ed set of flags for file_put_contents. One or |
||
956 | * more of FILE_USE_INCLUDE_PATH, FILE_APPEND, LOCK_EX. |
||
957 | * {@link http://php.net/file_put_contents} |
||
958 | * @return string|int<0, max>|false |
||
959 | * |
||
960 | * @throws InvalidArgumentException|\Random\RandomException |
||
961 | */ |
||
962 | 5 | public static function fileWrite(string $file, string $data = '', int $flags = 0): string | false | int |
|
963 | { |
||
964 | // Sanity checks |
||
965 | 5 | if (!is_readable($file)) { |
|
966 | 1 | throw new InvalidArgumentException(sprintf("File '%s' does not exist or is not readable.", $file)); |
|
967 | } |
||
968 | |||
969 | 5 | if (!static::isReallyWritable($file)) { |
|
970 | throw new InvalidArgumentException(sprintf("File '%s' is not writable.", $file)); |
||
971 | } |
||
972 | |||
973 | 5 | if ($flags < 0) { |
|
974 | 1 | $flags = 0; |
|
975 | } |
||
976 | 5 | return file_put_contents($file, $data, $flags); |
|
977 | } |
||
978 | |||
979 | /** miscellaneous functions **/ |
||
980 | |||
981 | /** |
||
982 | * Convert Fahrenheit (Fº) To Celsius (Cº) |
||
983 | * |
||
984 | * @since 1.2.0 |
||
985 | * |
||
986 | * @param float $fahrenheit Value in Fahrenheit |
||
987 | * @param bool $rounded Whether or not to round the result. |
||
988 | * @param int $precision Precision to use if $rounded is true. |
||
989 | * @return float |
||
990 | */ |
||
991 | 1 | public static function fahrenheitToCelsius(float $fahrenheit, bool $rounded = true, int $precision = 2): float |
|
992 | { |
||
993 | 1 | $result = ($fahrenheit - 32) / 1.8; |
|
994 | |||
995 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
996 | } |
||
997 | |||
998 | /** |
||
999 | * Convert Celsius (Cº) To Fahrenheit (Fº) |
||
1000 | * |
||
1001 | * @since 1.2.0 |
||
1002 | * |
||
1003 | * @param float $celsius Value in Celsius |
||
1004 | * @param bool $rounded Whether or not to round the result. |
||
1005 | * @param int $precision Precision to use if $rounded is true. |
||
1006 | * @return float |
||
1007 | */ |
||
1008 | 1 | public static function celsiusToFahrenheit(float $celsius, bool $rounded = true, int $precision = 2): float |
|
1009 | { |
||
1010 | 1 | $result = ($celsius * 1.8) + 32; |
|
1011 | |||
1012 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1013 | } |
||
1014 | |||
1015 | /** |
||
1016 | * Convert Celsius (Cº) To Kelvin (K) |
||
1017 | * |
||
1018 | * @since 1.2.0 |
||
1019 | * |
||
1020 | * @param float $celsius Value in Celsius |
||
1021 | * @param bool $rounded Whether or not to round the result. |
||
1022 | * @param int $precision Precision to use if $rounded is true. |
||
1023 | * @return float |
||
1024 | */ |
||
1025 | 1 | public static function celsiusToKelvin(float $celsius, bool $rounded = true, int $precision = 2): float |
|
1026 | { |
||
1027 | 1 | $result = $celsius + 273.15; |
|
1028 | |||
1029 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1030 | } |
||
1031 | |||
1032 | /** |
||
1033 | * Convert Kelvin (K) To Celsius (Cº) |
||
1034 | * |
||
1035 | * @since 1.2.0 |
||
1036 | * |
||
1037 | * @param float $kelvin Value in Kelvin |
||
1038 | * @param bool $rounded Whether or not to round the result. |
||
1039 | * @param int $precision Precision to use if $rounded is true. |
||
1040 | * @return float |
||
1041 | */ |
||
1042 | 1 | public static function kelvinToCelsius(float $kelvin, bool $rounded = true, int $precision = 2): float |
|
1043 | { |
||
1044 | 1 | $result = $kelvin - 273.15; |
|
1045 | |||
1046 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1047 | } |
||
1048 | |||
1049 | /** |
||
1050 | * Convert Fahrenheit (Fº) To Kelvin (K) |
||
1051 | * |
||
1052 | * @since 1.2.0 |
||
1053 | * |
||
1054 | * @param float $fahrenheit Value in Fahrenheit |
||
1055 | * @param bool $rounded Whether or not to round the result. |
||
1056 | * @param int $precision Precision to use if $rounded is true. |
||
1057 | * @return float |
||
1058 | */ |
||
1059 | 1 | public static function fahrenheitToKelvin(float $fahrenheit, bool $rounded = true, int $precision = 2): float |
|
1060 | { |
||
1061 | 1 | $result = (($fahrenheit - 32) / 1.8) + 273.15; |
|
1062 | |||
1063 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1064 | } |
||
1065 | |||
1066 | /** |
||
1067 | * Convert Kelvin (K) To Fahrenheit (Fº) |
||
1068 | * |
||
1069 | * @since 1.2.0 |
||
1070 | * |
||
1071 | * @param float $kelvin Value in Kelvin |
||
1072 | * @param bool $rounded Whether or not to round the result. |
||
1073 | * @param int $precision Precision to use if $rounded is true. |
||
1074 | * @return float |
||
1075 | */ |
||
1076 | 1 | public static function kelvinToFahrenheit(float $kelvin, bool $rounded = true, int $precision = 2): float |
|
1077 | { |
||
1078 | 1 | $result = (($kelvin - 273.15) * 1.8) + 32; |
|
1079 | |||
1080 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1081 | } |
||
1082 | |||
1083 | /** |
||
1084 | * Convert Fahrenheit (Fº) To Rankine (ºR) |
||
1085 | * |
||
1086 | * @since 1.2.0 |
||
1087 | * |
||
1088 | * @param float $fahrenheit Value in Fahrenheit |
||
1089 | * @param bool $rounded Whether or not to round the result. |
||
1090 | * @param int $precision Precision to use if $rounded is true. |
||
1091 | * @return float |
||
1092 | */ |
||
1093 | 1 | public static function fahrenheitToRankine(float $fahrenheit, bool $rounded = true, int $precision = 2): float |
|
1094 | { |
||
1095 | 1 | $result = $fahrenheit + 459.67; |
|
1096 | |||
1097 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1098 | } |
||
1099 | |||
1100 | /** |
||
1101 | * Convert Rankine (ºR) To Fahrenheit (Fº) |
||
1102 | * |
||
1103 | * @since 1.2.0 |
||
1104 | * |
||
1105 | * @param float $rankine Value in Rankine |
||
1106 | * @param bool $rounded Whether or not to round the result. |
||
1107 | * @param int $precision Precision to use if $rounded is true. |
||
1108 | * @return float |
||
1109 | */ |
||
1110 | 1 | public static function rankineToFahrenheit(float $rankine, bool $rounded = true, int $precision = 2): float |
|
1111 | { |
||
1112 | 1 | $result = $rankine - 459.67; |
|
1113 | |||
1114 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1115 | } |
||
1116 | |||
1117 | /** |
||
1118 | * Convert Celsius (Cº) To Rankine (ºR) |
||
1119 | * |
||
1120 | * @since 1.2.0 |
||
1121 | * |
||
1122 | * @param float $celsius Value in Celsius |
||
1123 | * @param bool $rounded Whether or not to round the result. |
||
1124 | * @param int $precision Precision to use if $rounded is true. |
||
1125 | * @return float |
||
1126 | */ |
||
1127 | 1 | public static function celsiusToRankine(float $celsius, bool $rounded = true, int $precision = 2): float |
|
1128 | { |
||
1129 | 1 | $result = ($celsius * 1.8) + 491.67; |
|
1130 | |||
1131 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1132 | } |
||
1133 | |||
1134 | /** |
||
1135 | * Convert Rankine (ºR) To Celsius (Cº) |
||
1136 | * |
||
1137 | * @since 1.2.0 |
||
1138 | * |
||
1139 | * @param float $rankine Value in Rankine |
||
1140 | * @param bool $rounded Whether or not to round the result. |
||
1141 | * @param int $precision Precision to use if $rounded is true. |
||
1142 | * @return float |
||
1143 | */ |
||
1144 | 1 | public static function rankineToCelsius(float $rankine, bool $rounded = true, int $precision = 2): float |
|
1145 | { |
||
1146 | 1 | $result = ($rankine - 491.67) / 1.8; |
|
1147 | |||
1148 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1149 | } |
||
1150 | |||
1151 | /** |
||
1152 | * Convert Kelvin (K) To Rankine (ºR) |
||
1153 | * |
||
1154 | * @since 1.2.0 |
||
1155 | * |
||
1156 | * @param float $kelvin Value in Kelvin |
||
1157 | * @param bool $rounded Whether or not to round the result. |
||
1158 | * @param int $precision Precision to use if $rounded is true. |
||
1159 | * @return float |
||
1160 | */ |
||
1161 | 1 | public static function kelvinToRankine(float $kelvin, bool $rounded = true, int $precision = 2): float |
|
1162 | { |
||
1163 | 1 | $result = (($kelvin - 273.15) * 1.8) + 491.67; |
|
1164 | |||
1165 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1166 | } |
||
1167 | |||
1168 | /** |
||
1169 | * Convert Rankine (ºR) To Kelvin (K) |
||
1170 | * |
||
1171 | * @since 1.2.0 |
||
1172 | * |
||
1173 | * @param float $rankine Value in Rankine |
||
1174 | * @param bool $rounded Whether or not to round the result. |
||
1175 | * @param int $precision Precision to use if $rounded is true. |
||
1176 | * @return float |
||
1177 | */ |
||
1178 | 1 | public static function rankineToKelvin(float $rankine, bool $rounded = true, int $precision = 2): float |
|
1179 | { |
||
1180 | 1 | $result = (($rankine - 491.67) / 1.8) + 273.15; |
|
1181 | |||
1182 | 1 | return ($rounded) ? round($result, $precision) : $result; |
|
1183 | } |
||
1184 | |||
1185 | /** |
||
1186 | * validEmail() |
||
1187 | * |
||
1188 | * Validate an email address using PHP's built-in filter. |
||
1189 | * |
||
1190 | * @param string $email Value to check. |
||
1191 | * @return bool |
||
1192 | */ |
||
1193 | 2 | public static function validEmail(string $email): bool |
|
1194 | { |
||
1195 | 2 | return (bool) filter_var($email, FILTER_VALIDATE_EMAIL); |
|
1196 | } |
||
1197 | |||
1198 | /** |
||
1199 | * validJson() |
||
1200 | * |
||
1201 | * Determines if a string is valid JSON. |
||
1202 | * |
||
1203 | * @param string $data The string to validate as JSON. |
||
1204 | * @return bool |
||
1205 | */ |
||
1206 | 1 | public static function validJson(string $data): bool |
|
1207 | { |
||
1208 | 1 | $data = trim($data); |
|
1209 | |||
1210 | 1 | json_decode($data); |
|
1211 | |||
1212 | 1 | return json_last_error() === JSON_ERROR_NONE; |
|
1213 | } |
||
1214 | |||
1215 | /** |
||
1216 | * sizeFormat() |
||
1217 | * |
||
1218 | * Format bytes to a human-readable format. |
||
1219 | * |
||
1220 | * @param int $bytes The number in bytes. |
||
1221 | * @param int $decimals How many decimal points to include. |
||
1222 | * @return string |
||
1223 | */ |
||
1224 | 1 | public static function sizeFormat(int $bytes, int $decimals = 0): string |
|
1225 | { |
||
1226 | 1 | static $pow; |
|
1227 | |||
1228 | // |
||
1229 | 1 | if (is_null($pow)) { |
|
1230 | 1 | $pow = [ |
|
1231 | 1 | 'kilo' => 1024, |
|
1232 | 1 | 'mega' => 1024 ** 2, |
|
1233 | 1 | 'giga' => 1024 ** 3, |
|
1234 | 1 | 'tera' => 1024 ** 4, |
|
1235 | 1 | 'peta' => 1024 ** 5, |
|
1236 | 1 | 'exa' => 1024 ** 6, |
|
1237 | 1 | 'zeta' => 1024 ** 7, |
|
1238 | 1 | 'yotta' => 1024 ** 8 |
|
1239 | 1 | ]; |
|
1240 | } |
||
1241 | |||
1242 | // |
||
1243 | 1 | $bytes = floatval($bytes); |
|
1244 | |||
1245 | 1 | return match (true) { |
|
1246 | 1 | $bytes >= $pow['yotta'] => number_format(($bytes / $pow['yotta']), $decimals, '.', '') . ' YiB', |
|
1247 | 1 | $bytes >= $pow['zeta'] => number_format(($bytes / $pow['zeta']), $decimals, '.', '') . ' ZiB', |
|
1248 | 1 | $bytes >= $pow['exa'] => number_format(($bytes / $pow['exa']), $decimals, '.', '') . ' EiB', |
|
1249 | 1 | $bytes >= $pow['peta'] => number_format(($bytes / $pow['peta']), $decimals, '.', '') . ' PiB', |
|
1250 | 1 | $bytes >= $pow['tera'] => number_format(($bytes / $pow['tera']), $decimals, '.', '') . ' TiB', |
|
1251 | 1 | $bytes >= $pow['giga'] => number_format(($bytes / $pow['giga']), $decimals, '.', '') . ' GiB', |
|
1252 | 1 | $bytes >= $pow['mega'] => number_format(($bytes / $pow['mega']), $decimals, '.', '') . ' MiB', |
|
1253 | 1 | $bytes >= $pow['kilo'] => number_format(($bytes / $pow['kilo']), $decimals, '.', '') . ' KiB', |
|
1254 | 1 | default => number_format($bytes, $decimals, '.', '') . ' B' |
|
1255 | 1 | }; |
|
1256 | } |
||
1257 | |||
1258 | /** |
||
1259 | * timeDifference() |
||
1260 | * |
||
1261 | * Formats the difference between two timestamps to be human-readable. |
||
1262 | * |
||
1263 | * @param int $timestampFrom Starting unix timestamp. |
||
1264 | * @param int $timestampTo Ending unix timestamp. |
||
1265 | * @param string $timezone The timezone to use. Must be a valid timezone: |
||
1266 | * {@see http://www.php.net/manual/en/timezones.php} |
||
1267 | * @param string $append The string to append to the difference. |
||
1268 | * @return string |
||
1269 | * |
||
1270 | * @throws InvalidArgumentException|Exception |
||
1271 | */ |
||
1272 | 1 | public static function timeDifference(int $timestampFrom, int $timestampTo = 0, string $timezone = 'UTC', string $append = ' old'): string |
|
1273 | { |
||
1274 | 1 | static $validTimezones; |
|
1275 | |||
1276 | 1 | if (!$validTimezones) { |
|
1277 | 1 | $validTimezones = DateTimeZone::listIdentifiers(); |
|
1278 | } |
||
1279 | |||
1280 | // Check to see if it is a valid timezone |
||
1281 | 1 | $timezone = ($timezone === '' ? 'UTC' : $timezone); |
|
1282 | |||
1283 | 1 | if (!in_array($timezone, $validTimezones, true)) { |
|
1284 | 1 | throw new InvalidArgumentException('$timezone appears to be invalid.'); |
|
1285 | } |
||
1286 | |||
1287 | // Cannot be zero |
||
1288 | 1 | if ($timestampTo <= 0) { |
|
1289 | 1 | $timestampTo = time(); |
|
1290 | } |
||
1291 | |||
1292 | 1 | if ($timestampFrom <= 0) { |
|
1293 | throw new InvalidArgumentException('$timestampFrom must be greater than 0.'); |
||
1294 | } |
||
1295 | |||
1296 | // Create \DateTime objects and set timezone |
||
1297 | 1 | $timestampFrom = new DateTime(date('Y-m-d H:i:s', $timestampFrom)); |
|
1298 | 1 | $timestampFrom->setTimezone(new DateTimeZone($timezone)); |
|
1299 | |||
1300 | 1 | $timestampTo = new DateTime(date('Y-m-d H:i:s', $timestampTo)); |
|
1301 | 1 | $timestampTo->setTimezone(new DateTimeZone($timezone)); |
|
1302 | |||
1303 | // Calculate difference |
||
1304 | 1 | $difference = $timestampFrom->diff($timestampTo); |
|
1305 | |||
1306 | 1 | $string = match (true) { |
|
1307 | 1 | $difference->y > 0 => $difference->y . ' year(s)', |
|
1308 | 1 | $difference->m > 0 => $difference->m . ' month(s)', |
|
1309 | 1 | $difference->d > 0 => ( |
|
1310 | 1 | $difference->d >= 7 |
|
1311 | 1 | ? ceil($difference->d / 7) . ' week(s)' |
|
1312 | 1 | : $difference->d . ' day(s)' |
|
1313 | 1 | ), |
|
1314 | 1 | $difference->h > 0 => $difference->h . ' hour(s)', |
|
1315 | 1 | $difference->i > 0 => $difference->i . ' minute(s)', |
|
1316 | 1 | $difference->s > 0 => $difference->s . ' second(s)', |
|
1317 | 1 | default => '' |
|
1318 | 1 | }; |
|
1319 | 1 | return $string . $append; |
|
1320 | } |
||
1321 | |||
1322 | /** |
||
1323 | * getIpAddress() |
||
1324 | * |
||
1325 | * Return the visitor's IP address. |
||
1326 | * |
||
1327 | * @param bool $trustProxy Whether to trust HTTP_CLIENT_IP and |
||
1328 | * HTTP_X_FORWARDED_FOR. |
||
1329 | * @return string |
||
1330 | */ |
||
1331 | 1 | public static function getIpAddress(bool $trustProxy = false): string |
|
1332 | { |
||
1333 | // Pretty self-explanatory. Try to get an 'accurate' IP |
||
1334 | 1 | if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) { |
|
1335 | 1 | $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP']; |
|
1336 | } |
||
1337 | |||
1338 | 1 | if (!$trustProxy) { |
|
1339 | /** @var string **/ |
||
1340 | 1 | return $_SERVER['REMOTE_ADDR']; |
|
1341 | } |
||
1342 | |||
1343 | 1 | $ip = ''; |
|
1344 | 1 | $ips = []; |
|
1345 | |||
1346 | 1 | if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { |
|
1347 | /** @var string $ips **/ |
||
1348 | 1 | $ips = $_SERVER['HTTP_X_FORWARDED_FOR']; |
|
1349 | 1 | $ips = explode(',', $ips); |
|
1350 | 1 | } elseif (isset($_SERVER['HTTP_X_REAL_IP'])) { |
|
1351 | /** @var string $ips **/ |
||
1352 | 1 | $ips = $_SERVER['HTTP_X_REAL_IP']; |
|
1353 | 1 | $ips = explode(',', $ips); |
|
1354 | } |
||
1355 | |||
1356 | /** @var array<mixed> $ips **/ |
||
1357 | 1 | $ips = static::arrayMapDeep($ips, 'trim'); |
|
1358 | |||
1359 | 1 | if (count($ips) > 0) { |
|
1360 | 1 | foreach ($ips as $val) { |
|
1361 | /** @phpstan-ignore-next-line */ |
||
1362 | 1 | if (inet_ntop(inet_pton($val)) === $val && static::isPublicIp($val)) { |
|
1363 | /** @var string $ip **/ |
||
1364 | 1 | $ip = $val; |
|
1365 | 1 | break; |
|
1366 | } |
||
1367 | } |
||
1368 | } |
||
1369 | 1 | unset($ips); |
|
1370 | |||
1371 | 1 | if ($ip === '') { |
|
1372 | /** @var string $ip **/ |
||
1373 | 1 | $ip = $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['REMOTE_ADDR']; |
|
1374 | } |
||
1375 | 1 | return $ip; |
|
1376 | } |
||
1377 | |||
1378 | /** |
||
1379 | * isPrivateIp() |
||
1380 | * |
||
1381 | * Determines if an IP address is within the private range. |
||
1382 | * |
||
1383 | * @param string $ipaddress IP address to check. |
||
1384 | * @return bool |
||
1385 | */ |
||
1386 | 3 | public static function isPrivateIp(string $ipaddress): bool |
|
1387 | { |
||
1388 | 3 | return !(bool) filter_var( |
|
1389 | 3 | $ipaddress, |
|
1390 | 3 | FILTER_VALIDATE_IP, |
|
1391 | 3 | FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE |
|
1392 | 3 | ); |
|
1393 | } |
||
1394 | |||
1395 | /** |
||
1396 | * isReservedIp() |
||
1397 | * |
||
1398 | * Determines if an IP address is within the reserved range. |
||
1399 | * |
||
1400 | * @param string $ipaddress IP address to check. |
||
1401 | * @return bool |
||
1402 | */ |
||
1403 | 3 | public static function isReservedIp(string $ipaddress): bool |
|
1409 | 3 | ); |
|
1410 | } |
||
1411 | |||
1412 | /** |
||
1413 | * isPublicIp() |
||
1414 | * |
||
1415 | * Determines if an IP address is not within the private or reserved ranges. |
||
1416 | * |
||
1417 | * @param string $ipaddress IP address to check. |
||
1418 | * @return bool |
||
1419 | */ |
||
1420 | 2 | public static function isPublicIp(string $ipaddress): bool |
|
1421 | { |
||
1422 | 2 | return (!static::isPrivateIp($ipaddress) && !static::isReservedIp($ipaddress)); |
|
1423 | } |
||
1424 | |||
1425 | /** |
||
1426 | * obscureEmail() |
||
1427 | * |
||
1428 | * Obscures an email address. |
||
1429 | * |
||
1430 | * @param string $email Email address to obscure. |
||
1431 | * @return string Obscured email address. |
||
1432 | * |
||
1433 | * @throws InvalidArgumentException |
||
1434 | */ |
||
1435 | 1 | public static function obscureEmail(string $email): string |
|
1436 | { |
||
1437 | // Sanity check |
||
1438 | 1 | if (!static::validEmail($email)) { |
|
1439 | 1 | throw new InvalidArgumentException('Invalid $email specified.'); |
|
1440 | } |
||
1441 | |||
1442 | // Split and process |
||
1443 | 1 | $email = array_map(function($char) { |
|
1444 | 1 | return '&#' . ord($char) . ';'; |
|
1445 | 1 | }, str_split($email)); |
|
1 ignored issue
–
show
|
|||
1446 | |||
1447 | 1 | return implode('', $email); |
|
1448 | } |
||
1449 | |||
1450 | /** |
||
1451 | * currentHost() |
||
1452 | * |
||
1453 | * Determines current hostname. |
||
1454 | * |
||
1455 | * @param bool $stripWww True to strip www. off the host, false to leave it be. |
||
1456 | * @param bool $acceptForwarded True to accept |
||
1457 | * @return string |
||
1458 | */ |
||
1459 | 2 | public static function currentHost(bool $stripWww = false, bool $acceptForwarded = false): string |
|
1460 | { |
||
1461 | /** @var string $host **/ |
||
1462 | 2 | $host = ( |
|
1463 | 2 | ($acceptForwarded && isset($_SERVER['HTTP_X_FORWARDED_HOST'])) ? |
|
1464 | 1 | $_SERVER['HTTP_X_FORWARDED_HOST'] : |
|
1465 | 2 | ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '') |
|
1466 | 2 | ); |
|
1467 | 2 | $host = trim(strval($host)); |
|
1468 | |||
1469 | 2 | if ($host === '' || preg_match('#^\[?(?:[a-z0-9-:\]_]+\.?)+$#', $host) === 0) { |
|
1470 | 1 | $host = 'localhost'; |
|
1471 | } |
||
1472 | |||
1473 | 2 | $host = static::lower($host); |
|
1474 | |||
1475 | // Strip 'www.' |
||
1476 | 2 | if ($stripWww) { |
|
1477 | 1 | $strippedHost = preg_replace('#^www\.#', '', $host); |
|
1478 | } |
||
1479 | 2 | return ($strippedHost ?? $host); |
|
1480 | } |
||
1481 | |||
1482 | /** |
||
1483 | * serverHttpVars() |
||
1484 | * |
||
1485 | * Builds an array of headers based on HTTP_* keys within $_SERVER. |
||
1486 | * |
||
1487 | * @param bool $asLowerCase |
||
1488 | * @return array<mixed> |
||
1489 | */ |
||
1490 | 3 | public static function serverHttpVars(bool $asLowerCase = false): array |
|
1491 | { |
||
1492 | 3 | $headers = []; |
|
1493 | |||
1494 | 3 | if (static::doesNotContain(PHP_SAPI, 'cli')) { |
|
1495 | /** @var array<mixed> $keys **/ |
||
1496 | $keys = static::arrayMapDeep(array_keys($_SERVER), [static::class, 'lower']); |
||
1497 | $keys = array_filter($keys, function($key) { |
||
1498 | /** @var string $key **/ |
||
1499 | return (static::beginsWith($key, 'http_')); |
||
1500 | }); |
||
1501 | |||
1502 | if (count($keys) > 0) { |
||
1503 | foreach ($keys as $key) { |
||
1504 | /** @var string $key **/ |
||
1505 | $headers[strtr( |
||
1506 | ucwords(strtr(static::substr($key, 5), '_', ' ')), |
||
1507 | ' ', |
||
1508 | '-' |
||
1509 | )] = &$_SERVER[($asLowerCase ? $key : static::upper($key))]; |
||
1510 | } |
||
1511 | } |
||
1512 | unset($keys); |
||
1513 | } |
||
1514 | 3 | return $headers; |
|
1515 | } |
||
1516 | |||
1517 | /** |
||
1518 | * isHttps() |
||
1519 | * |
||
1520 | * Checks to see if SSL is in use. |
||
1521 | * |
||
1522 | * @return bool |
||
1523 | */ |
||
1524 | 2 | public static function isHttps(): bool |
|
1525 | { |
||
1526 | 2 | $headers = static::serverHttpVars(true); |
|
1527 | |||
1528 | // Generally, as long as HTTPS is not set or is any empty value, it is considered to be "off" |
||
1529 | if ( |
||
1530 | 2 | (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== '' && $_SERVER['HTTPS'] !== 'off') |
|
1531 | 2 | || (isset($headers['X-Forwarded-Proto']) && $headers['X-Forwarded-Proto'] === 'https') |
|
1532 | 2 | || (isset($headers['Front-End-Https']) && $headers['Front-End-Https'] !== 'off') |
|
1533 | ) { |
||
1534 | 2 | return true; |
|
1535 | } |
||
1536 | 2 | return false; |
|
1537 | } |
||
1538 | |||
1539 | |||
1540 | /** |
||
1541 | * currentUrl() |
||
1542 | * |
||
1543 | * Retrieve the current URL. |
||
1544 | * |
||
1545 | * @param bool $parse True to return the url as an array, false otherwise. |
||
1546 | * @return mixed |
||
1547 | */ |
||
1548 | 1 | public static function currentUrl(bool $parse = false): mixed |
|
1549 | { |
||
1550 | // Scheme |
||
1551 | 1 | $scheme = (static::isHttps()) ? 'https://' : 'http://'; |
|
1552 | |||
1553 | // Auth |
||
1554 | 1 | $auth = ''; |
|
1555 | |||
1556 | 1 | if (isset($_SERVER['PHP_AUTH_USER'])) { |
|
1557 | 1 | $auth = $_SERVER['PHP_AUTH_USER'] . ( |
|
1558 | 1 | isset($_SERVER['PHP_AUTH_PW']) ? ':' . $_SERVER['PHP_AUTH_PW'] : '' |
|
1559 | 1 | ) . '@'; |
|
1560 | } |
||
1561 | |||
1562 | // Host and port |
||
1563 | 1 | $host = static::currentHost(); |
|
1564 | |||
1565 | /** @var int $port **/ |
||
1566 | 1 | $port = $_SERVER['SERVER_PORT'] ?? 0; |
|
1567 | 1 | $port = ($port === (static::isHttps() ? 443 : 80)) ? 0 : $port; |
|
1568 | |||
1569 | // Path |
||
1570 | /** @var string $self **/ |
||
1571 | 1 | $self = $_SERVER['PHP_SELF']; |
|
1572 | /** @var string $query **/ |
||
1573 | 1 | $query = $_SERVER['QUERY_STRING'] ?? ''; |
|
1574 | /** @var string $request **/ |
||
1575 | 1 | $request = $_SERVER['REQUEST_URI'] ?? ''; |
|
1576 | /** @var string $path **/ |
||
1577 | 1 | $path = ($request === '' ? $self . ($query !== '' ? '?' . $query : '') : $request); |
|
1578 | |||
1579 | // Put it all together |
||
1580 | /** @var string $url **/ |
||
1581 | 1 | $url = sprintf('%s%s%s%s%s', $scheme, $auth, $host, ($port > 0 ? ":$port" : ''), $path); |
|
1582 | |||
1583 | // If $parse is true, parse into array |
||
1584 | 1 | return ($parse ? parse_url($url) : $url); |
|
1585 | } |
||
1586 | |||
1587 | /** |
||
1588 | * ordinal() |
||
1589 | * |
||
1590 | * Retrieve the ordinal version of a number. |
||
1591 | * |
||
1592 | * Basically, it will append th, st, nd, or rd based on what the number ends with. |
||
1593 | * |
||
1594 | * @param int $number The number to create an ordinal version of. |
||
1595 | * @return string |
||
1596 | */ |
||
1597 | 1 | public static function ordinal(int $number): string |
|
1609 | } |
||
1610 | |||
1611 | /** |
||
1612 | * statusHeader() |
||
1613 | * |
||
1614 | * Send an HTTP status header. |
||
1615 | * |
||
1616 | * @param int $code The status code. |
||
1617 | * @param string $message Custom status message. |
||
1618 | * @param bool $replace True if the header should replace a previous similar header. |
||
1619 | * False to add a second header of the same type |
||
1620 | * |
||
1621 | * @throws Exception|InvalidArgumentException|RuntimeException |
||
1622 | * |
||
1623 | * @deprecated 1.2.0 Use \http_response_code() instead |
||
1624 | */ |
||
1625 | 1 | public static function statusHeader(int $code = 200, string $message = '', bool $replace = true): void |
|
1626 | { |
||
1627 | 1 | static $statusCodes; |
|
1628 | |||
1629 | 1 | @trigger_error('Utility function "statusHeader()" is deprecated since version 1.2.0. Use \http_response_code() instead', \E_USER_DEPRECATED); |
|
1630 | |||
1631 | 1 | if (!$statusCodes) { |
|
1632 | 1 | $statusCodes = [ |
|
1633 | 1 | 100 => 'Continue', |
|
1634 | 1 | 101 => 'Switching Protocols', |
|
1635 | 1 | 200 => 'OK', |
|
1636 | 1 | 201 => 'Created', |
|
1637 | 1 | 202 => 'Accepted', |
|
1638 | 1 | 203 => 'Non-Authoritative Information', |
|
1639 | 1 | 204 => 'No Content', |
|
1640 | 1 | 205 => 'Reset Content', |
|
1641 | 1 | 206 => 'Partial Content', |
|
1642 | 1 | 300 => 'Multiple Choices', |
|
1643 | 1 | 301 => 'Moved Permanently', |
|
1644 | 1 | 302 => 'Found', |
|
1645 | 1 | 303 => 'See Other', |
|
1646 | 1 | 304 => 'Not Modified', |
|
1647 | 1 | 305 => 'Use Proxy', |
|
1648 | 1 | 307 => 'Temporary Redirect', |
|
1649 | 1 | 400 => 'Bad Request', |
|
1650 | 1 | 401 => 'Unauthorized', |
|
1651 | 1 | 402 => 'Payment Required', |
|
1652 | 1 | 403 => 'Forbidden', |
|
1653 | 1 | 404 => 'Not Found', |
|
1654 | 1 | 405 => 'Method Not Allowed', |
|
1655 | 1 | 406 => 'Not Acceptable', |
|
1656 | 1 | 407 => 'Proxy Authentication Required', |
|
1657 | 1 | 408 => 'Request Timeout', |
|
1658 | 1 | 409 => 'Conflict', |
|
1659 | 1 | 410 => 'Gone', |
|
1660 | 1 | 411 => 'Length Required', |
|
1661 | 1 | 412 => 'Precondition Failed', |
|
1662 | 1 | 413 => 'Request Entity Too Large', |
|
1663 | 1 | 414 => 'Request-URI Too Long', |
|
1664 | 1 | 415 => 'Unsupported Media Type', |
|
1665 | 1 | 416 => 'Requested Range Not Satisfiable', |
|
1666 | 1 | 417 => 'Expectation Failed', |
|
1667 | 1 | 422 => 'Unprocessable Entity', |
|
1668 | 1 | 500 => 'Internal Server Error', |
|
1669 | 1 | 501 => 'Not Implemented', |
|
1670 | 1 | 502 => 'Bad Gateway', |
|
1671 | 1 | 503 => 'Service Unavailable', |
|
1672 | 1 | 504 => 'Gateway Timeout', |
|
1673 | 1 | 505 => 'HTTP Version Not Supported' |
|
1674 | 1 | ]; |
|
1675 | } |
||
1676 | |||
1677 | // Sanity check |
||
1678 | 1 | if ($code < 0 || $code > 505) { |
|
1679 | 1 | throw new InvalidArgumentException('$code is invalid.'); |
|
1680 | } |
||
1681 | |||
1682 | 1 | if ($message === '') { |
|
1683 | 1 | if (!isset($statusCodes[$code])) { |
|
1684 | throw new Exception('No status message available. Please double check your $code or provide a custom $message.'); |
||
1685 | } |
||
1686 | 1 | $message = $statusCodes[$code]; |
|
1687 | } |
||
1688 | |||
1689 | 1 | if (headers_sent($line, $file)) { |
|
1690 | throw new RuntimeException(sprintf('Failed to send header. Headers have already been sent by "%s" at line %d.', $file, $line)); |
||
1691 | } |
||
1692 | |||
1693 | // Properly format and send header, based on server API |
||
1694 | 1 | if (static::doesContain(PHP_SAPI, 'cgi')) { |
|
1695 | header("Status: $code $message", $replace); |
||
1696 | } else { |
||
1697 | 1 | header( |
|
1698 | 1 | ($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1') . " $code $message", |
|
1699 | 1 | $replace, |
|
1700 | 1 | $code |
|
1701 | 1 | ); |
|
1702 | } |
||
1703 | } |
||
1704 | |||
1705 | /** |
||
1706 | * guid() |
||
1707 | * |
||
1708 | * Generate a Globally/Universally Unique Identifier (version 4). |
||
1709 | * |
||
1710 | * @return string |
||
1711 | * @throws \Random\RandomException |
||
1712 | */ |
||
1713 | 1 | public static function guid(): string |
|
1714 | { |
||
1715 | 1 | static $format = '%04x%04x-%04x-%04x-%04x-%04x%04x%04x'; |
|
1716 | |||
1717 | try { |
||
1718 | 1 | $guid = sprintf( |
|
1719 | 1 | $format, |
|
1720 | 1 | static::randomInt(0, 0xffff), |
|
1721 | 1 | static::randomInt(0, 0xffff), |
|
1722 | 1 | static::randomInt(0, 0xffff), |
|
1723 | 1 | static::randomInt(0, 0x0fff) | 0x4000, |
|
1724 | 1 | static::randomInt(0, 0x3fff) | 0x8000, |
|
1725 | 1 | static::randomInt(0, 0xffff), |
|
1726 | 1 | static::randomInt(0, 0xffff), |
|
1727 | 1 | static::randomInt(0, 0xffff) |
|
1728 | 1 | ); |
|
1729 | } catch (\Random\RandomException $e) { |
||
1730 | throw new \Random\RandomException('Unable to generate GUID: ' . $e->getMessage(), 0, $e); |
||
1731 | } |
||
1732 | 1 | return $guid; |
|
1733 | } |
||
1734 | |||
1735 | /** |
||
1736 | * timezoneInfo() |
||
1737 | * |
||
1738 | * Retrieves information about a timezone. |
||
1739 | * |
||
1740 | * Note: Must be a valid timezone recognized by PHP. |
||
1741 | * |
||
1742 | * @see http://www.php.net/manual/en/timezones.php |
||
1743 | * |
||
1744 | * @param string $timezone The timezone to return information for. |
||
1745 | * @return array<mixed> |
||
1746 | * |
||
1747 | * @throws InvalidArgumentException|Exception |
||
1748 | */ |
||
1749 | 1 | public static function timezoneInfo(string $timezone): array |
|
1750 | { |
||
1751 | 1 | static $validTimezones; |
|
1752 | |||
1753 | 1 | if (!$validTimezones) { |
|
1754 | 1 | $validTimezones = DateTimeZone::listIdentifiers(); |
|
1755 | } |
||
1756 | |||
1757 | // Check to see if it is a valid timezone |
||
1758 | 1 | $timezone = ($timezone === '' ? 'UTC' : $timezone); |
|
1759 | |||
1760 | 1 | if (!in_array($timezone, $validTimezones, true)) { |
|
1761 | throw new InvalidArgumentException('$timezone appears to be invalid.'); |
||
1762 | } |
||
1763 | |||
1764 | try { |
||
1765 | 1 | $tz = new DateTimeZone($timezone); |
|
1766 | } catch (Exception $e) { |
||
1767 | throw new InvalidArgumentException($e->getMessage(), 0, $e); |
||
1768 | } |
||
1769 | |||
1770 | 1 | $location = $tz->getLocation(); |
|
1771 | |||
1772 | 1 | if ($location === false) { |
|
1773 | $location = [ |
||
1774 | 'country_code' => 'N/A', |
||
1775 | 'latitude' => 'N/A', |
||
1776 | 'longitude' => 'N/A' |
||
1777 | ]; |
||
1778 | } |
||
1779 | |||
1780 | 1 | $info = [ |
|
1781 | 1 | 'offset' => $tz->getOffset(new DateTime('now', new DateTimeZone('GMT'))) / 3600, |
|
1782 | 1 | 'country' => $location['country_code'], |
|
1783 | 1 | 'latitude' => $location['latitude'], |
|
1784 | 1 | 'longitude' => $location['longitude'], |
|
1785 | 1 | 'dst' => $tz->getTransitions($now = time(), $now)[0]['isdst'] |
|
1786 | 1 | ]; |
|
1787 | 1 | unset($tz); |
|
1788 | |||
1789 | 1 | return $info; |
|
1790 | } |
||
1791 | |||
1792 | /** |
||
1793 | * iniGet() |
||
1794 | * |
||
1795 | * Safe ini_get taking into account its availability. |
||
1796 | * |
||
1797 | * @param string $option The configuration option name. |
||
1798 | * @param bool $standardize Standardize returned values to 1 or 0? |
||
1799 | * @return string|false |
||
1800 | * |
||
1801 | * @throws RuntimeException|InvalidArgumentException |
||
1802 | */ |
||
1803 | 3 | public static function iniGet(string $option, bool $standardize = false): string | false |
|
1804 | { |
||
1805 | 3 | if (!function_exists('\\ini_get')) { |
|
1806 | // disabled_functions? |
||
1807 | throw new RuntimeException('Native ini_get function not available.'); |
||
1808 | } |
||
1809 | |||
1810 | 3 | if ($option === '') { |
|
1811 | 1 | throw new InvalidArgumentException('$option must not be empty.'); |
|
1812 | } |
||
1813 | |||
1814 | 3 | $value = ini_get($option); |
|
1815 | |||
1816 | 3 | if ($value === false) { |
|
1817 | throw new RuntimeException('$option does not exist.'); |
||
1818 | } |
||
1819 | |||
1820 | 3 | $value = trim($value); |
|
1821 | |||
1822 | 3 | if ($standardize) { |
|
1823 | 1 | $value = match (static::lower($option)) { |
|
1824 | 1 | 'yes', 'on', 'true', '1' => '1', |
|
1825 | 1 | 'no', 'off', 'false', '0' => '0', |
|
1826 | 1 | default => $value |
|
1827 | 1 | }; |
|
1828 | } |
||
1829 | 3 | return $value; |
|
1830 | } |
||
1831 | |||
1832 | /** |
||
1833 | * iniSet() |
||
1834 | * |
||
1835 | * Safe ini_set taking into account its availability. |
||
1836 | * |
||
1837 | * @param string $option The configuration option name. |
||
1838 | * @param string $value The new value for the option. |
||
1839 | * @return string|false |
||
1840 | * |
||
1841 | * @throws RuntimeException|InvalidArgumentException |
||
1842 | */ |
||
1843 | 2 | public static function iniSet(string $option, string $value): string | false |
|
1854 | } |
||
1855 | } |
||
1856 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths