WordPress.org

Make WordPress Core

Ticket #42264: 42264.1.diff

File 42264.1.diff, 33.9 KB (added by schlessera, 4 years ago)

Sample implementation of the discussed concept

  • src/wp-includes/compat.php

    diff --git src/wp-includes/compat.php src/wp-includes/compat.php
    index 7e37c30762..00c31d7bd5 100644
     
    11<?php
    22/**
    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 https://secure.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 /**
    492  * Polyfill for the SPL autoloader. In PHP 5.2 (but not 5.3 and later), SPL can
    493  * be disabled, and PHP 7.2 raises notices if the compiler finds an __autoload()
    494  * function declaration. Function availability is checked here, and the
    495  * autoloader is included only if necessary.
    496  */
    497 if ( ! function_exists( 'spl_autoload_register' ) ) {
    498         require_once ABSPATH . WPINC . '/spl-autoload-compat.php';
     3 * WordPress compatiblity logic.
     4 */
     5
     6// Check the PHP version and load the corresponding compatibility files.
     7// The fall-throughs (missing breaks) are intentional, as this makes sure that
     8// all compat files - starting from the first required one - will be loaded.
     9switch ( substr( PHP_VERSION, 0, 3 ) ) {
     10        case '5.2':
     11                require ABSPATH . WPINC . '/compat/php-5.2.php';
     12        case '5.3':
     13                require ABSPATH . WPINC . '/compat/php-5.3.php';
     14        case '5.4':
     15                require ABSPATH . WPINC . '/compat/php-5.4.php';
     16        case '5.5':
     17                require ABSPATH . WPINC . '/compat/php-5.5.php';
     18        case '5.6':
     19                require ABSPATH . WPINC . '/compat/php-5.6.php';
     20        case '7.0':
     21                require ABSPATH . WPINC . '/compat/php-7.0.php';
     22        case '7.1':
     23                require ABSPATH . WPINC . '/compat/php-7.1.php';
     24        case '7.2':
     25                require ABSPATH . WPINC . '/compat/php-7.2.php';
     26        default:
     27                require ABSPATH . WPINC . '/compat/default.php';
    49928}
  • new file src/wp-includes/compat/default.php

    diff --git src/wp-includes/compat/default.php src/wp-includes/compat/default.php
    new file mode 100644
    index 0000000000..bf1f2f01ba
    - +  
     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
     10if ( !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 */
     30function _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
     44if ( ! 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        }
     63endif;
     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 */
     82function _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
     134if ( ! 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        }
     150endif;
     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 */
     166function _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
     219if ( !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 */
     236function hash_hmac($algo, $data, $key, $raw_output = false) {
     237        return _hash_hmac($algo, $data, $key, $raw_output);
     238}
     239endif;
     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 */
     255function _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
     278if ( !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
     291if ( !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
  • new file src/wp-includes/compat/php-5.2.php

    diff --git src/wp-includes/compat/php-5.2.php src/wp-includes/compat/php-5.2.php
    new file mode 100644
    index 0000000000..25b740fddf
    - +  
     1<?php
     2/**
     3 * Compatibility file for PHP 5.2 or older.
     4 *
     5 * @package compat
     6 */
     7
     8if ( ! function_exists( 'array_replace_recursive' ) ) :
     9        /**
     10         * PHP-agnostic version of {@link array_replace_recursive()}.
     11         *
     12         * The array_replace_recursive() function is a PHP 5.3 function. WordPress
     13         * currently supports down to PHP 5.2, so this method is a workaround
     14         * for PHP 5.2.
     15         *
     16         * Note: array_replace_recursive() supports infinite arguments, but for our use-
     17         * case, we only need to support two arguments.
     18         *
     19         * Subject to removal once WordPress makes PHP 5.3.0 the minimum requirement.
     20         *
     21         * @since 4.5.3
     22         *
     23         * @see https://secure.php.net/manual/en/function.array-replace-recursive.php#109390
     24         *
     25         * @param  array $base         Array with keys needing to be replaced.
     26         * @param  array $replacements Array with the replaced keys.
     27         *
     28         * @return array
     29         */
     30        function array_replace_recursive( $base = array(), $replacements = array() ) {
     31                foreach ( array_slice( func_get_args(), 1 ) as $replacements ) {
     32                        $bref_stack = array( &$base );
     33                        $head_stack = array( $replacements );
     34
     35                        do {
     36                                end( $bref_stack );
     37
     38                                $bref = &$bref_stack[ key( $bref_stack ) ];
     39                                $head = array_pop( $head_stack );
     40
     41                                unset( $bref_stack[ key( $bref_stack ) ] );
     42
     43                                foreach ( array_keys( $head ) as $key ) {
     44                                        if ( isset( $key, $bref ) &&
     45                                             isset( $bref[ $key ] ) && is_array( $bref[ $key ] ) &&
     46                                             isset( $head[ $key ] ) && is_array( $head[ $key ] )
     47                                        ) {
     48                                                $bref_stack[] = &$bref[ $key ];
     49                                                $head_stack[] = $head[ $key ];
     50                                        } else {
     51                                                $bref[ $key ] = $head[ $key ];
     52                                        }
     53                                }
     54                        } while ( count( $head_stack ) );
     55                }
     56
     57                return $base;
     58        }
     59endif;
     60
     61/**
     62 * Polyfill for the SPL autoloader. In PHP 5.2 (but not 5.3 and later), SPL can
     63 * be disabled, and PHP 7.2 raises notices if the compiler finds an __autoload()
     64 * function declaration. Function availability is checked here, and the
     65 * autoloader is included only if necessary.
     66 */
     67if ( ! function_exists( 'spl_autoload_register' ) ) {
     68        require_once ABSPATH . WPINC . '/compat/spl-autoload-compat.php';
     69}
  • new file src/wp-includes/compat/php-5.3.php

    diff --git src/wp-includes/compat/php-5.3.php src/wp-includes/compat/php-5.3.php
    new file mode 100644
    index 0000000000..afd0f18993
    - +  
     1<?php
     2
     3// JSON_PRETTY_PRINT was introduced in PHP 5.4
     4// Defined here to prevent a notice when using it with wp_json_encode()
     5if ( ! defined( 'JSON_PRETTY_PRINT' ) ) {
     6        define( 'JSON_PRETTY_PRINT', 128 );
     7}
     8
     9if ( ! interface_exists( 'JsonSerializable' ) ) {
     10        define( 'WP_JSON_SERIALIZE_COMPATIBLE', true );
     11        /**
     12         * JsonSerializable interface.
     13         *
     14         * Compatibility shim for PHP <5.4
     15         *
     16         * @link https://secure.php.net/jsonserializable
     17         *
     18         * @since 4.4.0
     19         */
     20        interface JsonSerializable {
     21                public function jsonSerialize();
     22        }
     23}
  • new file src/wp-includes/compat/php-5.4.php

    diff --git src/wp-includes/compat/php-5.4.php src/wp-includes/compat/php-5.4.php
    new file mode 100644
    index 0000000000..da02d52818
    - +  
     1<?php
     2
     3if ( ! function_exists( 'json_last_error_msg' ) ) :
     4        /**
     5         * Retrieves the error string of the last json_encode() or json_decode() call.
     6         *
     7         * @since 4.4.0
     8         *
     9         * @internal This is a compatibility function for PHP <5.5
     10         *
     11         * @return bool|string Returns the error message on success, "No Error" if no error has occurred,
     12         *                     or false on failure.
     13         */
     14        function json_last_error_msg() {
     15                // See https://core.trac.wordpress.org/ticket/27799.
     16                if ( ! function_exists( 'json_last_error' ) ) {
     17                        return false;
     18                }
     19
     20                $last_error_code = json_last_error();
     21
     22                // Just in case JSON_ERROR_NONE is not defined.
     23                $error_code_none = defined( 'JSON_ERROR_NONE' ) ? JSON_ERROR_NONE : 0;
     24
     25                switch ( true ) {
     26                        case $last_error_code === $error_code_none:
     27                                return 'No error';
     28
     29                        case defined( 'JSON_ERROR_DEPTH' ) && JSON_ERROR_DEPTH === $last_error_code:
     30                                return 'Maximum stack depth exceeded';
     31
     32                        case defined( 'JSON_ERROR_STATE_MISMATCH' ) && JSON_ERROR_STATE_MISMATCH === $last_error_code:
     33                                return 'State mismatch (invalid or malformed JSON)';
     34
     35                        case defined( 'JSON_ERROR_CTRL_CHAR' ) && JSON_ERROR_CTRL_CHAR === $last_error_code:
     36                                return 'Control character error, possibly incorrectly encoded';
     37
     38                        case defined( 'JSON_ERROR_SYNTAX' ) && JSON_ERROR_SYNTAX === $last_error_code:
     39                                return 'Syntax error';
     40
     41                        case defined( 'JSON_ERROR_UTF8' ) && JSON_ERROR_UTF8 === $last_error_code:
     42                                return 'Malformed UTF-8 characters, possibly incorrectly encoded';
     43
     44                        case defined( 'JSON_ERROR_RECURSION' ) && JSON_ERROR_RECURSION === $last_error_code:
     45                                return 'Recursion detected';
     46
     47                        case defined( 'JSON_ERROR_INF_OR_NAN' ) && JSON_ERROR_INF_OR_NAN === $last_error_code:
     48                                return 'Inf and NaN cannot be JSON encoded';
     49
     50                        case defined( 'JSON_ERROR_UNSUPPORTED_TYPE' ) && JSON_ERROR_UNSUPPORTED_TYPE === $last_error_code:
     51                                return 'Type is not supported';
     52
     53                        default:
     54                                return 'An unknown error occurred';
     55                }
     56        }
     57endif;
  • new file src/wp-includes/compat/php-5.5.php

    diff --git src/wp-includes/compat/php-5.5.php src/wp-includes/compat/php-5.5.php
    new file mode 100644
    index 0000000000..5563b92248
    - +  
     1<?php
     2
     3if ( ! function_exists( 'hash_equals' ) ) :
     4/**
     5 * Timing attack safe string comparison
     6 *
     7 * Compares two strings using the same time whether they're equal or not.
     8 *
     9 * This function was added in PHP 5.6.
     10 *
     11 * Note: It can leak the length of a string when arguments of differing length are supplied.
     12 *
     13 * @since 3.9.2
     14 *
     15 * @param string $a Expected string.
     16 * @param string $b Actual, user supplied, string.
     17 * @return bool Whether strings are equal.
     18 */
     19function hash_equals( $a, $b ) {
     20        $a_length = strlen( $a );
     21        if ( $a_length !== strlen( $b ) ) {
     22                return false;
     23        }
     24        $result = 0;
     25
     26        // Do not attempt to "optimize" this.
     27        for ( $i = 0; $i < $a_length; $i++ ) {
     28                $result |= ord( $a[ $i ] ) ^ ord( $b[ $i ] );
     29        }
     30
     31        return $result === 0;
     32}
     33endif;
  • new file src/wp-includes/compat/php-5.6.php

    diff --git src/wp-includes/compat/php-5.6.php src/wp-includes/compat/php-5.6.php
    new file mode 100644
    index 0000000000..a5c4c7bb69
    - +  
     1<?php
     2
     3// random_int was introduced in PHP 7.0
     4if ( ! function_exists( 'random_int' ) ) {
     5        require ABSPATH . WPINC . '/random_compat/random.php';
     6}
  • new file src/wp-includes/compat/php-7.0.php

    diff --git src/wp-includes/compat/php-7.0.php src/wp-includes/compat/php-7.0.php
    new file mode 100644
    index 0000000000..b3d9bbc7f3
    - +  
     1<?php
  • new file src/wp-includes/compat/php-7.1.php

    diff --git src/wp-includes/compat/php-7.1.php src/wp-includes/compat/php-7.1.php
    new file mode 100644
    index 0000000000..b3d9bbc7f3
    - +  
     1<?php
  • new file src/wp-includes/compat/php-7.2.php

    diff --git src/wp-includes/compat/php-7.2.php src/wp-includes/compat/php-7.2.php
    new file mode 100644
    index 0000000000..b3d9bbc7f3
    - +  
     1<?php