PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / trunk
GiveWP – Donation Plugin and Fundraising Platform vtrunk
4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / vendor / symfony / polyfill-mbstring / Mbstring.php
give / vendor / symfony / polyfill-mbstring Last commit date
Resources 2 years ago LICENSE 2 years ago Mbstring.php 1 month ago bootstrap.php 1 month ago bootstrap80.php 4 months ago
Mbstring.php
1078 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 * - 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 <p@tchwork.com>
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, static 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]+))'.(\PHP_VERSION_ID >= 80200 ? '' : '(?!&)').';?/', static 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('/[\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);
780
781 return ($wide << 1) + iconv_strlen($s, 'UTF-8');
782 }
783
784 public static function mb_substr_count($haystack, $needle, $encoding = null)
785 {
786 return substr_count($haystack, $needle);
787 }
788
789 public static function mb_output_handler($contents, $status)
790 {
791 return $contents;
792 }
793
794 public static function mb_chr($code, $encoding = null)
795 {
796 if (0x80 > $code %= 0x200000) {
797 $s = \chr($code);
798 } elseif (0x800 > $code) {
799 $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
800 } elseif (0x10000 > $code) {
801 $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
802 } else {
803 $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
804 }
805
806 if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
807 $s = mb_convert_encoding($s, $encoding, 'UTF-8');
808 }
809
810 return $s;
811 }
812
813 public static function mb_ord($s, $encoding = null)
814 {
815 if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
816 $s = mb_convert_encoding($s, 'UTF-8', $encoding);
817 }
818
819 if (1 === \strlen($s)) {
820 return \ord($s);
821 }
822
823 $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
824 if (0xF0 <= $code) {
825 return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
826 }
827 if (0xE0 <= $code) {
828 return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
829 }
830 if (0xC0 <= $code) {
831 return (($code - 0xC0) << 6) + $s[2] - 0x80;
832 }
833
834 return $code;
835 }
836
837 /** @return string|false */
838 public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null)
839 {
840 if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
841 if (\PHP_VERSION_ID < 80000) {
842 trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING);
843
844 return false;
845 }
846
847 throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
848 }
849
850 if (null === $encoding) {
851 $encoding = self::mb_internal_encoding();
852 } elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) {
853 return false;
854 }
855
856 if (self::mb_strlen($pad_string, $encoding) <= 0) {
857 if (\PHP_VERSION_ID < 80000) {
858 trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING);
859
860 return false;
861 }
862
863 throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
864 }
865
866 $paddingRequired = $length - self::mb_strlen($string, $encoding);
867
868 if ($paddingRequired < 1) {
869 return $string;
870 }
871
872 switch ($pad_type) {
873 case \STR_PAD_LEFT:
874 return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string;
875 case \STR_PAD_RIGHT:
876 return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding);
877 default:
878 $leftPaddingLength = floor($paddingRequired / 2);
879 $rightPaddingLength = $paddingRequired - $leftPaddingLength;
880
881 return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
882 }
883 }
884
885 /** @return string|false */
886 public static function mb_ucfirst(string $string, ?string $encoding = null)
887 {
888 if (null === $encoding) {
889 $encoding = self::mb_internal_encoding();
890 } elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
891 return false;
892 }
893
894 $firstChar = mb_substr($string, 0, 1, $encoding);
895 $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding);
896
897 return $firstChar.mb_substr($string, 1, null, $encoding);
898 }
899
900 /** @return string|false */
901 public static function mb_lcfirst(string $string, ?string $encoding = null)
902 {
903 if (null === $encoding) {
904 $encoding = self::mb_internal_encoding();
905 } elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
906 return false;
907 }
908
909 $firstChar = mb_substr($string, 0, 1, $encoding);
910 $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding);
911
912 return $firstChar.mb_substr($string, 1, null, $encoding);
913 }
914
915 private static function getSubpart($pos, $part, $haystack, $encoding)
916 {
917 if (false === $pos) {
918 return false;
919 }
920 if ($part) {
921 return self::mb_substr($haystack, 0, $pos, $encoding);
922 }
923
924 return self::mb_substr($haystack, $pos, null, $encoding);
925 }
926
927 private static function html_encoding_callback(array $m)
928 {
929 $i = 1;
930 $entities = '';
931 $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));
932
933 while (isset($m[$i])) {
934 if (0x80 > $m[$i]) {
935 $entities .= \chr($m[$i++]);
936 continue;
937 }
938 if (0xF0 <= $m[$i]) {
939 $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
940 } elseif (0xE0 <= $m[$i]) {
941 $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
942 } else {
943 $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
944 }
945
946 $entities .= '&#'.$c.';';
947 }
948
949 return $entities;
950 }
951
952 private static function title_case(array $s)
953 {
954 return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
955 }
956
957 private static function getData($file)
958 {
959 if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
960 return require $file;
961 }
962
963 return false;
964 }
965
966 private static function getEncoding($encoding)
967 {
968 if (null === $encoding) {
969 return self::$internalEncoding;
970 }
971
972 if ('UTF-8' === $encoding) {
973 return 'UTF-8';
974 }
975
976 $encoding = strtoupper($encoding);
977
978 if ('8BIT' === $encoding || 'BINARY' === $encoding) {
979 return 'CP850';
980 }
981
982 if ('UTF8' === $encoding) {
983 return 'UTF-8';
984 }
985
986 if ('UTF-32' === $encoding) {
987 return 'UTF-32BE';
988 }
989
990 if ('UTF-16' === $encoding) {
991 return 'UTF-16BE';
992 }
993
994 return $encoding;
995 }
996
997 /** @return string|false */
998 public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null)
999 {
1000 return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
1001 }
1002
1003 /** @return string|false */
1004 public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null)
1005 {
1006 return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
1007 }
1008
1009 /** @return string|false */
1010 public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null)
1011 {
1012 return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
1013 }
1014
1015 /** @return string|false */
1016 private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function)
1017 {
1018 if (null === $encoding) {
1019 $encoding = self::mb_internal_encoding();
1020 } elseif (!self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) {
1021 return false;
1022 }
1023
1024 if ('' === $characters) {
1025 return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding);
1026 }
1027
1028 if ('UTF-8' === $encoding) {
1029 $encoding = null;
1030 if (!preg_match('//u', $string)) {
1031 $string = @iconv('UTF-8', 'UTF-8//IGNORE', $string);
1032 }
1033 if (null !== $characters && !preg_match('//u', $characters)) {
1034 $characters = @iconv('UTF-8', 'UTF-8//IGNORE', $characters);
1035 }
1036 } else {
1037 $string = iconv($encoding, 'UTF-8//IGNORE', $string);
1038
1039 if (null !== $characters) {
1040 $characters = iconv($encoding, 'UTF-8//IGNORE', $characters);
1041 }
1042 }
1043
1044 if (null === $characters) {
1045 $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}";
1046 } else {
1047 $characters = preg_quote($characters);
1048 }
1049
1050 $string = preg_replace(\sprintf($regex, $characters), '', $string);
1051
1052 if (null === $encoding) {
1053 return $string;
1054 }
1055
1056 return iconv('UTF-8', $encoding.'//IGNORE', $string);
1057 }
1058
1059 private static function assertEncoding(string $encoding, string $errorFormat): bool
1060 {
1061 try {
1062 $validEncoding = @self::mb_check_encoding('', $encoding);
1063 } catch (\ValueError $e) {
1064 throw new \ValueError(\sprintf($errorFormat, $encoding));
1065 }
1066
1067 if (!$validEncoding) {
1068 if (80000 > \PHP_VERSION_ID) {
1069 trigger_error(\sprintf($errorFormat, $encoding), \E_USER_WARNING);
1070 } else {
1071 throw new \ValueError(\sprintf($errorFormat, $encoding));
1072 }
1073 }
1074
1075 return $validEncoding;
1076 }
1077 }
1078