| [ Index ] |
PHP Cross Reference of YOURLS |
[Summary view] [Print] [Text view]
1 <?php 2 3 /* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <[email protected]> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12 namespace Symfony\Polyfill\Mbstring; 13 14 /** 15 * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. 16 * 17 * Implemented: 18 * - mb_chr - Returns a specific character from its Unicode code point 19 * - mb_convert_encoding - Convert character encoding 20 * - mb_convert_variables - Convert character code in variable(s) 21 * - mb_decode_mimeheader - Decode string in MIME header field 22 * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED 23 * - mb_decode_numericentity - Decode HTML numeric string reference to character 24 * - mb_encode_numericentity - Encode character to HTML numeric string reference 25 * - mb_convert_case - Perform case folding on a string 26 * - mb_detect_encoding - Detect character encoding 27 * - mb_get_info - Get internal settings of mbstring 28 * - mb_http_input - Detect HTTP input character encoding 29 * - mb_http_output - Set/Get HTTP output character encoding 30 * - mb_internal_encoding - Set/Get internal character encoding 31 * - mb_list_encodings - Returns an array of all supported encodings 32 * - mb_ord - Returns the Unicode code point of a character 33 * - mb_output_handler - Callback function converts character encoding in output buffer 34 * - mb_scrub - Replaces ill-formed byte sequences with substitute characters 35 * - mb_strlen - Get string length 36 * - mb_strpos - Find position of first occurrence of string in a string 37 * - mb_strrpos - Find position of last occurrence of a string in a string 38 * - mb_str_split - Convert a string to an array 39 * - mb_strtolower - Make a string lowercase 40 * - mb_strtoupper - Make a string uppercase 41 * - mb_substitute_character - Set/Get substitution character 42 * - mb_substr - Get part of string 43 * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive 44 * - mb_stristr - Finds first occurrence of a string within another, case insensitive 45 * - mb_strrchr - Finds the last occurrence of a character in a string within another 46 * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive 47 * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive 48 * - mb_strstr - Finds first occurrence of a string within another 49 * - mb_strwidth - Return width of string 50 * - mb_substr_count - Count the number of substring occurrences 51 * - mb_ucfirst - Make a string's first character uppercase 52 * - mb_lcfirst - Make a string's first character lowercase 53 * - mb_trim - Strip whitespace (or other characters) from the beginning and end of a string 54 * - mb_ltrim - Strip whitespace (or other characters) from the beginning of a string 55 * - mb_rtrim - Strip whitespace (or other characters) from the end of a string 56 * 57 * Not implemented: 58 * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) 59 * - mb_ereg_* - Regular expression with multibyte support 60 * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable 61 * - mb_preferred_mime_name - Get MIME charset string 62 * - mb_regex_encoding - Returns current encoding for multibyte regex as string 63 * - mb_regex_set_options - Set/Get the default options for mbregex functions 64 * - mb_send_mail - Send encoded mail 65 * - mb_split - Split multibyte string using regular expression 66 * - mb_strcut - Get part of string 67 * - mb_strimwidth - Get truncated string with specified width 68 * 69 * @author Nicolas Grekas <[email protected]> 70 * 71 * @internal 72 */ 73 final class Mbstring 74 { 75 public const MB_CASE_FOLD = \PHP_INT_MAX; 76 77 private const SIMPLE_CASE_FOLD = [ 78 ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], 79 ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], 80 ]; 81 82 private static $encodingList = ['ASCII', 'UTF-8']; 83 private static $language = 'neutral'; 84 private static $internalEncoding = 'UTF-8'; 85 private static $iconvSupportsIgnore; 86 87 public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) 88 { 89 if (\is_array($s)) { 90 $r = []; 91 foreach ($s as $str) { 92 $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding); 93 } 94 95 return $r; 96 } 97 98 if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { 99 $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); 100 } else { 101 $fromEncoding = self::getEncoding($fromEncoding); 102 } 103 104 $toEncoding = self::getEncoding($toEncoding); 105 106 if ('BASE64' === $fromEncoding) { 107 $s = base64_decode($s); 108 $fromEncoding = $toEncoding; 109 } 110 111 if ('BASE64' === $toEncoding) { 112 return base64_encode($s); 113 } 114 115 if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { 116 if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { 117 $fromEncoding = 'Windows-1252'; 118 } 119 if ('UTF-8' !== $fromEncoding) { 120 $s = self::iconv($fromEncoding, 'UTF-8', $s); 121 } 122 123 return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); 124 } 125 126 if ('HTML-ENTITIES' === $fromEncoding) { 127 $decodeControlChars = static function ($m) { 128 $code = '' !== ($m[2] ?? '') ? hexdec($m[2]) : (int) $m[1]; 129 130 if ($code < 32 || 127 === $code) { 131 return \chr($code); 132 } 133 if (128 <= $code && $code <= 159) { 134 return "\xC2".\chr(0x80 | ($code & 0x3F)); 135 } 136 137 return $m[0]; 138 }; 139 140 if (\PHP_VERSION_ID >= 70400) { 141 $s = html_entity_decode($s, \ENT_QUOTES, 'UTF-8'); 142 // html_entity_decode() leaves numeric entities for C0/C1 control 143 // characters as-is (HTML spec), but mb_convert_encoding() decodes 144 // them. Catch what html_entity_decode() missed. 145 if (false !== strpos($s, '&#')) { 146 $s = preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); 147 } 148 } else { 149 // PHP < 7.4: html_entity_decode() truncates strings at NUL bytes, 150 // so decode the control character entities first then call 151 // html_entity_decode() on each NUL-delimited chunk independently. 152 $s = preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); 153 $s = implode("\0", array_map(static function ($chunk) { 154 return html_entity_decode($chunk, \ENT_QUOTES, 'UTF-8'); 155 }, explode("\0", $s))); 156 } 157 $fromEncoding = 'UTF-8'; 158 } 159 160 if ($fromEncoding === $toEncoding) { 161 return $s; 162 } 163 164 return self::iconv($fromEncoding, $toEncoding, $s); 165 } 166 167 public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) 168 { 169 $ok = true; 170 array_walk_recursive($vars, static function (&$v) use (&$ok, $toEncoding, $fromEncoding) { 171 if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { 172 $ok = false; 173 } 174 }); 175 176 return $ok ? $fromEncoding : false; 177 } 178 179 public static function mb_decode_mimeheader($s) 180 { 181 return iconv_mime_decode($s, 2, self::$internalEncoding); 182 } 183 184 public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) 185 { 186 trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); 187 } 188 189 public static function mb_decode_numericentity($s, $convmap, $encoding = null) 190 { 191 if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { 192 trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); 193 194 return null; 195 } 196 197 if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { 198 return false; 199 } 200 201 if (null !== $encoding && !\is_scalar($encoding)) { 202 trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); 203 204 return ''; // Instead of null (cf. mb_encode_numericentity). 205 } 206 207 $s = (string) $s; 208 if ('' === $s) { 209 return ''; 210 } 211 212 $encoding = self::getEncoding($encoding); 213 214 if ('UTF-8' === $encoding) { 215 $encoding = null; 216 if (!preg_match('//u', $s)) { 217 $s = @self::iconv('UTF-8', 'UTF-8', $s); 218 } 219 } else { 220 $s = self::iconv($encoding, 'UTF-8', $s); 221 } 222 223 $cnt = floor(\count($convmap) / 4) * 4; 224 225 for ($i = 0; $i < $cnt; $i += 4) { 226 // collector_decode_htmlnumericentity ignores $convmap[$i + 3] 227 $convmap[$i] += $convmap[$i + 2]; 228 $convmap[$i + 1] += $convmap[$i + 2]; 229 } 230 231 $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))'.(\PHP_VERSION_ID >= 80200 ? '' : '(?!&)').';?/', static function (array $m) use ($cnt, $convmap) { 232 $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; 233 for ($i = 0; $i < $cnt; $i += 4) { 234 if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { 235 return self::mb_chr($c - $convmap[$i + 2]); 236 } 237 } 238 239 return $m[0]; 240 }, $s); 241 242 if (null === $encoding) { 243 return $s; 244 } 245 246 return self::iconv('UTF-8', $encoding, $s); 247 } 248 249 public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) 250 { 251 if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { 252 trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); 253 254 return null; 255 } 256 257 if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { 258 return false; 259 } 260 261 if (null !== $encoding && !\is_scalar($encoding)) { 262 trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); 263 264 return null; // Instead of '' (cf. mb_decode_numericentity). 265 } 266 267 if (null !== $is_hex && !\is_scalar($is_hex)) { 268 trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); 269 270 return null; 271 } 272 273 $s = (string) $s; 274 if ('' === $s) { 275 return ''; 276 } 277 278 $encoding = self::getEncoding($encoding); 279 280 if ('UTF-8' === $encoding) { 281 $encoding = null; 282 if (!preg_match('//u', $s)) { 283 $s = @self::iconv('UTF-8', 'UTF-8', $s); 284 } 285 } else { 286 $s = self::iconv($encoding, 'UTF-8', $s); 287 } 288 289 static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; 290 291 $cnt = floor(\count($convmap) / 4) * 4; 292 $i = 0; 293 $len = \strlen($s); 294 $result = ''; 295 296 while ($i < $len) { 297 $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; 298 $uchr = substr($s, $i, $ulen); 299 $i += $ulen; 300 $c = self::mb_ord($uchr); 301 302 for ($j = 0; $j < $cnt; $j += 4) { 303 if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { 304 $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; 305 $result .= $is_hex ? \sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; 306 continue 2; 307 } 308 } 309 $result .= $uchr; 310 } 311 312 if (null === $encoding) { 313 return $result; 314 } 315 316 return self::iconv('UTF-8', $encoding, $result); 317 } 318 319 public static function mb_convert_case($s, $mode, $encoding = null) 320 { 321 $s = (string) $s; 322 if ('' === $s) { 323 return ''; 324 } 325 326 $encoding = self::getEncoding($encoding); 327 328 if ('UTF-8' === $encoding) { 329 $encoding = null; 330 if (!preg_match('//u', $s)) { 331 $s = @self::iconv('UTF-8', 'UTF-8', $s); 332 } 333 } else { 334 $s = self::iconv($encoding, 'UTF-8', $s); 335 } 336 337 if (\MB_CASE_TITLE == $mode) { 338 static $titleRegexp = null; 339 if (null === $titleRegexp) { 340 $titleRegexp = self::getData('titleCaseRegexp'); 341 } 342 $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); 343 } else { 344 if (\MB_CASE_UPPER == $mode) { 345 static $upper = null; 346 if (null === $upper) { 347 $upper = self::getData('upperCase'); 348 } 349 $map = $upper; 350 } else { 351 if (self::MB_CASE_FOLD === $mode) { 352 static $caseFolding = null; 353 if (null === $caseFolding) { 354 $caseFolding = self::getData('caseFolding'); 355 } 356 $s = strtr($s, $caseFolding); 357 } 358 359 static $lower = null; 360 if (null === $lower) { 361 $lower = self::getData('lowerCase'); 362 } 363 $map = $lower; 364 } 365 366 static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; 367 368 $i = 0; 369 $len = \strlen($s); 370 371 while ($i < $len) { 372 $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; 373 $uchr = substr($s, $i, $ulen); 374 $i += $ulen; 375 376 if (isset($map[$uchr])) { 377 $uchr = $map[$uchr]; 378 $nlen = \strlen($uchr); 379 380 if ($nlen == $ulen) { 381 $nlen = $i; 382 do { 383 $s[--$nlen] = $uchr[--$ulen]; 384 } while ($ulen); 385 } else { 386 $s = substr_replace($s, $uchr, $i - $ulen, $ulen); 387 $len += $nlen - $ulen; 388 $i += $nlen - $ulen; 389 } 390 } 391 } 392 } 393 394 if (null === $encoding) { 395 return $s; 396 } 397 398 return self::iconv('UTF-8', $encoding, $s); 399 } 400 401 public static function mb_internal_encoding($encoding = null) 402 { 403 if (null === $encoding) { 404 return self::$internalEncoding; 405 } 406 407 $normalizedEncoding = self::getEncoding($encoding); 408 409 if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { 410 self::$internalEncoding = $normalizedEncoding; 411 412 return true; 413 } 414 415 if (80000 > \PHP_VERSION_ID) { 416 return false; 417 } 418 419 throw new \ValueError(\sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); 420 } 421 422 public static function mb_language($lang = null) 423 { 424 if (null === $lang) { 425 return self::$language; 426 } 427 428 switch ($normalizedLang = strtolower($lang)) { 429 case 'uni': 430 case 'neutral': 431 self::$language = $normalizedLang; 432 433 return true; 434 } 435 436 if (80000 > \PHP_VERSION_ID) { 437 return false; 438 } 439 440 throw new \ValueError(\sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); 441 } 442 443 public static function mb_list_encodings() 444 { 445 return ['UTF-8']; 446 } 447 448 public static function mb_encoding_aliases($encoding) 449 { 450 switch (strtoupper($encoding)) { 451 case 'UTF8': 452 case 'UTF-8': 453 return ['utf8']; 454 } 455 456 return false; 457 } 458 459 public static function mb_check_encoding($var = null, $encoding = null) 460 { 461 if (null === $encoding) { 462 if (null === $var) { 463 return false; 464 } 465 $encoding = self::$internalEncoding; 466 } 467 468 if (!\is_array($var)) { 469 return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); 470 } 471 472 foreach ($var as $key => $value) { 473 if (!self::mb_check_encoding($key, $encoding)) { 474 return false; 475 } 476 if (!self::mb_check_encoding($value, $encoding)) { 477 return false; 478 } 479 } 480 481 return true; 482 } 483 484 public static function mb_detect_encoding($str, $encodingList = null, $strict = false) 485 { 486 if (null === $encodingList) { 487 $encodingList = self::$encodingList; 488 } else { 489 if (!\is_array($encodingList)) { 490 $encodingList = array_map('trim', explode(',', $encodingList)); 491 } 492 $encodingList = array_map('strtoupper', $encodingList); 493 } 494 495 foreach ($encodingList as $enc) { 496 switch ($enc) { 497 case 'ASCII': 498 if (!preg_match('/[\x80-\xFF]/', $str)) { 499 return $enc; 500 } 501 break; 502 503 case 'UTF8': 504 case 'UTF-8': 505 if (preg_match('//u', $str)) { 506 return 'UTF-8'; 507 } 508 break; 509 510 default: 511 if (0 === strncmp($enc, 'ISO-8859-', 9)) { 512 return $enc; 513 } 514 } 515 } 516 517 return false; 518 } 519 520 public static function mb_detect_order($encodingList = null) 521 { 522 if (null === $encodingList) { 523 return self::$encodingList; 524 } 525 526 if (!\is_array($encodingList)) { 527 $encodingList = array_map('trim', explode(',', $encodingList)); 528 } 529 $encodingList = array_map('strtoupper', $encodingList); 530 531 foreach ($encodingList as $enc) { 532 switch ($enc) { 533 default: 534 if (strncmp($enc, 'ISO-8859-', 9)) { 535 return false; 536 } 537 // no break 538 case 'ASCII': 539 case 'UTF8': 540 case 'UTF-8': 541 } 542 } 543 544 self::$encodingList = $encodingList; 545 546 return true; 547 } 548 549 public static function mb_strlen($s, $encoding = null) 550 { 551 $encoding = self::getEncoding($encoding); 552 if ('CP850' === $encoding || 'ASCII' === $encoding) { 553 return \strlen($s); 554 } 555 556 if (false !== $len = @iconv_strlen($s, $encoding)) { 557 return $len; 558 } 559 560 if ('UTF-8' !== $encoding) { 561 return $len; 562 } 563 564 return preg_match_all('/[\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]?|[\xE0-\xEF][\x80-\xBF]{0,2}|[\xF0-\xF7][\x80-\xBF]{0,3}|[\xF8-\xFB][\x80-\xBF]{0,4}|[\xFC-\xFD][\x80-\xBF]{0,5}|[\x80-\xBF\xFE\xFF]/s', $s); 565 } 566 567 public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) 568 { 569 $encoding = self::getEncoding($encoding); 570 if ('CP850' === $encoding || 'ASCII' === $encoding) { 571 return strpos($haystack, $needle, $offset); 572 } 573 574 $needle = (string) $needle; 575 if ('' === $needle) { 576 if (80000 > \PHP_VERSION_ID) { 577 trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); 578 579 return false; 580 } 581 582 return 0; 583 } 584 585 return iconv_strpos($haystack, $needle, $offset, $encoding); 586 } 587 588 public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) 589 { 590 $encoding = self::getEncoding($encoding); 591 if ('CP850' === $encoding || 'ASCII' === $encoding) { 592 return strrpos($haystack, $needle, $offset); 593 } 594 595 if ($offset != (int) $offset) { 596 $offset = 0; 597 } elseif ($offset = (int) $offset) { 598 if ($offset < 0) { 599 if (0 > $offset += self::mb_strlen($needle)) { 600 $haystack = self::mb_substr($haystack, 0, $offset, $encoding); 601 } 602 $offset = 0; 603 } else { 604 $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); 605 } 606 } 607 608 $pos = '' !== $needle || 80000 > \PHP_VERSION_ID 609 ? iconv_strrpos($haystack, $needle, $encoding) 610 : self::mb_strlen($haystack, $encoding); 611 612 return false !== $pos ? $offset + $pos : false; 613 } 614 615 public static function mb_str_split($string, $split_length = 1, $encoding = null) 616 { 617 if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { 618 trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); 619 620 return null; 621 } 622 623 if (1 > $split_length = (int) $split_length) { 624 if (80000 > \PHP_VERSION_ID) { 625 trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); 626 627 return false; 628 } 629 630 throw new \ValueError('Argument #2 ($length) must be greater than 0'); 631 } 632 633 if (null === $encoding) { 634 $encoding = mb_internal_encoding(); 635 } 636 637 if ('UTF-8' === $encoding = self::getEncoding($encoding)) { 638 $rx = '/('; 639 while (65535 < $split_length) { 640 $rx .= '.{65535}'; 641 $split_length -= 65535; 642 } 643 $rx .= '.{'.$split_length.'})/us'; 644 645 return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); 646 } 647 648 $result = []; 649 $length = mb_strlen($string, $encoding); 650 651 for ($i = 0; $i < $length; $i += $split_length) { 652 $result[] = mb_substr($string, $i, $split_length, $encoding); 653 } 654 655 return $result; 656 } 657 658 public static function mb_strtolower($s, $encoding = null) 659 { 660 return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); 661 } 662 663 public static function mb_strtoupper($s, $encoding = null) 664 { 665 return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); 666 } 667 668 public static function mb_substitute_character($c = null) 669 { 670 if (null === $c) { 671 return 'none'; 672 } 673 if (0 === strcasecmp($c, 'none')) { 674 return true; 675 } 676 if (80000 > \PHP_VERSION_ID) { 677 return false; 678 } 679 if (\is_int($c) || 'long' === $c || 'entity' === $c) { 680 return false; 681 } 682 683 throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); 684 } 685 686 public static function mb_substr($s, $start, $length = null, $encoding = null) 687 { 688 $encoding = self::getEncoding($encoding); 689 if ('CP850' === $encoding || 'ASCII' === $encoding) { 690 return (string) substr($s, $start, null === $length ? 2147483647 : $length); 691 } 692 693 if ($start < 0) { 694 $start = iconv_strlen($s, $encoding) + $start; 695 if ($start < 0) { 696 $start = 0; 697 } 698 } 699 700 if (null === $length) { 701 $length = 2147483647; 702 } elseif ($length < 0) { 703 $length = iconv_strlen($s, $encoding) + $length - $start; 704 if ($length < 0) { 705 return ''; 706 } 707 } 708 709 return (string) iconv_substr($s, $start, $length, $encoding); 710 } 711 712 public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) 713 { 714 [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ 715 self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), 716 self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), 717 ]); 718 719 return self::mb_strpos($haystack, $needle, $offset, $encoding); 720 } 721 722 public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) 723 { 724 $pos = self::mb_stripos($haystack, $needle, 0, $encoding); 725 726 return self::getSubpart($pos, $part, $haystack, $encoding); 727 } 728 729 public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) 730 { 731 $encoding = self::getEncoding($encoding); 732 if ('CP850' === $encoding || 'ASCII' === $encoding) { 733 $pos = strrpos($haystack, $needle); 734 } else { 735 $needle = self::mb_substr($needle, 0, 1, $encoding); 736 $pos = iconv_strrpos($haystack, $needle, $encoding); 737 } 738 739 return self::getSubpart($pos, $part, $haystack, $encoding); 740 } 741 742 public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) 743 { 744 $needle = self::mb_substr($needle, 0, 1, $encoding); 745 $pos = self::mb_strripos($haystack, $needle, $encoding); 746 747 return self::getSubpart($pos, $part, $haystack, $encoding); 748 } 749 750 public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) 751 { 752 $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); 753 $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); 754 755 $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); 756 $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); 757 758 return self::mb_strrpos($haystack, $needle, $offset, $encoding); 759 } 760 761 public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) 762 { 763 $pos = strpos($haystack, $needle); 764 if (false === $pos) { 765 return false; 766 } 767 if ($part) { 768 return substr($haystack, 0, $pos); 769 } 770 771 return substr($haystack, $pos); 772 } 773 774 public static function mb_get_info($type = 'all') 775 { 776 $info = [ 777 'internal_encoding' => self::$internalEncoding, 778 'http_output' => 'pass', 779 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', 780 'func_overload' => 0, 781 'func_overload_list' => 'no overload', 782 'mail_charset' => 'UTF-8', 783 'mail_header_encoding' => 'BASE64', 784 'mail_body_encoding' => 'BASE64', 785 'illegal_chars' => 0, 786 'encoding_translation' => 'Off', 787 'language' => self::$language, 788 'detect_order' => self::$encodingList, 789 'substitute_character' => 'none', 790 'strict_detection' => 'Off', 791 ]; 792 793 if ('all' === $type) { 794 return $info; 795 } 796 if (isset($info[$type])) { 797 return $info[$type]; 798 } 799 800 return false; 801 } 802 803 public static function mb_http_input($type = '') 804 { 805 return false; 806 } 807 808 public static function mb_http_output($encoding = null) 809 { 810 return null !== $encoding ? 'pass' === $encoding : 'pass'; 811 } 812 813 public static function mb_strwidth($s, $encoding = null) 814 { 815 $encoding = self::getEncoding($encoding); 816 817 if ('UTF-8' !== $encoding) { 818 $s = self::iconv($encoding, 'UTF-8', $s); 819 } 820 821 $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); 822 823 return ($wide << 1) + iconv_strlen($s, 'UTF-8'); 824 } 825 826 public static function mb_substr_count($haystack, $needle, $encoding = null) 827 { 828 return substr_count($haystack, $needle); 829 } 830 831 public static function mb_output_handler($contents, $status) 832 { 833 return $contents; 834 } 835 836 public static function mb_chr($code, $encoding = null) 837 { 838 if (0x80 > $code %= 0x200000) { 839 $s = \chr($code); 840 } elseif (0x800 > $code) { 841 $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); 842 } elseif (0x10000 > $code) { 843 $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); 844 } else { 845 $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); 846 } 847 848 if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { 849 $s = mb_convert_encoding($s, $encoding, 'UTF-8'); 850 } 851 852 return $s; 853 } 854 855 public static function mb_ord($s, $encoding = null) 856 { 857 if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { 858 $s = mb_convert_encoding($s, 'UTF-8', $encoding); 859 } 860 861 if (1 === \strlen($s)) { 862 return \ord($s); 863 } 864 865 $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; 866 if (0xF0 <= $code) { 867 return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; 868 } 869 if (0xE0 <= $code) { 870 return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; 871 } 872 if (0xC0 <= $code) { 873 return (($code - 0xC0) << 6) + $s[2] - 0x80; 874 } 875 876 return $code; 877 } 878 879 /** @return string|false */ 880 public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null) 881 { 882 if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { 883 if (\PHP_VERSION_ID < 80000) { 884 trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING); 885 886 return false; 887 } 888 889 throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); 890 } 891 892 if (null === $encoding) { 893 $encoding = self::mb_internal_encoding(); 894 } elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) { 895 return false; 896 } 897 898 if (self::mb_strlen($pad_string, $encoding) <= 0) { 899 if (\PHP_VERSION_ID < 80000) { 900 trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING); 901 902 return false; 903 } 904 905 throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); 906 } 907 908 $paddingRequired = $length - self::mb_strlen($string, $encoding); 909 910 if ($paddingRequired < 1) { 911 return $string; 912 } 913 914 switch ($pad_type) { 915 case \STR_PAD_LEFT: 916 return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; 917 case \STR_PAD_RIGHT: 918 return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); 919 default: 920 $leftPaddingLength = floor($paddingRequired / 2); 921 $rightPaddingLength = $paddingRequired - $leftPaddingLength; 922 923 return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); 924 } 925 } 926 927 /** @return string|false */ 928 public static function mb_ucfirst(string $string, ?string $encoding = null) 929 { 930 if (null === $encoding) { 931 $encoding = self::mb_internal_encoding(); 932 } elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { 933 return false; 934 } 935 936 $firstChar = mb_substr($string, 0, 1, $encoding); 937 $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); 938 939 return $firstChar.mb_substr($string, 1, null, $encoding); 940 } 941 942 /** @return string|false */ 943 public static function mb_lcfirst(string $string, ?string $encoding = null) 944 { 945 if (null === $encoding) { 946 $encoding = self::mb_internal_encoding(); 947 } elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { 948 return false; 949 } 950 951 $firstChar = mb_substr($string, 0, 1, $encoding); 952 $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); 953 954 return $firstChar.mb_substr($string, 1, null, $encoding); 955 } 956 957 /** @return string|false */ 958 public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) 959 { 960 return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__); 961 } 962 963 /** @return string|false */ 964 public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) 965 { 966 return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__); 967 } 968 969 /** @return string|false */ 970 public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) 971 { 972 return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__); 973 } 974 975 private static function getSubpart($pos, $part, $haystack, $encoding) 976 { 977 if (false === $pos) { 978 return false; 979 } 980 if ($part) { 981 return self::mb_substr($haystack, 0, $pos, $encoding); 982 } 983 984 return self::mb_substr($haystack, $pos, null, $encoding); 985 } 986 987 private static function html_encoding_callback(array $m) 988 { 989 $i = 1; 990 $entities = ''; 991 $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); 992 993 while (isset($m[$i])) { 994 if (0x80 > $m[$i]) { 995 $entities .= \chr($m[$i++]); 996 continue; 997 } 998 if (0xF0 <= $m[$i]) { 999 $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; 1000 } elseif (0xE0 <= $m[$i]) { 1001 $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; 1002 } else { 1003 $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; 1004 } 1005 1006 $entities .= '&#'.$c.';'; 1007 } 1008 1009 return $entities; 1010 } 1011 1012 private static function title_case(array $s) 1013 { 1014 return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); 1015 } 1016 1017 private static function getData($file) 1018 { 1019 if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { 1020 return require $file; 1021 } 1022 1023 return false; 1024 } 1025 1026 private static function getEncoding($encoding) 1027 { 1028 if (null === $encoding) { 1029 return self::$internalEncoding; 1030 } 1031 1032 if ('UTF-8' === $encoding) { 1033 return 'UTF-8'; 1034 } 1035 1036 $encoding = strtoupper($encoding); 1037 1038 if ('8BIT' === $encoding || 'BINARY' === $encoding) { 1039 return 'CP850'; 1040 } 1041 1042 if ('UTF8' === $encoding) { 1043 return 'UTF-8'; 1044 } 1045 1046 if ('UTF-32' === $encoding) { 1047 return 'UTF-32BE'; 1048 } 1049 1050 if ('UTF-16' === $encoding) { 1051 return 'UTF-16BE'; 1052 } 1053 1054 return $encoding; 1055 } 1056 1057 private static function iconv($fromEncoding, $toEncoding, $s) 1058 { 1059 if (null === self::$iconvSupportsIgnore) { 1060 self::$iconvSupportsIgnore = false !== @iconv('UTF-8', 'UTF-8//IGNORE', ''); 1061 } 1062 1063 return self::$iconvSupportsIgnore 1064 ? iconv($fromEncoding, $toEncoding.'//IGNORE', $s) 1065 : iconv($fromEncoding, $toEncoding, $s); 1066 } 1067 1068 /** @return string|false */ 1069 private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function) 1070 { 1071 if (null === $encoding) { 1072 $encoding = self::mb_internal_encoding(); 1073 } elseif (!self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) { 1074 return false; 1075 } 1076 1077 if ('' === $characters) { 1078 return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding); 1079 } 1080 1081 if ('UTF-8' === $encoding) { 1082 $encoding = null; 1083 if (!preg_match('//u', $string)) { 1084 $string = @self::iconv('UTF-8', 'UTF-8', $string); 1085 } 1086 if (null !== $characters && !preg_match('//u', $characters)) { 1087 $characters = @self::iconv('UTF-8', 'UTF-8', $characters); 1088 } 1089 } else { 1090 $string = self::iconv($encoding, 'UTF-8', $string); 1091 1092 if (null !== $characters) { 1093 $characters = self::iconv($encoding, 'UTF-8', $characters); 1094 } 1095 } 1096 1097 if (null === $characters) { 1098 $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}"; 1099 } else { 1100 $characters = preg_quote($characters); 1101 } 1102 1103 $string = preg_replace(\sprintf($regex, $characters), '', $string); 1104 1105 if (null === $encoding) { 1106 return $string; 1107 } 1108 1109 return self::iconv('UTF-8', $encoding, $string); 1110 } 1111 1112 private static function assertEncoding(string $encoding, string $errorFormat): bool 1113 { 1114 try { 1115 $validEncoding = @self::mb_check_encoding('', $encoding); 1116 } catch (\ValueError $e) { 1117 throw new \ValueError(\sprintf($errorFormat, $encoding)); 1118 } 1119 1120 if (!$validEncoding) { 1121 if (80000 > \PHP_VERSION_ID) { 1122 trigger_error(\sprintf($errorFormat, $encoding), \E_USER_WARNING); 1123 } else { 1124 throw new \ValueError(\sprintf($errorFormat, $encoding)); 1125 } 1126 } 1127 1128 return $validEncoding; 1129 } 1130 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Sun Jul 12 05:10:03 2026 | Cross-referenced by PHPXref 0.7.1 |