PluginProbe ʕ •ᴥ•ʔ
Transferito: WP Migration / trunk
Transferito: WP Migration vtrunk
trunk 11.4.0 12.0.0 13.1.0 14.0.0 14.0.11 14.0.7 14.1.0 14.1.1 14.1.2 14.1.3 14.1.4
transferito / vendor / symfony / polyfill-mbstring / Mbstring.php
transferito / vendor / symfony / polyfill-mbstring Last commit date
Resources 11 months ago LICENSE 11 months ago Mbstring.php 11 months ago README.md 11 months ago bootstrap.php 11 months ago bootstrap80.php 11 months ago composer.json 11 months ago
Mbstring.php
948 lines
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
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 *
52 * Not implemented:
53 * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
54 * - mb_ereg_* - Regular expression with multibyte support
55 * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
56 * - mb_preferred_mime_name - Get MIME charset string
57 * - mb_regex_encoding - Returns current encoding for multibyte regex as string
58 * - mb_regex_set_options - Set/Get the default options for mbregex functions
59 * - mb_send_mail - Send encoded mail
60 * - mb_split - Split multibyte string using regular expression
61 * - mb_strcut - Get part of string
62 * - mb_strimwidth - Get truncated string with specified width
63 *
64 * @author Nicolas Grekas <p@tchwork.com>
65 *
66 * @internal
67 */
68 final class Mbstring
69 {
70 public const MB_CASE_FOLD = \PHP_INT_MAX;
71
72 private const SIMPLE_CASE_FOLD = [
73 ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"],
74 ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'],
75 ];
76
77 private static $encodingList = ['ASCII', 'UTF-8'];
78 private static $language = 'neutral';
79 private static $internalEncoding = 'UTF-8';
80
81 public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
82 {
83 if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) {
84 $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
85 } else {
86 $fromEncoding = self::getEncoding($fromEncoding);
87 }
88
89 $toEncoding = self::getEncoding($toEncoding);
90
91 if ('BASE64' === $fromEncoding) {
92 $s = base64_decode($s);
93 $fromEncoding = $toEncoding;
94 }
95
96 if ('BASE64' === $toEncoding) {
97 return base64_encode($s);
98 }
99
100 if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
101 if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
102 $fromEncoding = 'Windows-1252';
103 }
104 if ('UTF-8' !== $fromEncoding) {
105 $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
106 }
107
108 return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s);
109 }
110
111 if ('HTML-ENTITIES' === $fromEncoding) {
112 $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8');
113 $fromEncoding = 'UTF-8';
114 }
115
116 return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
117 }
118
119 public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
120 {
121 $ok = true;
122 array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
123 if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
124 $ok = false;
125 }
126 });
127
128 return $ok ? $fromEncoding : false;
129 }
130
131 public static function mb_decode_mimeheader($s)
132 {
133 return iconv_mime_decode($s, 2, self::$internalEncoding);
134 }
135
136 public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
137 {
138 trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING);
139 }
140
141 public static function mb_decode_numericentity($s, $convmap, $encoding = null)
142 {
143 if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
144 trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
145
146 return null;
147 }
148
149 if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
150 return false;
151 }
152
153 if (null !== $encoding && !\is_scalar($encoding)) {
154 trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
155
156 return ''; // Instead of null (cf. mb_encode_numericentity).
157 }
158
159 $s = (string) $s;
160 if ('' === $s) {
161 return '';
162 }
163
164 $encoding = self::getEncoding($encoding);
165
166 if ('UTF-8' === $encoding) {
167 $encoding = null;
168 if (!preg_match('//u', $s)) {
169 $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
170 }
171 } else {
172 $s = iconv($encoding, 'UTF-8//IGNORE', $s);
173 }
174
175 $cnt = floor(\count($convmap) / 4) * 4;
176
177 for ($i = 0; $i < $cnt; $i += 4) {
178 // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
179 $convmap[$i] += $convmap[$i + 2];
180 $convmap[$i + 1] += $convmap[$i + 2];
181 }
182
183 $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
184 $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
185 for ($i = 0; $i < $cnt; $i += 4) {
186 if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
187 return self::mb_chr($c - $convmap[$i + 2]);
188 }
189 }
190
191 return $m[0];
192 }, $s);
193
194 if (null === $encoding) {
195 return $s;
196 }
197
198 return iconv('UTF-8', $encoding.'//IGNORE', $s);
199 }
200
201 public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
202 {
203 if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
204 trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
205
206 return null;
207 }
208
209 if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
210 return false;
211 }
212
213 if (null !== $encoding && !\is_scalar($encoding)) {
214 trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
215
216 return null; // Instead of '' (cf. mb_decode_numericentity).
217 }
218
219 if (null !== $is_hex && !\is_scalar($is_hex)) {
220 trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING);
221
222 return null;
223 }
224
225 $s = (string) $s;
226 if ('' === $s) {
227 return '';
228 }
229
230 $encoding = self::getEncoding($encoding);
231
232 if ('UTF-8' === $encoding) {
233 $encoding = null;
234 if (!preg_match('//u', $s)) {
235 $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
236 }
237 } else {
238 $s = iconv($encoding, 'UTF-8//IGNORE', $s);
239 }
240
241 static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
242
243 $cnt = floor(\count($convmap) / 4) * 4;
244 $i = 0;
245 $len = \strlen($s);
246 $result = '';
247
248 while ($i < $len) {
249 $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
250 $uchr = substr($s, $i, $ulen);
251 $i += $ulen;
252 $c = self::mb_ord($uchr);
253
254 for ($j = 0; $j < $cnt; $j += 4) {
255 if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
256 $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
257 $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
258 continue 2;
259 }
260 }
261 $result .= $uchr;
262 }
263
264 if (null === $encoding) {
265 return $result;
266 }
267
268 return iconv('UTF-8', $encoding.'//IGNORE', $result);
269 }
270
271 public static function mb_convert_case($s, $mode, $encoding = null)
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 = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
284 }
285 } else {
286 $s = iconv($encoding, 'UTF-8//IGNORE', $s);
287 }
288
289 if (\MB_CASE_TITLE == $mode) {
290 static $titleRegexp = null;
291 if (null === $titleRegexp) {
292 $titleRegexp = self::getData('titleCaseRegexp');
293 }
294 $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s);
295 } else {
296 if (\MB_CASE_UPPER == $mode) {
297 static $upper = null;
298 if (null === $upper) {
299 $upper = self::getData('upperCase');
300 }
301 $map = $upper;
302 } else {
303 if (self::MB_CASE_FOLD === $mode) {
304 static $caseFolding = null;
305 if (null === $caseFolding) {
306 $caseFolding = self::getData('caseFolding');
307 }
308 $s = strtr($s, $caseFolding);
309 }
310
311 static $lower = null;
312 if (null === $lower) {
313 $lower = self::getData('lowerCase');
314 }
315 $map = $lower;
316 }
317
318 static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
319
320 $i = 0;
321 $len = \strlen($s);
322
323 while ($i < $len) {
324 $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
325 $uchr = substr($s, $i, $ulen);
326 $i += $ulen;
327
328 if (isset($map[$uchr])) {
329 $uchr = $map[$uchr];
330 $nlen = \strlen($uchr);
331
332 if ($nlen == $ulen) {
333 $nlen = $i;
334 do {
335 $s[--$nlen] = $uchr[--$ulen];
336 } while ($ulen);
337 } else {
338 $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
339 $len += $nlen - $ulen;
340 $i += $nlen - $ulen;
341 }
342 }
343 }
344 }
345
346 if (null === $encoding) {
347 return $s;
348 }
349
350 return iconv('UTF-8', $encoding.'//IGNORE', $s);
351 }
352
353 public static function mb_internal_encoding($encoding = null)
354 {
355 if (null === $encoding) {
356 return self::$internalEncoding;
357 }
358
359 $normalizedEncoding = self::getEncoding($encoding);
360
361 if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
362 self::$internalEncoding = $normalizedEncoding;
363
364 return true;
365 }
366
367 if (80000 > \PHP_VERSION_ID) {
368 return false;
369 }
370
371 throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
372 }
373
374 public static function mb_language($lang = null)
375 {
376 if (null === $lang) {
377 return self::$language;
378 }
379
380 switch ($normalizedLang = strtolower($lang)) {
381 case 'uni':
382 case 'neutral':
383 self::$language = $normalizedLang;
384
385 return true;
386 }
387
388 if (80000 > \PHP_VERSION_ID) {
389 return false;
390 }
391
392 throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
393 }
394
395 public static function mb_list_encodings()
396 {
397 return ['UTF-8'];
398 }
399
400 public static function mb_encoding_aliases($encoding)
401 {
402 switch (strtoupper($encoding)) {
403 case 'UTF8':
404 case 'UTF-8':
405 return ['utf8'];
406 }
407
408 return false;
409 }
410
411 public static function mb_check_encoding($var = null, $encoding = null)
412 {
413 if (PHP_VERSION_ID < 70200 && \is_array($var)) {
414 trigger_error('mb_check_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING);
415
416 return null;
417 }
418
419 if (null === $encoding) {
420 if (null === $var) {
421 return false;
422 }
423 $encoding = self::$internalEncoding;
424 }
425
426 if (!\is_array($var)) {
427 return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var);
428 }
429
430 foreach ($var as $key => $value) {
431 if (!self::mb_check_encoding($key, $encoding)) {
432 return false;
433 }
434 if (!self::mb_check_encoding($value, $encoding)) {
435 return false;
436 }
437 }
438
439 return true;
440
441 }
442
443 public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
444 {
445 if (null === $encodingList) {
446 $encodingList = self::$encodingList;
447 } else {
448 if (!\is_array($encodingList)) {
449 $encodingList = array_map('trim', explode(',', $encodingList));
450 }
451 $encodingList = array_map('strtoupper', $encodingList);
452 }
453
454 foreach ($encodingList as $enc) {
455 switch ($enc) {
456 case 'ASCII':
457 if (!preg_match('/[\x80-\xFF]/', $str)) {
458 return $enc;
459 }
460 break;
461
462 case 'UTF8':
463 case 'UTF-8':
464 if (preg_match('//u', $str)) {
465 return 'UTF-8';
466 }
467 break;
468
469 default:
470 if (0 === strncmp($enc, 'ISO-8859-', 9)) {
471 return $enc;
472 }
473 }
474 }
475
476 return false;
477 }
478
479 public static function mb_detect_order($encodingList = null)
480 {
481 if (null === $encodingList) {
482 return self::$encodingList;
483 }
484
485 if (!\is_array($encodingList)) {
486 $encodingList = array_map('trim', explode(',', $encodingList));
487 }
488 $encodingList = array_map('strtoupper', $encodingList);
489
490 foreach ($encodingList as $enc) {
491 switch ($enc) {
492 default:
493 if (strncmp($enc, 'ISO-8859-', 9)) {
494 return false;
495 }
496 // no break
497 case 'ASCII':
498 case 'UTF8':
499 case 'UTF-8':
500 }
501 }
502
503 self::$encodingList = $encodingList;
504
505 return true;
506 }
507
508 public static function mb_strlen($s, $encoding = null)
509 {
510 $encoding = self::getEncoding($encoding);
511 if ('CP850' === $encoding || 'ASCII' === $encoding) {
512 return \strlen($s);
513 }
514
515 return @iconv_strlen($s, $encoding);
516 }
517
518 public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
519 {
520 $encoding = self::getEncoding($encoding);
521 if ('CP850' === $encoding || 'ASCII' === $encoding) {
522 return strpos($haystack, $needle, $offset);
523 }
524
525 $needle = (string) $needle;
526 if ('' === $needle) {
527 if (80000 > \PHP_VERSION_ID) {
528 trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING);
529
530 return false;
531 }
532
533 return 0;
534 }
535
536 return iconv_strpos($haystack, $needle, $offset, $encoding);
537 }
538
539 public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
540 {
541 $encoding = self::getEncoding($encoding);
542 if ('CP850' === $encoding || 'ASCII' === $encoding) {
543 return strrpos($haystack, $needle, $offset);
544 }
545
546 if ($offset != (int) $offset) {
547 $offset = 0;
548 } elseif ($offset = (int) $offset) {
549 if ($offset < 0) {
550 if (0 > $offset += self::mb_strlen($needle)) {
551 $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
552 }
553 $offset = 0;
554 } else {
555 $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
556 }
557 }
558
559 $pos = '' !== $needle || 80000 > \PHP_VERSION_ID
560 ? iconv_strrpos($haystack, $needle, $encoding)
561 : self::mb_strlen($haystack, $encoding);
562
563 return false !== $pos ? $offset + $pos : false;
564 }
565
566 public static function mb_str_split($string, $split_length = 1, $encoding = null)
567 {
568 if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
569 trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING);
570
571 return null;
572 }
573
574 if (1 > $split_length = (int) $split_length) {
575 if (80000 > \PHP_VERSION_ID) {
576 trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);
577
578 return false;
579 }
580
581 throw new \ValueError('Argument #2 ($length) must be greater than 0');
582 }
583
584 if (null === $encoding) {
585 $encoding = mb_internal_encoding();
586 }
587
588 if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
589 $rx = '/(';
590 while (65535 < $split_length) {
591 $rx .= '.{65535}';
592 $split_length -= 65535;
593 }
594 $rx .= '.{'.$split_length.'})/us';
595
596 return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
597 }
598
599 $result = [];
600 $length = mb_strlen($string, $encoding);
601
602 for ($i = 0; $i < $length; $i += $split_length) {
603 $result[] = mb_substr($string, $i, $split_length, $encoding);
604 }
605
606 return $result;
607 }
608
609 public static function mb_strtolower($s, $encoding = null)
610 {
611 return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding);
612 }
613
614 public static function mb_strtoupper($s, $encoding = null)
615 {
616 return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding);
617 }
618
619 public static function mb_substitute_character($c = null)
620 {
621 if (null === $c) {
622 return 'none';
623 }
624 if (0 === strcasecmp($c, 'none')) {
625 return true;
626 }
627 if (80000 > \PHP_VERSION_ID) {
628 return false;
629 }
630 if (\is_int($c) || 'long' === $c || 'entity' === $c) {
631 return false;
632 }
633
634 throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint');
635 }
636
637 public static function mb_substr($s, $start, $length = null, $encoding = null)
638 {
639 $encoding = self::getEncoding($encoding);
640 if ('CP850' === $encoding || 'ASCII' === $encoding) {
641 return (string) substr($s, $start, null === $length ? 2147483647 : $length);
642 }
643
644 if ($start < 0) {
645 $start = iconv_strlen($s, $encoding) + $start;
646 if ($start < 0) {
647 $start = 0;
648 }
649 }
650
651 if (null === $length) {
652 $length = 2147483647;
653 } elseif ($length < 0) {
654 $length = iconv_strlen($s, $encoding) + $length - $start;
655 if ($length < 0) {
656 return '';
657 }
658 }
659
660 return (string) iconv_substr($s, $start, $length, $encoding);
661 }
662
663 public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
664 {
665 [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [
666 self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding),
667 self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding),
668 ]);
669
670 return self::mb_strpos($haystack, $needle, $offset, $encoding);
671 }
672
673 public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
674 {
675 $pos = self::mb_stripos($haystack, $needle, 0, $encoding);
676
677 return self::getSubpart($pos, $part, $haystack, $encoding);
678 }
679
680 public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
681 {
682 $encoding = self::getEncoding($encoding);
683 if ('CP850' === $encoding || 'ASCII' === $encoding) {
684 $pos = strrpos($haystack, $needle);
685 } else {
686 $needle = self::mb_substr($needle, 0, 1, $encoding);
687 $pos = iconv_strrpos($haystack, $needle, $encoding);
688 }
689
690 return self::getSubpart($pos, $part, $haystack, $encoding);
691 }
692
693 public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
694 {
695 $needle = self::mb_substr($needle, 0, 1, $encoding);
696 $pos = self::mb_strripos($haystack, $needle, $encoding);
697
698 return self::getSubpart($pos, $part, $haystack, $encoding);
699 }
700
701 public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
702 {
703 $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding);
704 $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding);
705
706 $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack);
707 $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle);
708
709 return self::mb_strrpos($haystack, $needle, $offset, $encoding);
710 }
711
712 public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
713 {
714 $pos = strpos($haystack, $needle);
715 if (false === $pos) {
716 return false;
717 }
718 if ($part) {
719 return substr($haystack, 0, $pos);
720 }
721
722 return substr($haystack, $pos);
723 }
724
725 public static function mb_get_info($type = 'all')
726 {
727 $info = [
728 'internal_encoding' => self::$internalEncoding,
729 'http_output' => 'pass',
730 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
731 'func_overload' => 0,
732 'func_overload_list' => 'no overload',
733 'mail_charset' => 'UTF-8',
734 'mail_header_encoding' => 'BASE64',
735 'mail_body_encoding' => 'BASE64',
736 'illegal_chars' => 0,
737 'encoding_translation' => 'Off',
738 'language' => self::$language,
739 'detect_order' => self::$encodingList,
740 'substitute_character' => 'none',
741 'strict_detection' => 'Off',
742 ];
743
744 if ('all' === $type) {
745 return $info;
746 }
747 if (isset($info[$type])) {
748 return $info[$type];
749 }
750
751 return false;
752 }
753
754 public static function mb_http_input($type = '')
755 {
756 return false;
757 }
758
759 public static function mb_http_output($encoding = null)
760 {
761 return null !== $encoding ? 'pass' === $encoding : 'pass';
762 }
763
764 public static function mb_strwidth($s, $encoding = null)
765 {
766 $encoding = self::getEncoding($encoding);
767
768 if ('UTF-8' !== $encoding) {
769 $s = iconv($encoding, 'UTF-8//IGNORE', $s);
770 }
771
772 $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);
773
774 return ($wide << 1) + iconv_strlen($s, 'UTF-8');
775 }
776
777 public static function mb_substr_count($haystack, $needle, $encoding = null)
778 {
779 return substr_count($haystack, $needle);
780 }
781
782 public static function mb_output_handler($contents, $status)
783 {
784 return $contents;
785 }
786
787 public static function mb_chr($code, $encoding = null)
788 {
789 if (0x80 > $code %= 0x200000) {
790 $s = \chr($code);
791 } elseif (0x800 > $code) {
792 $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
793 } elseif (0x10000 > $code) {
794 $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
795 } else {
796 $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
797 }
798
799 if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
800 $s = mb_convert_encoding($s, $encoding, 'UTF-8');
801 }
802
803 return $s;
804 }
805
806 public static function mb_ord($s, $encoding = null)
807 {
808 if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
809 $s = mb_convert_encoding($s, 'UTF-8', $encoding);
810 }
811
812 if (1 === \strlen($s)) {
813 return \ord($s);
814 }
815
816 $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
817 if (0xF0 <= $code) {
818 return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
819 }
820 if (0xE0 <= $code) {
821 return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
822 }
823 if (0xC0 <= $code) {
824 return (($code - 0xC0) << 6) + $s[2] - 0x80;
825 }
826
827 return $code;
828 }
829
830 public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, string $encoding = null): string
831 {
832 if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
833 throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
834 }
835
836 if (null === $encoding) {
837 $encoding = self::mb_internal_encoding();
838 }
839
840 try {
841 $validEncoding = @self::mb_check_encoding('', $encoding);
842 } catch (\ValueError $e) {
843 throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
844 }
845
846 // BC for PHP 7.3 and lower
847 if (!$validEncoding) {
848 throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
849 }
850
851 if (self::mb_strlen($pad_string, $encoding) <= 0) {
852 throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
853 }
854
855 $paddingRequired = $length - self::mb_strlen($string, $encoding);
856
857 if ($paddingRequired < 1) {
858 return $string;
859 }
860
861 switch ($pad_type) {
862 case \STR_PAD_LEFT:
863 return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string;
864 case \STR_PAD_RIGHT:
865 return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding);
866 default:
867 $leftPaddingLength = floor($paddingRequired / 2);
868 $rightPaddingLength = $paddingRequired - $leftPaddingLength;
869
870 return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
871 }
872 }
873
874 private static function getSubpart($pos, $part, $haystack, $encoding)
875 {
876 if (false === $pos) {
877 return false;
878 }
879 if ($part) {
880 return self::mb_substr($haystack, 0, $pos, $encoding);
881 }
882
883 return self::mb_substr($haystack, $pos, null, $encoding);
884 }
885
886 private static function html_encoding_callback(array $m)
887 {
888 $i = 1;
889 $entities = '';
890 $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));
891
892 while (isset($m[$i])) {
893 if (0x80 > $m[$i]) {
894 $entities .= \chr($m[$i++]);
895 continue;
896 }
897 if (0xF0 <= $m[$i]) {
898 $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
899 } elseif (0xE0 <= $m[$i]) {
900 $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
901 } else {
902 $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
903 }
904
905 $entities .= '&#'.$c.';';
906 }
907
908 return $entities;
909 }
910
911 private static function title_case(array $s)
912 {
913 return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
914 }
915
916 private static function getData($file)
917 {
918 if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
919 return require $file;
920 }
921
922 return false;
923 }
924
925 private static function getEncoding($encoding)
926 {
927 if (null === $encoding) {
928 return self::$internalEncoding;
929 }
930
931 if ('UTF-8' === $encoding) {
932 return 'UTF-8';
933 }
934
935 $encoding = strtoupper($encoding);
936
937 if ('8BIT' === $encoding || 'BINARY' === $encoding) {
938 return 'CP850';
939 }
940
941 if ('UTF8' === $encoding) {
942 return 'UTF-8';
943 }
944
945 return $encoding;
946 }
947 }
948