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