GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( cc39b3...2b121f )
by Anton
04:25 queued 01:05
created

Mbstring::mb_rtrim()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 3
dl 0
loc 3
rs 10
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)) {
0 ignored issues
show
introduced by
The condition false === $v = self::mb_...ncoding, $fromEncoding) is always false.
Loading history...
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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $convmap of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $convmap of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $s can also be of type false; however, parameter $string of strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

259
        $len = \strlen(/** @scrutinizer ignore-type */ $s);
Loading history...
260
        $result = '';
261
262
        while ($i < $len) {
263
            $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
264
            $uchr = substr($s, $i, $ulen);
0 ignored issues
show
Bug introduced by
It seems like $s can also be of type false; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

264
            $uchr = substr(/** @scrutinizer ignore-type */ $s, $i, $ulen);
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $s can also be of type false; however, parameter $str of strtr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

322
                    $s = strtr(/** @scrutinizer ignore-type */ $s, $caseFolding);
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $s can also be of type false; however, parameter $string of strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

335
            $len = \strlen(/** @scrutinizer ignore-type */ $s);
Loading history...
336
337
            while ($i < $len) {
338
                $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
339
                $uchr = substr($s, $i, $ulen);
0 ignored issues
show
Bug introduced by
It seems like $s can also be of type false; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

339
                $uchr = substr(/** @scrutinizer ignore-type */ $s, $i, $ulen);
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $s can also be of type false; however, parameter $string of substr_replace() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

352
                        $s = substr_replace(/** @scrutinizer ignore-type */ $s, $uchr, $i - $ulen, $ulen);
Loading history...
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
    public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
838
    {
839
        if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
840
            throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
841
        }
842
843
        if (null === $encoding) {
844
            $encoding = self::mb_internal_encoding();
845
        } else {
846
            self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given');
847
        }
848
849
        if (self::mb_strlen($pad_string, $encoding) <= 0) {
850
            throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
851
        }
852
853
        $paddingRequired = $length - self::mb_strlen($string, $encoding);
854
855
        if ($paddingRequired < 1) {
856
            return $string;
857
        }
858
859
        switch ($pad_type) {
860
            case \STR_PAD_LEFT:
861
                return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string;
862
            case \STR_PAD_RIGHT:
863
                return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding);
864
            default:
865
                $leftPaddingLength = floor($paddingRequired / 2);
866
                $rightPaddingLength = $paddingRequired - $leftPaddingLength;
867
868
                return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
0 ignored issues
show
Bug introduced by
$leftPaddingLength of type double is incompatible with the type integer expected by parameter $times of str_repeat(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

868
                return self::mb_substr(str_repeat($pad_string, /** @scrutinizer ignore-type */ $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
Loading history...
869
        }
870
    }
871
872
    public static function mb_ucfirst(string $string, ?string $encoding = null): string
873
    {
874
        if (null === $encoding) {
875
            $encoding = self::mb_internal_encoding();
876
        } else {
877
            self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
878
        }
879
880
        $firstChar = mb_substr($string, 0, 1, $encoding);
881
        $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding);
882
883
        return $firstChar.mb_substr($string, 1, null, $encoding);
884
    }
885
886
    public static function mb_lcfirst(string $string, ?string $encoding = null): string
887
    {
888
        if (null === $encoding) {
889
            $encoding = self::mb_internal_encoding();
890
        } else {
891
            self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
892
        }
893
894
        $firstChar = mb_substr($string, 0, 1, $encoding);
895
        $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding);
896
897
        return $firstChar.mb_substr($string, 1, null, $encoding);
898
    }
899
900
    private static function getSubpart($pos, $part, $haystack, $encoding)
901
    {
902
        if (false === $pos) {
903
            return false;
904
        }
905
        if ($part) {
906
            return self::mb_substr($haystack, 0, $pos, $encoding);
907
        }
908
909
        return self::mb_substr($haystack, $pos, null, $encoding);
910
    }
911
912
    private static function html_encoding_callback(array $m)
913
    {
914
        $i = 1;
915
        $entities = '';
916
        $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));
