1<?php
2/**
3 * WordPress implementation for PHP functions either missing from older PHP versions or not included by default.
4 *
5 * This file is loaded extremely early and the functions can be relied upon by drop-ins.
6 * Ergo, please ensure you do not rely on external functions when writing code for this file.
7 * Only use functions built into PHP or are defined in this file and have adequate testing
8 * and error suppression to ensure the file will run correctly and not break websites.
9 *
10 * @package PHP
11 * @access private
12 */
13
14// If gettext isn't available.
15if ( ! function_exists( '_' ) ) {
16 /**
17 * Compat function to mimic _(), an alias of gettext().
18 *
19 * @since 0.71
20 *
21 * @see https://php.net/manual/en/function.gettext.php
22 *
23 * @param string $message The message being translated.
24 * @return string
25 */
26 function _( $message ) {
27 return $message;
28 }
29}
30
31/**
32 * Returns whether PCRE/u (PCRE_UTF8 modifier) is available for use.
33 *
34 * @ignore
35 * @since 4.2.2
36 * @since 6.9.0 Deprecated the `$set` argument.
37 * @access private
38 *
39 * @param bool $set Deprecated. This argument is no longer used for testing purposes.
40 */
41function _wp_can_use_pcre_u( $set = null ) {
42 static $utf8_pcre = null;
43
44 if ( isset( $set ) ) {
45 _deprecated_argument( __FUNCTION__, '6.9.0' );
46 }
47
48 if ( isset( $utf8_pcre ) ) {
49 return $utf8_pcre;
50 }
51
52 $utf8_pcre = true;
53 set_error_handler(
54 function ( $errno, $errstr ) use ( &$utf8_pcre ) {
55 if ( str_starts_with( $errstr, 'preg_match():' ) ) {
56 $utf8_pcre = false;
57 return true;
58 }
59
60 return false;
61 },
62 E_WARNING
63 );
64
65 /*
66 * Attempt to compile a PCRE pattern with the PCRE_UTF8 flag. For
67 * systems lacking Unicode support this will trigger a warning
68 * during compilation, which the error handler will intercept.
69 */
70 preg_match( '//u', '' );
71 restore_error_handler();
72
73 return $utf8_pcre;
74}
75
76/**
77 * Indicates if a given slug for a character set represents the UTF-8 text encoding.
78 *
79 * A charset is considered to represent UTF-8 if it is a case-insensitive match
80 * of "UTF-8" with or without the hyphen.
81 *
82 * Example:
83 *
84 * true === _is_utf8_charset( 'UTF-8' );
85 * true === _is_utf8_charset( 'utf8' );
86 * false === _is_utf8_charset( 'latin1' );
87 * false === _is_utf8_charset( 'UTF 8' );
88 *
89 * // Only strings match.
90 * false === _is_utf8_charset( [ 'charset' => 'utf-8' ] );
91 *
92 * `is_utf8_charset` should be used outside of this file.
93 *
94 * @ignore
95 * @since 6.6.1
96 *
97 * @param string $charset_slug Slug representing a text character encoding, or "charset".
98 * E.g. "UTF-8", "Windows-1252", "ISO-8859-1", "SJIS".
99 *
100 * @return bool Whether the slug represents the UTF-8 encoding.
101 */
102function _is_utf8_charset( $charset_slug ) {
103 if ( ! is_string( $charset_slug ) ) {
104 return false;
105 }
106
107 return (
108 0 === strcasecmp( 'UTF-8', $charset_slug ) ||
109 0 === strcasecmp( 'UTF8', $charset_slug )
110 );
111}
112
113if ( ! function_exists( 'mb_substr' ) ) :
114 /**
115 * Compat function to mimic mb_substr().
116 *
117 * @ignore
118 * @since 3.2.0
119 *
120 * @see _mb_substr()
121 *
122 * @param string $string The string to extract the substring from.
123 * @param int $start Position to being extraction from in `$string`.
124 * @param int|null $length Optional. Maximum number of characters to extract from `$string`.
125 * Default null.
126 * @param string|null $encoding Optional. Character encoding to use. Default null.
127 * @return string Extracted substring.
128 */
129 function mb_substr( $string, $start, $length = null, $encoding = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound
130 return _mb_substr( $string, $start, $length, $encoding );
131 }
132endif;
133
134/**
135 * Internal compat function to mimic mb_substr().
136 *
137 * Only supports UTF-8 and non-shifting single-byte encodings. For all other encodings
138 * expect the substrings to be misaligned. When the given encoding (or the `blog_charset`
139 * if none is provided) isn’t UTF-8 then the function returns the output of {@see \substr()}.
140 *
141 * @ignore
142 * @since 3.2.0
143 *
144 * @param string $str The string to extract the substring from.
145 * @param int $start Character offset at which to start the substring extraction.
146 * @param int|null $length Optional. Maximum number of characters to extract from `$str`.
147 * Default null.
148 * @param string|null $encoding Optional. Character encoding to use. Default null.
149 * @return string Extracted substring.
150 */
151function _mb_substr( $str, $start, $length = null, $encoding = null ) {
152 if ( null === $str ) {
153 return '';
154 }
155
156 // The solution below works only for UTF-8; treat all other encodings as byte streams.
157 if ( ! _is_utf8_charset( $encoding ?? get_option( 'blog_charset' ) ) ) {
158 return is_null( $length ) ? substr( $str, $start ) : substr( $str, $start, $length );
159 }
160
161 $total_length = ( $start < 0 || $length < 0 )
162 ? _wp_utf8_codepoint_count( $str )
163 : 0;
164
165 $normalized_start = $start < 0
166 ? max( 0, $total_length + $start )
167 : $start;
168
169 /*
170 * The starting offset is provided as characters, which means this needs to
171 * find how many bytes that many characters occupies at the start of the string.
172 */
173 $starting_byte_offset = _wp_utf8_codepoint_span( $str, 0, $normalized_start );
174
175 $normalized_length = $length < 0
176 ? max( 0, $total_length - $normalized_start + $length )
177 : $length;
178
179 /*
180 * This is the main step. It finds how many bytes the given length of code points
181 * occupies in the input, starting at the byte offset calculated above.
182 */
183 $byte_length = isset( $normalized_length )
184 ? _wp_utf8_codepoint_span( $str, $starting_byte_offset, $normalized_length )
185 : ( strlen( $str ) - $starting_byte_offset );
186
187 // The result is a normal byte-level substring using the computed ranges.
188 return substr( $str, $starting_byte_offset, $byte_length );
189}
190
191if ( ! function_exists( 'mb_strlen' ) ) :
192 /**
193 * Compat function to mimic mb_strlen().
194 *
195 * @ignore
196 * @since 4.2.0
197 *
198 * @see _mb_strlen()
199 *
200 * @param string $string The string to retrieve the character length from.
201 * @param string|null $encoding Optional. Character encoding to use. Default null.
202 * @return int String length of `$string`.
203 */
204 function mb_strlen( $string, $encoding = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound
205 return _mb_strlen( $string, $encoding );
206 }
207endif;
208
209/**
210 * Internal compat function to mimic mb_strlen().
211 *
212 * Only supports UTF-8 and non-shifting single-byte encodings. For all other
213 * encodings expect the counts to be wrong. When the given encoding (or the
214 * `blog_charset` if none is provided) isn’t UTF-8 then the function returns
215 * the byte-count of the provided string.
216 *
217 * @ignore
218 * @since 4.2.0
219 *
220 * @param string $str The string to retrieve the character length from.
221 * @param string|null $encoding Optional. Count characters according to this encoding.
222 * Default is to consult `blog_charset`.
223 * @return int Count of code points if UTF-8, byte length otherwise.
224 */
225function _mb_strlen( $str, $encoding = null ) {
226 return _is_utf8_charset( $encoding ?? get_option( 'blog_charset' ) )
227 ? _wp_utf8_codepoint_count( $str )
228 : strlen( $str );
229}
230
231if ( ! function_exists( 'utf8_encode' ) ) :
232 if ( extension_loaded( 'mbstring' ) ) :
233 /**
234 * Converts a string from ISO-8859-1 to UTF-8.
235 *
236 * @deprecated Use {@see \mb_convert_encoding()} instead.
237 *
238 * @since 6.9.0
239 *
240 * @param string $iso_8859_1_text Text treated as ISO-8859-1 (latin1) bytes.
241 * @return string Text converted into a UTF-8.
242 */
243 function utf8_encode( $iso_8859_1_text ): string {
244 _deprecated_function( __FUNCTION__, '6.9.0', 'mb_convert_encoding' );
245
246 return mb_convert_encoding( $iso_8859_1_text, 'UTF-8', 'ISO-8859-1' );
247 }
248
249 else :
250 /**
251 * @ignore
252 * @private
253 *
254 * @since 6.9.0
255 */
256 function utf8_encode( $iso_8859_1_text ): string {
257 _deprecated_function( __FUNCTION__, '6.9.0', 'mb_convert_encoding' );
258
259 return _wp_utf8_encode_fallback( $iso_8859_1_text );
260 }
261
262 endif;
263endif;
264
265if ( ! function_exists( 'utf8_decode' ) ) :
266 if ( extension_loaded( 'mbstring' ) ) :
267 /**
268 * Converts a string from UTF-8 to ISO-8859-1.
269 *
270 * @deprecated Use {@see \mb_convert_encoding()} instead.
271 *
272 * @since 6.9.0
273 *
274 * @param string $utf8_text Text treated as UTF-8.
275 * @return string Text converted into ISO-8859-1.
276 */
277 function utf8_decode( $utf8_text ): string {
278 _deprecated_function( __FUNCTION__, '6.9.0', 'mb_convert_encoding' );
279
280 return mb_convert_encoding( $utf8_text, 'ISO-8859-1', 'UTF-8' );
281 }
282
283 else :
284 /**
285 * @ignore
286 * @private
287 *
288 * @since 6.9.0
289 */
290 function utf8_decode( $utf8_text ): string {
291 _deprecated_function( __FUNCTION__, '6.9.0', 'mb_convert_encoding' );
292
293 return _wp_utf8_decode_fallback( $utf8_text );
294 }
295
296 endif;
297endif;
298
299// sodium_crypto_box() was introduced in PHP 7.2.
300if ( ! function_exists( 'sodium_crypto_box' ) ) {
301 require ABSPATH . WPINC . '/sodium_compat/autoload.php';
302}
303
304if ( ! function_exists( 'is_countable' ) ) {
305 /**
306 * Polyfill for is_countable() function added in PHP 7.3.
307 *
308 * Verify that the content of a variable is an array or an object
309 * implementing the Countable interface.
310 *
311 * @since 4.9.6
312 *
313 * @param mixed $value The value to check.
314 * @return bool True if `$value` is countable, false otherwise.
315 */
316 function is_countable( $value ) {
317 return ( is_array( $value )
318 || $value instanceof Countable
319 || $value instanceof SimpleXMLElement
320 || $value instanceof ResourceBundle
321 );
322 }
323}
324
325if ( ! function_exists( 'array_key_first' ) ) {
326 /**
327 * Polyfill for array_key_first() function added in PHP 7.3.
328 *
329 * Get the first key of the given array without affecting
330 * the internal array pointer.
331 *
332 * @since 5.9.0
333 *
334 * @param array $array An array.
335 * @return string|int|null The first key of array if the array
336 * is not empty; `null` otherwise.
337 */
338 function array_key_first( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
339 if ( empty( $array ) ) {
340 return null;
341 }
342
343 foreach ( $array as $key => $value ) {
344 return $key;
345 }
346 }
347}
348
349if ( ! function_exists( 'array_key_last' ) ) {
350 /**
351 * Polyfill for `array_key_last()` function added in PHP 7.3.
352 *
353 * Get the last key of the given array without affecting the
354 * internal array pointer.
355 *
356 * @since 5.9.0
357 *
358 * @param array $array An array.
359 * @return string|int|null The last key of array if the array
360 *. is not empty; `null` otherwise.
361 */
362 function array_key_last( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
363 if ( empty( $array ) ) {
364 return null;
365 }
366
367 end( $array );
368
369 return key( $array );
370 }
371}
372
373if ( ! function_exists( 'array_is_list' ) ) {
374 /**
375 * Polyfill for `array_is_list()` function added in PHP 8.1.
376 *
377 * Determines if the given array is a list.
378 *
379 * An array is considered a list if its keys consist of consecutive numbers from 0 to count($array)-1.
380 *
381 * @see https://github.com/symfony/polyfill-php81/tree/main
382 *
383 * @since 6.5.0
384 *
385 * @param array<mixed> $arr The array being evaluated.
386 * @return bool True if array is a list, false otherwise.
387 */
388 function array_is_list( $arr ) {
389 if ( ( array() === $arr ) || ( array_values( $arr ) === $arr ) ) {
390 return true;
391 }
392
393 $next_key = -1;
394
395 foreach ( $arr as $k => $v ) {
396 if ( ++$next_key !== $k ) {
397 return false;
398 }
399 }
400
401 return true;
402 }
403}
404
405if ( ! function_exists( 'str_contains' ) ) {
406 /**
407 * Polyfill for `str_contains()` function added in PHP 8.0.
408 *
409 * Performs a case-sensitive check indicating if needle is
410 * contained in haystack.
411 *
412 * @since 5.9.0
413 *
414 * @param string $haystack The string to search in.
415 * @param string $needle The substring to search for in the `$haystack`.
416 * @return bool True if `$needle` is in `$haystack`, otherwise false.
417 */
418 function str_contains( $haystack, $needle ) {
419 if ( '' === $needle ) {
420 return true;
421 }
422
423 return false !== strpos( $haystack, $needle );
424 }
425}
426
427if ( ! function_exists( 'str_starts_with' ) ) {
428 /**
429 * Polyfill for `str_starts_with()` function added in PHP 8.0.
430 *
431 * Performs a case-sensitive check indicating if
432 * the haystack begins with needle.
433 *
434 * @since 5.9.0
435 *
436 * @param string $haystack The string to search in.
437 * @param string $needle The substring to search for in the `$haystack`.
438 * @return bool True if `$haystack` starts with `$needle`, otherwise false.
439 */
440 function str_starts_with( $haystack, $needle ) {
441 if ( '' === $needle ) {
442 return true;
443 }
444
445 return 0 === strpos( $haystack, $needle );
446 }
447}
448
449if ( ! function_exists( 'str_ends_with' ) ) {
450 /**
451 * Polyfill for `str_ends_with()` function added in PHP 8.0.
452 *
453 * Performs a case-sensitive check indicating if
454 * the haystack ends with needle.
455 *
456 * @since 5.9.0
457 *
458 * @param string $haystack The string to search in.
459 * @param string $needle The substring to search for in the `$haystack`.
460 * @return bool True if `$haystack` ends with `$needle`, otherwise false.
461 */
462 function str_ends_with( $haystack, $needle ) {
463 if ( '' === $haystack ) {
464 return '' === $needle;
465 }
466
467 $len = strlen( $needle );
468
469 return substr( $haystack, -$len, $len ) === $needle;
470 }
471}
472
473if ( ! function_exists( 'array_find' ) ) {
474 /**
475 * Polyfill for `array_find()` function added in PHP 8.4.
476 *
477 * Searches an array for the first element that passes a given callback.
478 *
479 * @since 6.8.0
480 *
481 * @param array $array The array to search.
482 * @param callable $callback The callback to run for each element.
483 * @return mixed|null The first element in the array that passes the `$callback`, otherwise null.
484 */
485 function array_find( array $array, callable $callback ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
486 foreach ( $array as $key => $value ) {
487 if ( $callback( $value, $key ) ) {
488 return $value;
489 }
490 }
491
492 return null;
493 }
494}
495
496if ( ! function_exists( 'array_find_key' ) ) {
497 /**
498 * Polyfill for `array_find_key()` function added in PHP 8.4.
499 *
500 * Searches an array for the first key that passes a given callback.
501 *
502 * @since 6.8.0
503 *
504 * @param array $array The array to search.
505 * @param callable $callback The callback to run for each element.
506 * @return int|string|null The first key in the array that passes the `$callback`, otherwise null.
507 */
508 function array_find_key( array $array, callable $callback ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
509 foreach ( $array as $key => $value ) {
510 if ( $callback( $value, $key ) ) {
511 return $key;
512 }
513 }
514
515 return null;
516 }
517}
518
519if ( ! function_exists( 'array_any' ) ) {
520 /**
521 * Polyfill for `array_any()` function added in PHP 8.4.
522 *
523 * Checks if any element of an array passes a given callback.
524 *
525 * @since 6.8.0
526 *
527 * @param array $array The array to check.
528 * @param callable $callback The callback to run for each element.
529 * @return bool True if any element in the array passes the `$callback`, otherwise false.
530 */
531 function array_any( array $array, callable $callback ): bool { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
532 foreach ( $array as $key => $value ) {
533 if ( $callback( $value, $key ) ) {
534 return true;
535 }
536 }
537
538 return false;
539 }
540}
541
542if ( ! function_exists( 'array_all' ) ) {
543 /**
544 * Polyfill for `array_all()` function added in PHP 8.4.
545 *
546 * Checks if all elements of an array pass a given callback.
547 *
548 * @since 6.8.0
549 *
550 * @param array $array The array to check.
551 * @param callable $callback The callback to run for each element.
552 * @return bool True if all elements in the array pass the `$callback`, otherwise false.
553 */
554 function array_all( array $array, callable $callback ): bool { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
555 foreach ( $array as $key => $value ) {
556 if ( ! $callback( $value, $key ) ) {
557 return false;
558 }
559 }
560
561 return true;
562 }
563}
564
565if ( ! function_exists( 'array_first' ) ) {
566 /**
567 * Polyfill for `array_first()` function added in PHP 8.5.
568 *
569 * Returns the first element of an array.
570 *
571 * @since 6.9.0
572 *
573 * @param array $array The array to get the first element from.
574 * @return mixed|null The first element of the array, or null if the array is empty.
575 */
576 function array_first( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
577 if ( empty( $array ) ) {
578 return null;
579 }
580
581 foreach ( $array as $value ) {
582 return $value;
583 }
584 }
585}
586
587if ( ! function_exists( 'array_last' ) ) {
588 /**
589 * Polyfill for `array_last()` function added in PHP 8.5.
590 *
591 * Returns the last element of an array.
592 *
593 * @since 6.9.0
594 *
595 * @param array $array The array to get the last element from.
596 * @return mixed|null The last element of the array, or null if the array is empty.
597 */
598 function array_last( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
599 if ( empty( $array ) ) {
600 return null;
601 }
602
603 return $array[ array_key_last( $array ) ];
604 }
605}
606
607// IMAGETYPE_AVIF constant is only defined in PHP 8.x or later.
608if ( ! defined( 'IMAGETYPE_AVIF' ) ) {
609 define( 'IMAGETYPE_AVIF', 19 );
610}
611
612// IMG_AVIF constant is only defined in PHP 8.x or later.
613if ( ! defined( 'IMG_AVIF' ) ) {
614 define( 'IMG_AVIF', IMAGETYPE_AVIF );
615}
616
617// IMAGETYPE_HEIF constant is only defined in PHP 8.5 or later.
618if ( ! defined( 'IMAGETYPE_HEIF' ) ) {
619 define( 'IMAGETYPE_HEIF', 20 );
620}
621