PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.1
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / vendor / symfony / polyfill-mbstring / Mbstring.php

Mbstring.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 11.0.1, at vendor/symfony/polyfill-mbstring/Mbstring.php

1,057 lines 35.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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
86 public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
87 {
88 if (\is_array($s)) {
89 $r = [];
90 foreach ($s as $str) {
91 $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding);
92 }
93
94 return $r;
95 }
96
97 if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) {
98 $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
99 } else {
100 $fromEncoding = self::getEncoding($fromEncoding);
101 }
102
103 $toEncoding = self::getEncoding($toEncoding);
104
105 if ('BASE64' === $fromEncoding) {
106 $s = base64_decode($s);
107 $fromEncoding = $toEncoding;
108 }
109
110 if ('BASE64' === $toEncoding) {
111 return base64_encode($s);
112 }
113
114 if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
115 if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
116 $fromEncoding = 'Windows-1252';
117 }
118 if ('UTF-8' !== $fromEncoding) {
119 $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
120 }
121
122 return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s);
123 }
124
125 if ('HTML-ENTITIES' === $fromEncoding) {
126 $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8');
127 $fromEncoding = 'UTF-8';
128 }
129
130 return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
131 }
132
133 public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
134 {
135 $ok = true;
136 array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
137 if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
138 $ok = false;
139 }
140 });
141
142 return $ok ? $fromEncoding : false;
143 }
144
145 public static function mb_decode_mimeheader($s)
146 {
147 return iconv_mime_decode($s, 2, self::$internalEncoding);
148 }
149
150 public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
151 {
152 trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING);
153 }
154
155 public static function mb_decode_numericentity($s, $convmap, $encoding = null)
156 {
157 if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
158 trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
159
160 return null;
161 }
162
163 if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
164 return false;
165 }
166
167 if (null !== $encoding && !\is_scalar($encoding)) {
168 trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
169
170 return ''; // Instead of null (cf. mb_encode_numericentity).
171 }
172
173 $s = (string)$s;
174 if ('' === $s) {
175 return '';
176 }
177
178 $encoding = self::getEncoding($encoding);
179
180 if ('UTF-8' === $encoding) {
181 $encoding = null;
182 if (!preg_match('//u', $s)) {
183 $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
184 }
185 } else {
186 $s = iconv($encoding, 'UTF-8//IGNORE', $s);
187 }
188
189 $cnt = floor(\count($convmap) / 4) * 4;
190
191 for ($i = 0; $i < $cnt; $i += 4) {
192 // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
193 $convmap[$i] += $convmap[$i + 2];
194 $convmap[$i + 1] += $convmap[$i + 2];
195 }
196
197 $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
198 $c = isset($m[2]) ? (int)hexdec($m[2]) : $m[1];
199 for ($i = 0; $i < $cnt; $i += 4) {
200 if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
201 return self::mb_chr($c - $convmap[$i + 2]);
202 }
203 }
204
205 return $m[0];
206 }, $s);
207
208 if (null === $encoding) {
209 return $s;
210 }
211
212 return iconv('UTF-8', $encoding.'//IGNORE', $s);
213 }
214
215 public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
216 {
217 if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
218 trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
219
220 return null;
221 }
222
223 if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
224 return false;
225 }
226
227 if (null !== $encoding && !\is_scalar($encoding)) {
228 trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
229
230 return null; // Instead of '' (cf. mb_decode_numericentity).
231 }
232
233 if (null !== $is_hex && !\is_scalar($is_hex)) {
234 trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING);
235
236 return null;
237 }
238
239 $s = (string)$s;
240 if ('' === $s) {
241 return '';
242 }
243
244 $encoding = self::getEncoding($encoding);
245
246 if ('UTF-8' === $encoding) {
247 $encoding = null;
248 if (!preg_match('//u', $s)) {
249 $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
250 }
251 } else {
252 $s = iconv($encoding, 'UTF-8//IGNORE', $s);
253 }
254
255 static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
256
257 $cnt = floor(\count($convmap) / 4) * 4;
258 $i = 0;
259 $len = \strlen($s);
260 $result = '';
261
262 while ($i < $len) {
263 $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
264 $uchr = substr($s, $i, $ulen);
265 $i += $ulen;
266 $c = self::mb_ord($uchr);
267
268 for ($j = 0; $j < $cnt; $j += 4) {
269 if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
270 $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
271 $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
272 continue 2;
273 }
274 }
275 $result .= $uchr;
276 }
277
278 if (null === $encoding) {
279 return $result;
280 }
281
282 return iconv('UTF-8', $encoding.'//IGNORE', $result);
283 }
284
285 public static function mb_convert_case($s, $mode, $encoding = null)
286 {
287 $s = (string)$s;
288 if ('' === $s) {
289 return '';
290 }
291
292 $encoding = self::getEncoding($encoding);
293
294 if ('UTF-8' === $encoding) {
295 $encoding = null;
296 if (!preg_match('//u', $s)) {
297 $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
298 }
299 } else {
300 $s = iconv($encoding, 'UTF-8//IGNORE', $s);
301 }
302
303 if (\MB_CASE_TITLE == $mode) {
304 static $titleRegexp = null;
305 if (null === $titleRegexp) {
306 $titleRegexp = self::getData('titleCaseRegexp');
307 }
308 $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s);
309 } else {
310 if (\MB_CASE_UPPER == $mode) {
311 static $upper = null;
312 if (null === $upper) {
313 $upper = self::getData('upperCase');
314 }
315 $map = $upper;
316 } else {
317 if (self::MB_CASE_FOLD === $mode) {
318 static $caseFolding = null;
319 if (null === $caseFolding) {
320 $caseFolding = self::getData('caseFolding');
321 }
322 $s = strtr($s, $caseFolding);
323 }
324
325 static $lower = null;
326 if (null === $lower) {
327 $lower = self::getData('lowerCase');
328 }
329 $map = $lower;
330 }
331
332 static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
333
334 $i = 0;
335 $len = \strlen($s);
336
337 while ($i < $len) {
338 $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
339 $uchr = substr($s, $i, $ulen);
340 $i += $ulen;
341
342 if (isset($map[$uchr])) {
343 $uchr = $map[$uchr];
344 $nlen = \strlen($uchr);
345
346 if ($nlen == $ulen) {
347 $nlen = $i;
348 do {
349 $s[--$nlen] = $uchr[--$ulen];
350 } while ($ulen);
351 } else {
352 $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
353 $len += $nlen - $ulen;
354 $i += $nlen - $ulen;
355 }
356 }
357 }
358 }
359
360 if (null === $encoding) {
361 return $s;
362 }
363
364 return iconv('UTF-8', $encoding.'//IGNORE', $s);
365 }
366
367 public static function mb_internal_encoding($encoding = null)
368 {
369 if (null === $encoding) {
370 return self::$internalEncoding;
371 }
372
373 $normalizedEncoding = self::getEncoding($encoding);
374
375 if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
376 self::$internalEncoding = $normalizedEncoding;
377
378 return true;
379 }
380
381 if (80000 > \PHP_VERSION_ID) {
382 return false;
383 }
384
385 throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
386 }
387
388 public static function mb_language($lang = null)
389 {
390 if (null === $lang) {
391 return self::$language;
392 }
393
394 switch ($normalizedLang = strtolower($lang)) {
395 case 'uni':
396 case 'neutral':
397 self::$language = $normalizedLang;
398
399 return true;
400 }
401
402 if (80000 > \PHP_VERSION_ID) {
403 return false;
404 }
405
406 throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
407 }
408
409 public static function mb_list_encodings()
410 {
411 return ['UTF-8'];
412 }
413
414 public static function mb_encoding_aliases($encoding)
415 {
416 switch (strtoupper($encoding)) {
417 case 'UTF8':
418 case 'UTF-8':
419 return ['utf8'];
420 }
421
422 return false;
423 }
424
425 public static function mb_check_encoding($var = null, $encoding = null)
426 {
427 if (null === $encoding) {
428 if (null === $var) {
429 return false;
430 }
431 $encoding = self::$internalEncoding;
432 }
433
434 if (!\is_array($var)) {
435 return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var);
436 }
437
438 foreach ($var as $key => $value) {
439 if (!self::mb_check_encoding($key, $encoding)) {
440 return false;
441 }
442 if (!self::mb_check_encoding($value, $encoding)) {
443 return false;
444 }
445 }
446
447 return true;
448 }
449
450 public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
451 {
452 if (null === $encodingList) {
453 $encodingList = self::$encodingList;
454 } else {
455 if (!\is_array($encodingList)) {
456 $encodingList = array_map('trim', explode(',', $encodingList));
457 }
458 $encodingList = array_map('strtoupper', $encodingList);
459 }
460
461 foreach ($encodingList as $enc) {
462 switch ($enc) {
463 case 'ASCII':
464 if (!preg_match('/[\x80-\xFF]/', $str)) {
465 return $enc;
466 }
467 break;
468
469 case 'UTF8':
470 case 'UTF-8':
471 if (preg_match('//u', $str)) {
472 return 'UTF-8';
473 }
474 break;
475
476 default:
477 if (0 === strncmp($enc, 'ISO-8859-', 9)) {
478 return $enc;
479 }
480 }
481 }
482
483 return false;
484 }
485
486 public static function mb_detect_order($encodingList = null)
487 {
488 if (null === $encodingList) {
489 return self::$encodingList;
490 }
491
492 if (!\is_array($encodingList)) {
493 $encodingList = array_map('trim', explode(',', $encodingList));
494 }
495 $encodingList = array_map('strtoupper', $encodingList);
496
497 foreach ($encodingList as $enc) {
498 switch ($enc) {
499 default:
500 if (strncmp($enc, 'ISO-8859-', 9)) {
501 return false;
502 }
503 // no break
504 case 'ASCII':
505 case 'UTF8':
506 case 'UTF-8':
507 }
508 }
509
510 self::$encodingList = $encodingList;
511
512 return true;
513 }
514
515 public static function mb_strlen($s, $encoding = null)
516 {
517 $encoding = self::getEncoding($encoding);
518 if ('CP850' === $encoding || 'ASCII' === $encoding) {
519 return \strlen($s);
520 }
521
522 return @iconv_strlen($s, $encoding);
523 }
524
525 public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
526 {
527 $encoding = self::getEncoding($encoding);
528 if ('CP850' === $encoding || 'ASCII' === $encoding) {
529 return strpos($haystack, $needle, $offset);
530 }
531
532 $needle = (string)$needle;
533 if ('' === $needle) {
534 if (80000 > \PHP_VERSION_ID) {
535 trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING);
536
537 return false;
538 }
539
540 return 0;
541 }
542
543 return iconv_strpos($haystack, $needle, $offset, $encoding);
544 }
545
546 public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
547 {
548 $encoding = self::getEncoding($encoding);
549 if ('CP850' === $encoding || 'ASCII' === $encoding) {
550 return strrpos($haystack, $needle, $offset);
551 }
552
553 if ($offset != (int)$offset) {
554 $offset = 0;
555 } elseif ($offset = (int)$offset) {
556 if ($offset < 0) {
557 if (0 > $offset += self::mb_strlen($needle)) {
558 $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
559 }
560 $offset = 0;
561 } else {
562 $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
563 }
564 }
565
566 $pos = '' !== $needle || 80000 > \PHP_VERSION_ID
567 ? iconv_strrpos($haystack, $needle, $encoding)
568 : self::mb_strlen($haystack, $encoding);
569
570 return false !== $pos ? $offset + $pos : false;
571 }
572
573 public static function mb_str_split($string, $split_length = 1, $encoding = null)
574 {
575 if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
576 trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING);
577
578 return null;
579 }
580
581 if (1 > $split_length = (int)$split_length) {
582 if (80000 > \PHP_VERSION_ID) {
583 trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);
584
585 return false;
586 }
587
588 throw new \ValueError('Argument #2 ($length) must be greater than 0');
589 }
590
591 if (null === $encoding) {
592 $encoding = mb_internal_encoding();
593 }
594
595 if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
596 $rx = '/(';
597 while (65535 < $split_length) {
598 $rx .= '.{65535}';
599 $split_length -= 65535;
600 }
601 $rx .= '.{'.$split_length.'})/us';
602
603 return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
604 }
605
606 $result = [];
607 $length = mb_strlen($string, $encoding);
608
609 for ($i = 0; $i < $length; $i += $split_length) {
610 $result[] = mb_substr($string, $i, $split_length, $encoding);
611 }
612
613 return $result;
614 }
615
616 public static function mb_strtolower($s, $encoding = null)
617 {
618 return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding);
619 }
620
621 public static function mb_strtoupper($s, $encoding = null)
622 {
623 return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding);
624 }
625
626 public static function mb_substitute_character($c = null)
627 {
628 if (null === $c) {
629 return 'none';
630 }
631 if (0 === strcasecmp($c, 'none')) {
632 return true;
633 }
634 if (80000 > \PHP_VERSION_ID) {
635 return false;
636 }
637 if (\is_int($c) || 'long' === $c || 'entity' === $c) {
638 return false;
639 }
640
641 throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint');
642 }
643
644 public static function mb_substr($s, $start, $length = null, $encoding = null)
645 {
646 $encoding = self::getEncoding($encoding);
647 if ('CP850' === $encoding || 'ASCII' === $encoding) {
648 return (string)substr($s, $start, null === $length ? 2147483647 : $length);
649 }
650
651 if ($start < 0) {
652 $start = iconv_strlen($s, $encoding) + $start;
653 if ($start < 0) {
654 $start = 0;
655 }
656 }
657
658 if (null === $length) {
659 $length = 2147483647;
660 } elseif ($length < 0) {
661 $length = iconv_strlen($s, $encoding) + $length - $start;
662 if ($length < 0) {
663 return '';
664 }
665 }
666
667 return (string)iconv_substr($s, $start, $length, $encoding);
668 }
669
670 public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
671 {
672 [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [
673 self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding),
674 self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding),
675 ]);
676
677 return self::mb_strpos($haystack, $needle, $offset, $encoding);
678 }
679
680 public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
681 {
682 $pos = self::mb_stripos($haystack, $needle, 0, $encoding);
683
684 return self::getSubpart($pos, $part, $haystack, $encoding);
685 }
686
687 public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
688 {
689 $encoding = self::getEncoding($encoding);
690 if ('CP850' === $encoding || 'ASCII' === $encoding) {
691 $pos = strrpos($haystack, $needle);
692 } else {
693 $needle = self::mb_substr($needle, 0, 1, $encoding);
694 $pos = iconv_strrpos($haystack, $needle, $encoding);
695 }
696
697 return self::getSubpart($pos, $part, $haystack, $encoding);
698 }
699
700 public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
701 {
702 $needle = self::mb_substr($needle, 0, 1, $encoding);
703 $pos = self::mb_strripos($haystack, $needle, $encoding);
704
705 return self::getSubpart($pos, $part, $haystack, $encoding);
706 }
707
708 public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
709 {
710 $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding);
711 $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding);
712
713 $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack);
714 $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle);
715
716 return self::mb_strrpos($haystack, $needle, $offset, $encoding);
717 }
718
719 public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
720 {
721 $pos = strpos($haystack, $needle);
722 if (false === $pos) {
723 return false;
724 }
725 if ($part) {
726 return substr($haystack, 0, $pos);
727 }
728
729 return substr($haystack, $pos);
730 }
731
732 public static function mb_get_info($type = 'all')
733 {
734 $info = [
735 'internal_encoding' => self::$internalEncoding,
736 'http_output' => 'pass',
737 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
738 'func_overload' => 0,
739 'func_overload_list' => 'no overload',
740 'mail_charset' => 'UTF-8',
741 'mail_header_encoding' => 'BASE64',
742 'mail_body_encoding' => 'BASE64',
743 'illegal_chars' => 0,
744 'encoding_translation' => 'Off',
745 'language' => self::$language,
746 'detect_order' => self::$encodingList,
747 'substitute_character' => 'none',
748 'strict_detection' => 'Off',
749 ];
750
751 if ('all' === $type) {
752 return $info;
753 }
754 if (isset($info[$type])) {
755 return $info[$type];
756 }
757
758 return false;
759 }
760
761 public static function mb_http_input($type = '')
762 {
763 return false;
764 }
765
766 public static function mb_http_output($encoding = null)
767 {
768 return null !== $encoding ? 'pass' === $encoding : 'pass';
769 }
770
771 public static function mb_strwidth($s, $encoding = null)
772 {
773 $encoding = self::getEncoding($encoding);
774
775 if ('UTF-8' !== $encoding) {
776 $s = iconv($encoding, 'UTF-8//IGNORE', $s);
777 }
778
779 $s = preg_replace(
780 '/[\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',
781 '',
782 $s,
783 -1,
784 $wide
785 );
786
787 return ($wide << 1) + iconv_strlen($s, 'UTF-8');
788 }
789
790 public static function mb_substr_count($haystack, $needle, $encoding = null)
791 {
792 return substr_count($haystack, $needle);
793 }
794
795 public static function mb_output_handler($contents, $status)
796 {
797 return $contents;
798 }
799
800 public static function mb_chr($code, $encoding = null)
801 {
802 if (0x80 > $code %= 0x200000) {
803 $s = \chr($code);
804 } elseif (0x800 > $code) {
805 $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
806 } elseif (0x10000 > $code) {
807 $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
808 } else {
809 $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
810 }
811
812 if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
813 $s = mb_convert_encoding($s, $encoding, 'UTF-8');
814 }
815
816 return $s;
817 }
818
819 public static function mb_ord($s, $encoding = null)
820 {
821 if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
822 $s = mb_convert_encoding($s, 'UTF-8', $encoding);
823 }
824
825 if (1 === \strlen($s)) {
826 return \ord($s);
827 }
828
829 $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
830 if (0xF0 <= $code) {
831 return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
832 }
833 if (0xE0 <= $code) {
834 return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
835 }
836 if (0xC0 <= $code) {
837 return (($code - 0xC0) << 6) + $s[2] - 0x80;
838 }
839
840 return $code;
841 }
842
843 public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
844 {
845 if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
846 throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
847 }
848
849 if (null === $encoding) {
850 $encoding = self::mb_internal_encoding();
851 } else {
852 self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given');
853 }
854
855 if (self::mb_strlen($pad_string, $encoding) <= 0) {
856 throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
857 }
858
859 $paddingRequired = $length - self::mb_strlen($string, $encoding);
860
861 if ($paddingRequired < 1) {
862 return $string;
863 }
864
865 switch ($pad_type) {
866 case \STR_PAD_LEFT:
867 return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string;
868 case \STR_PAD_RIGHT:
869 return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding);
870 default:
871 $leftPaddingLength = floor($paddingRequired / 2);
872 $rightPaddingLength = $paddingRequired - $leftPaddingLength;
873
874 return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(
875 str_repeat($pad_string, $rightPaddingLength),
876 0,
877 $rightPaddingLength,
878 $encoding
879 );
880 }
881 }
882
883 public static function mb_ucfirst(string $string, ?string $encoding = null): string
884 {
885 if (null === $encoding) {
886 $encoding = self::mb_internal_encoding();
887 } else {
888 self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
889 }
890
891 $firstChar = mb_substr($string, 0, 1, $encoding);
892 $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding);
893
894 return $firstChar.mb_substr($string, 1, null, $encoding);
895 }
896
897 public static function mb_lcfirst(string $string, ?string $encoding = null): string
898 {
899 if (null === $encoding) {
900 $encoding = self::mb_internal_encoding();
901 } else {
902 self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
903 }
904
905 $firstChar = mb_substr($string, 0, 1, $encoding);
906 $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding);
907
908 return $firstChar.mb_substr($string, 1, null, $encoding);
909 }
910
911 private static function getSubpart($pos, $part, $haystack, $encoding)
912 {
913 if (false === $pos) {
914 return false;
915 }
916 if ($part) {
917 return self::mb_substr($haystack, 0, $pos, $encoding);
918 }
919
920 return self::mb_substr($haystack, $pos, null, $encoding);
921 }
922
923 private static function html_encoding_callback(array $m)
924 {
925 $i = 1;
926 $entities = '';
927 $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));
928
929 while (isset($m[$i])) {
930 if (0x80 > $m[$i]) {
931 $entities .= \chr($m[$i++]);
932 continue;
933 }
934 if (0xF0 <= $m[$i]) {
935 $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
936 } elseif (0xE0 <= $m[$i]) {
937 $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
938 } else {
939 $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
940 }
941
942 $entities .= '&#'.$c.';';
943 }
944
945 return $entities;
946 }
947
948 private static function title_case(array $s)
949 {
950 return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
951 }
952
953 private static function getData($file)
954 {
955 if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
956 return require $file;
957 }
958
959 return false;
960 }
961
962 private static function getEncoding($encoding)
963 {
964 if (null === $encoding) {
965 return self::$internalEncoding;
966 }
967
968 if ('UTF-8' === $encoding) {
969 return 'UTF-8';
970 }
971
972 $encoding = strtoupper($encoding);
973
974 if ('8BIT' === $encoding || 'BINARY' === $encoding) {
975 return 'CP850';
976 }
977
978 if ('UTF8' === $encoding) {
979 return 'UTF-8';
980 }
981
982 return $encoding;
983 }
984
985 public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string
986 {
987 return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
988 }
989
990 public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string
991 {
992 return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
993 }
994
995 public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string
996 {
997 return self::mb_internal_trim('{[%s]+$}D', $string, $characters, $encoding, __FUNCTION__);
998 }
999
1000 private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string
1001 {
1002 if (null === $encoding) {
1003 $encoding = self::mb_internal_encoding();
1004 } else {
1005 self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given');
1006 }
1007
1008 if ('' === $characters) {
1009 return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding);
1010 }
1011
1012 if ('UTF-8' === $encoding) {
1013 $encoding = null;
1014 if (!preg_match('//u', $string)) {
1015 $string = @iconv('UTF-8', 'UTF-8//IGNORE', $string);
1016 }
1017 if (null !== $characters && !preg_match('//u', $characters)) {
1018 $characters = @iconv('UTF-8', 'UTF-8//IGNORE', $characters);
1019 }
1020 } else {
1021 $string = iconv($encoding, 'UTF-8//IGNORE', $string);
1022
1023 if (null !== $characters) {
1024 $characters = iconv($encoding, 'UTF-8//IGNORE', $characters);
1025 }
1026 }
1027
1028 if (null === $characters) {
1029 $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}";
1030 } else {
1031 $characters = preg_quote($characters);
1032 }
1033
1034 $string = preg_replace(sprintf($regex, $characters), '', $string);
1035
1036 if (null === $encoding) {
1037 return $string;
1038 }
1039
1040 return iconv('UTF-8', $encoding.'//IGNORE', $string);
1041 }
1042
1043 private static function assertEncoding(string $encoding, string $errorFormat): void
1044 {
1045 try {
1046 $validEncoding = @self::mb_check_encoding('', $encoding);
1047 } catch (\ValueError $e) {
1048 throw new \ValueError(sprintf($errorFormat, $encoding));
1049 }
1050
1051 // BC for PHP 7.3 and lower
1052 if (!$validEncoding) {
1053 throw new \ValueError(sprintf($errorFormat, $encoding));
1054 }
1055 }
1056 }
1057