| | 219 | if ( ! function_exists( 'mb_strpos' ) ) : |
| | 220 | /** |
| | 221 | * Compat function to mimic mb_strpos(). |
| | 222 | * |
| | 223 | * @ignore |
| | 224 | * @since 4.5.0 |
| | 225 | * |
| | 226 | * @see _mb_strpos() |
| | 227 | * |
| | 228 | * @param string $haystack The string to search in. |
| | 229 | * @param mixed $needle If needle is not a string, it is converted to an integer and applied as the ordinal value of a character. |
| | 230 | * @param int $offset Optional, search will start this number of characters counted from the beginning of the string. The offset cannot be negative. |
| | 231 | * @param string|null $encoding Optional. Character encoding to use. Default null. |
| | 232 | * @return int Position of first occurence found of $haystack of `$needle`. |
| | 233 | */ |
| | 234 | function mb_strpos( $haystack, $needle, $offset = 0, $encoding = null ) { |
| | 235 | return _mb_strpos( $haystack, $needle, $offset, $encoding ); |
| | 236 | } |
| | 237 | endif; |
| | 238 | |
| | 239 | /** |
| | 240 | * Internal compat function to mimic mb_strpos(). |
| | 241 | * |
| | 242 | * Only understands UTF-8 and 8bit. All other character sets will be treated as 8bit. |
| | 243 | * For $encoding === UTF-8, the $str input is expected to be a valid UTF-8 byte sequence. |
| | 244 | * The behavior of this function for invalid inputs is PHP compliant. |
| | 245 | * |
| | 246 | * @ignore |
| | 247 | * @since 4.5.0 |
| | 248 | * |
| | 249 | * @param string $haystack The string to search in. |
| | 250 | * @param mixed $needle If needle is not a string, it is converted to an integer and applied as the ordinal value of a character. |
| | 251 | * @param int $offset Optional, search will start this number of characters counted from the beginning of the string. The offset cannot be negative. |
| | 252 | * @param string|null $encoding Optional. Character encoding to use. Default null. |
| | 253 | * |
| | 254 | * @return int Position of first occurence found of $haystack of `$needle`. |
| | 255 | */ |
| | 256 | function _mb_strpos( $haystack, $needle, $offset = 0, $encoding = null ) { |
| | 257 | |
| | 258 | if ( null === $encoding ) { |
| | 259 | $encoding = get_option( 'blog_charset' ); |
| | 260 | } |
| | 261 | |
| | 262 | // The solution below works only for UTF-8, |
| | 263 | // So in case of a different charset just use built-in strpos() |
| | 264 | if ( ! in_array( $encoding, array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) ) ) { |
| | 265 | return $offset === 0 ? strpos( $haystack, $needle ) : strpos( $haystack, $needle, $offset ); |
| | 266 | } |
| | 267 | |
| | 268 | $haystack_len = mb_strlen( $haystack ); |
| | 269 | |
| | 270 | if ( $offset < (int) 0 || $offset > $haystack_len ) { |
| | 271 | trigger_error( 'mb_strpos(): Offset not contained in string', E_USER_WARNING ); |
| | 272 | return false; |
| | 273 | } |
| | 274 | |
| | 275 | if ( ! is_string( $needle ) ) { |
| | 276 | $needle = (int) $needle; |
| | 277 | |
| | 278 | if ( ! is_int( $needle ) ) { |
| | 279 | trigger_error( 'mb_strpos(): Array to string conversion', E_USER_WARNING ); |
| | 280 | return false; |
| | 281 | } |
| | 282 | } |
| | 283 | |
| | 284 | if ( empty( $needle ) ) { |
| | 285 | trigger_error( 'mb_strpos(): Empty needle', E_USER_WARNING ); |
| | 286 | return false; |
| | 287 | } |
| | 288 | |
| | 289 | // Slice off the offset |
| | 290 | $haystack_sub = mb_substr( $haystack, $offset ); |
| | 291 | |
| | 292 | if ( _wp_can_use_pcre_u() ) { |
| | 293 | // Use the regex unicode support to separate the UTF-8 characters into an array |
| | 294 | preg_match_all( "/./us", $haystack, $match_h ); |
| | 295 | preg_match_all( "/$needle/us", $haystack_sub, $match_n ); |
| | 296 | |
| | 297 | $inter = array_intersect( $match_h[0], $match_n[0] ); |
| | 298 | |
| | 299 | if ( ! isset( $inter ) ) |
| | 300 | return false; |
| | 301 | |
| | 302 | //* Prevent bugs, (re)assign var. |
| | 303 | $pos = null; |
| | 304 | |
| | 305 | // Find first occurence greater than or equal to offset |
| | 306 | foreach ( $inter as $key => $value ) { |
| | 307 | if ( $key >= $offset ) { |
| | 308 | $pos = $key; |
| | 309 | break; |
| | 310 | } |
| | 311 | } |
| | 312 | |
| | 313 | //* No key has been found. |
| | 314 | if ( ! isset( $pos ) ) |
| | 315 | return false; |
| | 316 | |
| | 317 | return (int) $pos; |
| | 318 | } |
| | 319 | |
| | 320 | $regex = '/( |
| | 321 | [\x00-\x7F] # single-byte sequences 0xxxxxxx |
| | 322 | | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx |
| | 323 | | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 |
| | 324 | | [\xE1-\xEC][\x80-\xBF]{2} |
| | 325 | | \xED[\x80-\x9F][\x80-\xBF] |
| | 326 | | [\xEE-\xEF][\x80-\xBF]{2} |
| | 327 | | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 |
| | 328 | | [\xF1-\xF3][\x80-\xBF]{3} |
| | 329 | | \xF4[\x80-\x8F][\x80-\xBF]{2} |
| | 330 | )/x'; |
| | 331 | |
| | 332 | /** |
| | 333 | * Place haystack into array |
| | 334 | */ |
| | 335 | $match_h = array( '' ); // Start with 1 element instead of 0 since the first thing we do is pop |
| | 336 | do { |
| | 337 | // We had some string left over from the last round, but we counted it in that last round. |
| | 338 | array_pop( $match_h ); |
| | 339 | |
| | 340 | // Split by UTF-8 character, limit to 1000 characters (last array element will contain the rest of the string) |
| | 341 | $pieces = preg_split( $regex, $haystack, 1000, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); |
| | 342 | |
| | 343 | $match_h = array_merge( $match_h, $pieces ); |
| | 344 | } while ( count( $pieces ) > 1 && $haystack = array_pop( $pieces ) ); // If there's anything left over, repeat the loop. |
| | 345 | |
| | 346 | /** |
| | 347 | * Place haystack offset into array |
| | 348 | */ |
| | 349 | $match_hs = array( '' ); // Start with 1 element instead of 0 since the first thing we do is pop |
| | 350 | do { |
| | 351 | // We had some string left over from the last round, but we counted it in that last round. |
| | 352 | array_pop( $match_hs ); |
| | 353 | |
| | 354 | // Split by UTF-8 character, limit to 1000 characters (last array element will contain the rest of the string) |
| | 355 | $pieces = preg_split( $regex, $haystack_sub, 1000, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); |
| | 356 | |
| | 357 | $match_hs = array_merge( $match_hs, $pieces ); |
| | 358 | } while ( count( $pieces ) > 1 && $haystack_sub = array_pop( $pieces ) ); // If there's anything left over, repeat the loop. |
| | 359 | |
| | 360 | /** |
| | 361 | * Put needle into array |
| | 362 | */ |
| | 363 | $match_n = array( '' ); // Start with 1 element instead of 0 since the first thing we do is pop |
| | 364 | do { |
| | 365 | // We had some string left over from the last round, but we counted it in that last round. |
| | 366 | array_pop( $match_n ); |
| | 367 | |
| | 368 | // Split by UTF-8 character, limit to 1000 characters (last array element will contain the rest of the string) |
| | 369 | $pieces = preg_split( $regex, $needle, 1000, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); |
| | 370 | |
| | 371 | $match_n = array_merge( $match_n, $pieces ); |
| | 372 | } while ( count( $pieces ) > 1 && $needle = array_pop( $pieces ) ); // If there's anything left over, repeat the loop. |
| | 373 | |
| | 374 | /** |
| | 375 | * Compute match of haystack offset with needle |
| | 376 | * If passed, return the array key number within the full haystack. |
| | 377 | */ |
| | 378 | if ( false !== in_array( $match_n[0], $match_hs ) ) { |
| | 379 | $inter = array_intersect( $match_h, $match_n ); |
| | 380 | |
| | 381 | if ( ! isset( $inter ) ) |
| | 382 | return false; |
| | 383 | |
| | 384 | //* Prevent bugs, (re)assign var. |
| | 385 | $pos = null; |
| | 386 | |
| | 387 | // Find first occurence greater than or equal to offset |
| | 388 | foreach ( $inter as $key => $value ) { |
| | 389 | if ( $key >= $offset ) { |
| | 390 | $pos = $key; |
| | 391 | break; |
| | 392 | } |
| | 393 | } |
| | 394 | |
| | 395 | //* No key has been found. |
| | 396 | if ( ! isset( $pos ) ) |
| | 397 | return false; |
| | 398 | |
| | 399 | return (int) $pos; |
| | 400 | } else { |
| | 401 | return false; |
| | 402 | } |
| | 403 | } |
| | 404 | |
| | 405 | |
| | 406 | /** |
| | 407 | * Internal compat function to mimic mb_strpos(). |
| | 408 | * |
| | 409 | * Only understands UTF-8 and 8bit. All other character sets will be treated as 8bit. |
| | 410 | * For $encoding === UTF-8, the `$str` input is expected to be a valid UTF-8 byte |
| | 411 | * sequence. The behavior of this function for invalid inputs is undefined. |
| | 412 | * |
| | 413 | * @ignore |
| | 414 | * @since 4.2.0 |
| | 415 | * |
| | 416 | * @param string $str The string to retrieve the character length from. |
| | 417 | * @param string|null $encoding Optional. Character encoding to use. Default null. |
| | 418 | * @return int String length of `$str`. |
| | 419 | */ |
| | 420 | |