Wordpress 4.6
[autoinstalls/wordpress.git] / wp-includes / compat.php
1 <?php
2 /**
3  * WordPress implementation for PHP functions either missing from older PHP versions or not included by default.
4  *
5  * @package PHP
6  * @access private
7  */
8
9 // If gettext isn't available
10 if ( !function_exists('_') ) {
11         function _($string) {
12                 return $string;
13         }
14 }
15
16 /**
17  * Returns whether PCRE/u (PCRE_UTF8 modifier) is available for use.
18  *
19  * @ignore
20  * @since 4.2.2
21  * @access private
22  *
23  * @staticvar string $utf8_pcre
24  *
25  * @param bool $set - Used for testing only
26  *             null   : default - get PCRE/u capability
27  *             false  : Used for testing - return false for future calls to this function
28  *             'reset': Used for testing - restore default behavior of this function
29  */
30 function _wp_can_use_pcre_u( $set = null ) {
31         static $utf8_pcre = 'reset';
32
33         if ( null !== $set ) {
34                 $utf8_pcre = $set;
35         }
36
37         if ( 'reset' === $utf8_pcre ) {
38                 $utf8_pcre = @preg_match( '/^./u', 'a' );
39         }
40
41         return $utf8_pcre;
42 }
43
44 if ( ! function_exists( 'mb_substr' ) ) :
45         /**
46          * Compat function to mimic mb_substr().
47          *
48          * @ignore
49          * @since 3.2.0
50          *
51          * @see _mb_substr()
52          *
53          * @param string      $str      The string to extract the substring from.
54          * @param int         $start    Position to being extraction from in `$str`.
55          * @param int|null    $length   Optional. Maximum number of characters to extract from `$str`.
56          *                              Default null.
57          * @param string|null $encoding Optional. Character encoding to use. Default null.
58          * @return string Extracted substring.
59          */
60         function mb_substr( $str, $start, $length = null, $encoding = null ) {
61                 return _mb_substr( $str, $start, $length, $encoding );
62         }
63 endif;
64
65 /**
66  * Internal compat function to mimic mb_substr().
67  *
68  * Only understands UTF-8 and 8bit.  All other character sets will be treated as 8bit.
69  * For $encoding === UTF-8, the $str input is expected to be a valid UTF-8 byte sequence.
70  * The behavior of this function for invalid inputs is undefined.
71  *
72  * @ignore
73  * @since 3.2.0
74  *
75  * @param string      $str      The string to extract the substring from.
76  * @param int         $start    Position to being extraction from in `$str`.
77  * @param int|null    $length   Optional. Maximum number of characters to extract from `$str`.
78  *                              Default null.
79  * @param string|null $encoding Optional. Character encoding to use. Default null.
80  * @return string Extracted substring.
81  */
82 function _mb_substr( $str, $start, $length = null, $encoding = null ) {
83         if ( null === $encoding ) {
84                 $encoding = get_option( 'blog_charset' );
85         }
86
87         /*
88          * The solution below works only for UTF-8, so in case of a different
89          * charset just use built-in substr().
90          */
91         if ( ! in_array( $encoding, array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) ) ) {
92                 return is_null( $length ) ? substr( $str, $start ) : substr( $str, $start, $length );
93         }
94
95         if ( _wp_can_use_pcre_u() ) {
96                 // Use the regex unicode support to separate the UTF-8 characters into an array.
97                 preg_match_all( '/./us', $str, $match );
98                 $chars = is_null( $length ) ? array_slice( $match[0], $start ) : array_slice( $match[0], $start, $length );
99                 return implode( '', $chars );
100         }
101
102         $regex = '/(
103                   [\x00-\x7F]                  # single-byte sequences   0xxxxxxx
104                 | [\xC2-\xDF][\x80-\xBF]       # double-byte sequences   110xxxxx 10xxxxxx
105                 | \xE0[\xA0-\xBF][\x80-\xBF]   # triple-byte sequences   1110xxxx 10xxxxxx * 2
106                 | [\xE1-\xEC][\x80-\xBF]{2}
107                 | \xED[\x80-\x9F][\x80-\xBF]
108                 | [\xEE-\xEF][\x80-\xBF]{2}
109                 | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
110                 | [\xF1-\xF3][\x80-\xBF]{3}
111                 | \xF4[\x80-\x8F][\x80-\xBF]{2}
112         )/x';
113
114         // Start with 1 element instead of 0 since the first thing we do is pop.
115         $chars = array( '' );
116         do {
117                 // We had some string left over from the last round, but we counted it in that last round.
118                 array_pop( $chars );
119
120                 /*
121                  * Split by UTF-8 character, limit to 1000 characters (last array element will contain
122                  * the rest of the string).
123                  */
124                 $pieces = preg_split( $regex, $str, 1000, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
125
126                 $chars = array_merge( $chars, $pieces );
127
128         // If there's anything left over, repeat the loop.
129         } while ( count( $pieces ) > 1 && $str = array_pop( $pieces ) );
130
131         return join( '', array_slice( $chars, $start, $length ) );
132 }
133
134 if ( ! function_exists( 'mb_strlen' ) ) :
135         /**
136          * Compat function to mimic mb_strlen().
137          *
138          * @ignore
139          * @since 4.2.0
140          *
141          * @see _mb_strlen()
142          *
143          * @param string      $str      The string to retrieve the character length from.
144          * @param string|null $encoding Optional. Character encoding to use. Default null.
145          * @return int String length of `$str`.
146          */
147         function mb_strlen( $str, $encoding = null ) {
148                 return _mb_strlen( $str, $encoding );
149         }
150 endif;
151
152 /**
153  * Internal compat function to mimic mb_strlen().
154  *
155  * Only understands UTF-8 and 8bit.  All other character sets will be treated as 8bit.
156  * For $encoding === UTF-8, the `$str` input is expected to be a valid UTF-8 byte
157  * sequence. The behavior of this function for invalid inputs is undefined.
158  *
159  * @ignore
160  * @since 4.2.0
161  *
162  * @param string      $str      The string to retrieve the character length from.
163  * @param string|null $encoding Optional. Character encoding to use. Default null.
164  * @return int String length of `$str`.
165  */
166 function _mb_strlen( $str, $encoding = null ) {
167         if ( null === $encoding ) {
168                 $encoding = get_option( 'blog_charset' );
169         }
170
171         /*
172          * The solution below works only for UTF-8, so in case of a different charset
173          * just use built-in strlen().
174          */
175         if ( ! in_array( $encoding, array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) ) ) {
176                 return strlen( $str );
177         }
178
179         if ( _wp_can_use_pcre_u() ) {
180                 // Use the regex unicode support to separate the UTF-8 characters into an array.
181                 preg_match_all( '/./us', $str, $match );
182                 return count( $match[0] );
183         }
184
185         $regex = '/(?:
186                   [\x00-\x7F]                  # single-byte sequences   0xxxxxxx
187                 | [\xC2-\xDF][\x80-\xBF]       # double-byte sequences   110xxxxx 10xxxxxx
188                 | \xE0[\xA0-\xBF][\x80-\xBF]   # triple-byte sequences   1110xxxx 10xxxxxx * 2
189                 | [\xE1-\xEC][\x80-\xBF]{2}
190                 | \xED[\x80-\x9F][\x80-\xBF]
191                 | [\xEE-\xEF][\x80-\xBF]{2}
192                 | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
193                 | [\xF1-\xF3][\x80-\xBF]{3}
194                 | \xF4[\x80-\x8F][\x80-\xBF]{2}
195         )/x';
196
197         // Start at 1 instead of 0 since the first thing we do is decrement.
198         $count = 1;
199         do {
200                 // We had some string left over from the last round, but we counted it in that last round.
201                 $count--;
202
203                 /*
204                  * Split by UTF-8 character, limit to 1000 characters (last array element will contain
205                  * the rest of the string).
206                  */
207                 $pieces = preg_split( $regex, $str, 1000 );
208
209                 // Increment.
210                 $count += count( $pieces );
211
212         // If there's anything left over, repeat the loop.
213         } while ( $str = array_pop( $pieces ) );
214
215         // Fencepost: preg_split() always returns one extra item in the array.
216         return --$count;
217 }
218
219 if ( !function_exists('hash_hmac') ):
220 /**
221  * Compat function to mimic hash_hmac().
222  *
223  * @ignore
224  * @since 3.2.0
225  *
226  * @see _hash_hmac()
227  *
228  * @param string $algo       Hash algorithm. Accepts 'md5' or 'sha1'.
229  * @param string $data       Data to be hashed.
230  * @param string $key        Secret key to use for generating the hash.
231  * @param bool   $raw_output Optional. Whether to output raw binary data (true),
232  *                           or lowercase hexits (false). Default false.
233  * @return string|false The hash in output determined by `$raw_output`. False if `$algo`
234  *                      is unknown or invalid.
235  */
236 function hash_hmac($algo, $data, $key, $raw_output = false) {
237         return _hash_hmac($algo, $data, $key, $raw_output);
238 }
239 endif;
240
241 /**
242  * Internal compat function to mimic hash_hmac().
243  *
244  * @ignore
245  * @since 3.2.0
246  *
247  * @param string $algo       Hash algorithm. Accepts 'md5' or 'sha1'.
248  * @param string $data       Data to be hashed.
249  * @param string $key        Secret key to use for generating the hash.
250  * @param bool   $raw_output Optional. Whether to output raw binary data (true),
251  *                           or lowercase hexits (false). Default false.
252  * @return string|false The hash in output determined by `$raw_output`. False if `$algo`
253  *                      is unknown or invalid.
254  */
255 function _hash_hmac($algo, $data, $key, $raw_output = false) {
256         $packs = array('md5' => 'H32', 'sha1' => 'H40');
257
258         if ( !isset($packs[$algo]) )
259                 return false;
260
261         $pack = $packs[$algo];
262
263         if (strlen($key) > 64)
264                 $key = pack($pack, $algo($key));
265
266         $key = str_pad($key, 64, chr(0));
267
268         $ipad = (substr($key, 0, 64) ^ str_repeat(chr(0x36), 64));
269         $opad = (substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64));
270
271         $hmac = $algo($opad . pack($pack, $algo($ipad . $data)));
272
273         if ( $raw_output )
274                 return pack( $pack, $hmac );
275         return $hmac;
276 }
277
278 if ( !function_exists('json_encode') ) {
279         function json_encode( $string ) {
280                 global $wp_json;
281
282                 if ( ! ( $wp_json instanceof Services_JSON ) ) {
283                         require_once( ABSPATH . WPINC . '/class-json.php' );
284                         $wp_json = new Services_JSON();
285                 }
286
287                 return $wp_json->encodeUnsafe( $string );
288         }
289 }
290
291 if ( !function_exists('json_decode') ) {
292         /**
293          * @global Services_JSON $wp_json
294          * @param string $string
295          * @param bool   $assoc_array
296          * @return object|array
297          */
298         function json_decode( $string, $assoc_array = false ) {
299                 global $wp_json;
300
301                 if ( ! ($wp_json instanceof Services_JSON ) ) {
302                         require_once( ABSPATH . WPINC . '/class-json.php' );
303                         $wp_json = new Services_JSON();
304                 }
305
306                 $res = $wp_json->decode( $string );
307                 if ( $assoc_array )
308                         $res = _json_decode_object_helper( $res );
309                 return $res;
310         }
311
312         /**
313          * @param object $data
314          * @return array
315          */
316         function _json_decode_object_helper($data) {
317                 if ( is_object($data) )
318                         $data = get_object_vars($data);
319                 return is_array($data) ? array_map(__FUNCTION__, $data) : $data;
320         }
321 }
322
323 if ( ! function_exists( 'hash_equals' ) ) :
324 /**
325  * Timing attack safe string comparison
326  *
327  * Compares two strings using the same time whether they're equal or not.
328  *
329  * This function was added in PHP 5.6.
330  *
331  * Note: It can leak the length of a string when arguments of differing length are supplied.
332  *
333  * @since 3.9.2
334  *
335  * @param string $a Expected string.
336  * @param string $b Actual, user supplied, string.
337  * @return bool Whether strings are equal.
338  */
339 function hash_equals( $a, $b ) {
340         $a_length = strlen( $a );
341         if ( $a_length !== strlen( $b ) ) {
342                 return false;
343         }
344         $result = 0;
345
346         // Do not attempt to "optimize" this.
347         for ( $i = 0; $i < $a_length; $i++ ) {
348                 $result |= ord( $a[ $i ] ) ^ ord( $b[ $i ] );
349         }
350
351         return $result === 0;
352 }
353 endif;
354
355 // JSON_PRETTY_PRINT was introduced in PHP 5.4
356 // Defined here to prevent a notice when using it with wp_json_encode()
357 if ( ! defined( 'JSON_PRETTY_PRINT' ) ) {
358         define( 'JSON_PRETTY_PRINT', 128 );
359 }
360
361 if ( ! function_exists( 'json_last_error_msg' ) ) :
362         /**
363          * Retrieves the error string of the last json_encode() or json_decode() call.
364          *
365          * @since 4.4.0
366          *
367          * @internal This is a compatibility function for PHP <5.5
368          *
369          * @return bool|string Returns the error message on success, "No Error" if no error has occurred,
370          *                     or false on failure.
371          */
372         function json_last_error_msg() {
373                 // See https://core.trac.wordpress.org/ticket/27799.
374                 if ( ! function_exists( 'json_last_error' ) ) {
375                         return false;
376                 }
377
378                 $last_error_code = json_last_error();
379
380                 // Just in case JSON_ERROR_NONE is not defined.
381                 $error_code_none = defined( 'JSON_ERROR_NONE' ) ? JSON_ERROR_NONE : 0;
382
383                 switch ( true ) {
384                         case $last_error_code === $error_code_none:
385                                 return 'No error';
386
387                         case defined( 'JSON_ERROR_DEPTH' ) && JSON_ERROR_DEPTH === $last_error_code:
388                                 return 'Maximum stack depth exceeded';
389
390                         case defined( 'JSON_ERROR_STATE_MISMATCH' ) && JSON_ERROR_STATE_MISMATCH === $last_error_code:
391                                 return 'State mismatch (invalid or malformed JSON)';
392
393                         case defined( 'JSON_ERROR_CTRL_CHAR' ) && JSON_ERROR_CTRL_CHAR === $last_error_code:
394                                 return 'Control character error, possibly incorrectly encoded';
395
396                         case defined( 'JSON_ERROR_SYNTAX' ) && JSON_ERROR_SYNTAX === $last_error_code:
397                                 return 'Syntax error';
398
399                         case defined( 'JSON_ERROR_UTF8' ) && JSON_ERROR_UTF8 === $last_error_code:
400                                 return 'Malformed UTF-8 characters, possibly incorrectly encoded';
401
402                         case defined( 'JSON_ERROR_RECURSION' ) && JSON_ERROR_RECURSION === $last_error_code:
403                                 return 'Recursion detected';
404
405                         case defined( 'JSON_ERROR_INF_OR_NAN' ) && JSON_ERROR_INF_OR_NAN === $last_error_code:
406                                 return 'Inf and NaN cannot be JSON encoded';
407
408                         case defined( 'JSON_ERROR_UNSUPPORTED_TYPE' ) && JSON_ERROR_UNSUPPORTED_TYPE === $last_error_code:
409                                 return 'Type is not supported';
410
411                         default:
412                                 return 'An unknown error occurred';
413                 }
414         }
415 endif;
416
417 if ( ! interface_exists( 'JsonSerializable' ) ) {
418         define( 'WP_JSON_SERIALIZE_COMPATIBLE', true );
419         /**
420          * JsonSerializable interface.
421          *
422          * Compatibility shim for PHP <5.4
423          *
424          * @link https://secure.php.net/jsonserializable
425          *
426          * @since 4.4.0
427          */
428         interface JsonSerializable {
429                 public function jsonSerialize();
430         }
431 }
432
433 // random_int was introduced in PHP 7.0
434 if ( ! function_exists( 'random_int' ) ) {
435         require ABSPATH . WPINC . '/random_compat/random.php';
436 }
437
438 if ( ! function_exists( 'array_replace_recursive' ) ) :
439         /**
440          * PHP-agnostic version of {@link array_replace_recursive()}.
441          *
442          * The array_replace_recursive() function is a PHP 5.3 function. WordPress
443          * currently supports down to PHP 5.2, so this method is a workaround
444          * for PHP 5.2.
445          *
446          * Note: array_replace_recursive() supports infinite arguments, but for our use-
447          * case, we only need to support two arguments.
448          *
449          * Subject to removal once WordPress makes PHP 5.3.0 the minimum requirement.
450          *
451          * @since 4.5.3
452          *
453          * @see http://php.net/manual/en/function.array-replace-recursive.php#109390
454          *
455          * @param  array $base         Array with keys needing to be replaced.
456          * @param  array $replacements Array with the replaced keys.
457          *
458          * @return array
459          */
460         function array_replace_recursive( $base = array(), $replacements = array() ) {
461                 foreach ( array_slice( func_get_args(), 1 ) as $replacements ) {
462                         $bref_stack = array( &$base );
463                         $head_stack = array( $replacements );
464
465                         do {
466                                 end( $bref_stack );
467
468                                 $bref = &$bref_stack[ key( $bref_stack ) ];
469                                 $head = array_pop( $head_stack );
470
471                                 unset( $bref_stack[ key( $bref_stack ) ] );
472
473                                 foreach ( array_keys( $head ) as $key ) {
474                                         if ( isset( $key, $bref ) &&
475                                              isset( $bref[ $key ] ) && is_array( $bref[ $key ] ) &&
476                                              isset( $head[ $key ] ) && is_array( $head[ $key ] )
477                                         ) {
478                                                 $bref_stack[] = &$bref[ $key ];
479                                                 $head_stack[] = $head[ $key ];
480                                         } else {
481                                                 $bref[ $key ] = $head[ $key ];
482                                         }
483                                 }
484                         } while ( count( $head_stack ) );
485                 }
486
487                 return $base;
488         }
489 endif;
490
491 // SPL can be disabled on PHP 5.2
492 if ( ! function_exists( 'spl_autoload_register' ) ):
493         $_wp_spl_autoloaders = array();
494
495         /**
496          * Autoloader compatibility callback.
497          *
498          * @since 4.6.0
499          *
500          * @param string $classname Class to attempt autoloading.
501          */
502         function __autoload( $classname ) {
503                 global $_wp_spl_autoloaders;
504                 foreach ( $_wp_spl_autoloaders as $autoloader ) {
505                         if ( ! is_callable( $autoloader ) ) {
506                                 // Avoid the extra warning if the autoloader isn't callable.
507                                 continue;
508                         }
509
510                         call_user_func( $autoloader, $classname );
511
512                         // If it has been autoloaded, stop processing.
513                         if ( class_exists( $classname, false ) ) {
514                                 return;
515                         }
516                 }
517         }
518
519         /**
520          * Registers a function to be autoloaded.
521          *
522          * @since 4.6.0
523          *
524          * @param callable $autoload_function The function to register.
525          * @param bool     $throw             Optional. Whether the function should throw an exception
526          *                                    if the function isn't callable. Default true.
527          * @param bool     $prepend           Whether the function should be prepended to the stack.
528          *                                    Default false.
529          */
530         function spl_autoload_register( $autoload_function, $throw = true, $prepend = false ) {
531                 if ( $throw && ! is_callable( $autoload_function ) ) {
532                         // String not translated to match PHP core.
533                         throw new Exception( 'Function not callable' );
534                 }
535
536                 global $_wp_spl_autoloaders;
537
538                 // Don't allow multiple registration.
539                 if ( in_array( $autoload_function, $_wp_spl_autoloaders ) ) {
540                         return;
541                 }
542
543                 if ( $prepend ) {
544                         array_unshift( $_wp_spl_autoloaders, $autoload_function );
545                 } else {
546                         $_wp_spl_autoloaders[] = $autoload_function;
547                 }
548         }
549
550         /**
551          * Unregisters an autoloader function.
552          *
553          * @since 4.6.0
554          *
555          * @param callable $function The function to unregister.
556          * @return bool True if the function was unregistered, false if it could not be.
557          */
558         function spl_autoload_unregister( $function ) {
559                 global $_wp_spl_autoloaders;
560                 foreach ( $_wp_spl_autoloaders as &$autoloader ) {
561                         if ( $autoloader === $function ) {
562                                 unset( $autoloader );
563                                 return true;
564                         }
565                 }
566
567                 return false;
568         }
569
570         /**
571          * Retrieves the registered autoloader functions.
572          *
573          * @since 4.6.0
574          *
575          * @return array List of autoloader functions.
576          */
577         function spl_autoload_functions() {
578                 return $GLOBALS['_wp_spl_autoloaders'];
579         }
580 endif;