917
918
        while (isset($m[$i])) {
919
            if (0x80 > $m[$i]) {
920
                $entities .= \chr($m[$i++]);
921
                continue;
922
            }
923
            if (0xF0 <= $m[$i]) {
924
                $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
925
            } elseif (0xE0 <= $m[$i]) {
926
                $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
927
            } else {
928
                $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
929
            }
930
931
            $entities .= '&#'.$c.';';
932
        }
933
934
        return $entities;
935
    }
936
937
    private static function title_case(array $s)
938
    {
939
        return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
0 ignored issues
show
Bug introduced by
Are you sure self::mb_convert_case($s...MB_CASE_UPPER, 'UTF-8') of type array|false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

939
        return /** @scrutinizer ignore-type */ self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
Loading history...
940
    }
941
942
    private static function getData($file)
943
    {
944
        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
945
            return require $file;
946
        }
947
948
        return false;
949
    }
950
951
    private static function getEncoding($encoding)
952
    {
953
        if (null === $encoding) {
954
            return self::$internalEncoding;
955
        }
956
957
        if ('UTF-8' === $encoding) {
958
            return 'UTF-8';
959
        }
960
961
        $encoding = strtoupper($encoding);
962
963
        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
964
            return 'CP850';
965
        }
966
967
        if ('UTF8' === $encoding) {
968
            return 'UTF-8';
969
        }
970
971
        return $encoding;
972
    }
973
974
    public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string
975
    {
976
        return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
977
    }
978
979
    public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string
980
    {
981
        return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
982
    }
983
984
    public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string
985
    {
986
        return self::mb_internal_trim('{[%s]+$}D', $string, $characters, $encoding, __FUNCTION__);
987
    }
988
989
    private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string
990
    {
991
        if (null === $encoding) {
992
            $encoding = self::mb_internal_encoding();
993
        } else {
994
            self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given');
995
        }
996
997
        if ('' === $characters) {
998
            return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding);
0 ignored issues
show
introduced by
The condition null === $encoding is always false.
Loading history...
999
        }
1000
1001
        if ('UTF-8' === $encoding) {
1002
            $encoding = null;
1003
            if (!preg_match('//u', $string)) {
1004
                $string = @iconv('UTF-8', 'UTF-8//IGNORE', $string);
1005
            }
1006
            if (null !== $characters && !preg_match('//u', $characters)) {
1007
                $characters = @iconv('UTF-8', 'UTF-8//IGNORE', $characters);
1008
            }
1009
        } else {
1010
            $string = iconv($encoding, 'UTF-8//IGNORE', $string);
1011
1012
            if (null !== $characters) {
1013
                $characters = iconv($encoding, 'UTF-8//IGNORE', $characters);
1014
            }
1015
        }
1016
1017
        if (null === $characters) {
1018
            $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}";
1019
        } else {
1020
            $characters = preg_quote($characters);
0 ignored issues
show
Bug introduced by
It seems like $characters can also be of type false; however, parameter $str of preg_quote() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1020
            $characters = preg_quote(/** @scrutinizer ignore-type */ $characters);
Loading history...
1021
        }
1022
1023
        $string = preg_replace(sprintf($regex, $characters), '', $string);
1024
1025
        if (null === $encoding) {
1026
            return $string;
1027
        }
1028
1029
        return iconv('UTF-8', $encoding.'//IGNORE', $string);
1030
    }
1031
1032
    private static function assertEncoding(string $encoding, string $errorFormat): void
1033
    {
1034
        try {
1035
            $validEncoding = @self::mb_check_encoding('', $encoding);
1036
        } catch (\ValueError $e) {
1037
            throw new \ValueError(sprintf($errorFormat, $encoding));
1038
        }
1039
1040
        // BC for PHP 7.3 and lower
1041
        if (!$validEncoding) {
1042
            throw new \ValueError(sprintf($errorFormat, $encoding));
1043
        }
1044
    }
1045
}
1046