Completed
Push — master ( 589f93...e09b1b )
by Lars
25:40
created

UTF8::strtolower()   B

Complexity

Conditions 10
Paths 29

Size

Total Lines 54
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 11.1743

Importance

Changes 6
Bugs 2 Features 0
Metric Value
cc 10
eloc 21
c 6
b 2
f 0
nc 29
nop 5
dl 0
loc 54
ccs 17
cts 22
cp 0.7727
crap 11.1743
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
final class UTF8
8
{
9
    /**
10
     * (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control])
11
     * This regular expression is a work around for http://bugs.exim.org/1279
12
     */
13
    const GRAPHEME_CLUSTER_RX = "(?:\r\n|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{200D}\x{1D165}\x{1D16E}-\x{1D172}]*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])";
14
15
    /**
16
     * Bom => Byte-Length
17
     *
18
     * INFO: https://en.wikipedia.org/wiki/Byte_order_mark
19
     *
20
     * @var array<string, int>
21
     */
22
    private static $BOM = [
23
        "\xef\xbb\xbf"     => 3, // UTF-8 BOM
24
        ''              => 6, // UTF-8 BOM as "WINDOWS-1252" (one char has [maybe] more then one byte ...)
25
        "\x00\x00\xfe\xff" => 4, // UTF-32 (BE) BOM
26
        '  þÿ'             => 6, // UTF-32 (BE) BOM as "WINDOWS-1252"
27
        "\xff\xfe\x00\x00" => 4, // UTF-32 (LE) BOM
28
        'ÿþ  '             => 6, // UTF-32 (LE) BOM as "WINDOWS-1252"
29
        "\xfe\xff"         => 2, // UTF-16 (BE) BOM
30
        'þÿ'               => 4, // UTF-16 (BE) BOM as "WINDOWS-1252"
31
        "\xff\xfe"         => 2, // UTF-16 (LE) BOM
32
        'ÿþ'               => 4, // UTF-16 (LE) BOM as "WINDOWS-1252"
33
    ];
34
35
    /**
36
     * Numeric code point => UTF-8 Character
37
     *
38
     * url: http://www.w3schools.com/charsets/ref_utf_punctuation.asp
39
     *
40
     * @var array<int, string>
41
     */
42
    private static $WHITESPACE = [
43
        // NUL Byte
44
        0 => "\x0",
45
        // Tab
46
        9 => "\x9",
47
        // New Line
48
        10 => "\xa",
49
        // Vertical Tab
50
        11 => "\xb",
51
        // Carriage Return
52
        13 => "\xd",
53
        // Ordinary Space
54
        32 => "\x20",
55
        // NO-BREAK SPACE
56
        160 => "\xc2\xa0",
57
        // OGHAM SPACE MARK
58
        5760 => "\xe1\x9a\x80",
59
        // MONGOLIAN VOWEL SEPARATOR
60
        6158 => "\xe1\xa0\x8e",
61
        // EN QUAD
62
        8192 => "\xe2\x80\x80",
63
        // EM QUAD
64
        8193 => "\xe2\x80\x81",
65
        // EN SPACE
66
        8194 => "\xe2\x80\x82",
67
        // EM SPACE
68
        8195 => "\xe2\x80\x83",
69
        // THREE-PER-EM SPACE
70
        8196 => "\xe2\x80\x84",
71
        // FOUR-PER-EM SPACE
72
        8197 => "\xe2\x80\x85",
73
        // SIX-PER-EM SPACE
74
        8198 => "\xe2\x80\x86",
75
        // FIGURE SPACE
76
        8199 => "\xe2\x80\x87",
77
        // PUNCTUATION SPACE
78
        8200 => "\xe2\x80\x88",
79
        // THIN SPACE
80
        8201 => "\xe2\x80\x89",
81
        //HAIR SPACE
82
        8202 => "\xe2\x80\x8a",
83
        // LINE SEPARATOR
84
        8232 => "\xe2\x80\xa8",
85
        // PARAGRAPH SEPARATOR
86
        8233 => "\xe2\x80\xa9",
87
        // NARROW NO-BREAK SPACE
88
        8239 => "\xe2\x80\xaf",
89
        // MEDIUM MATHEMATICAL SPACE
90
        8287 => "\xe2\x81\x9f",
91
        // HALFWIDTH HANGUL FILLER
92
        65440 => "\xef\xbe\xa0",
93
        // IDEOGRAPHIC SPACE
94
        12288 => "\xe3\x80\x80",
95
    ];
96
97
    /**
98
     * @var array<string, string>
99
     */
100
    private static $WHITESPACE_TABLE = [
101
        'SPACE'                     => "\x20",
102
        'NO-BREAK SPACE'            => "\xc2\xa0",
103
        'OGHAM SPACE MARK'          => "\xe1\x9a\x80",
104
        'EN QUAD'                   => "\xe2\x80\x80",
105
        'EM QUAD'                   => "\xe2\x80\x81",
106
        'EN SPACE'                  => "\xe2\x80\x82",
107
        'EM SPACE'                  => "\xe2\x80\x83",
108
        'THREE-PER-EM SPACE'        => "\xe2\x80\x84",
109
        'FOUR-PER-EM SPACE'         => "\xe2\x80\x85",
110
        'SIX-PER-EM SPACE'          => "\xe2\x80\x86",
111
        'FIGURE SPACE'              => "\xe2\x80\x87",
112
        'PUNCTUATION SPACE'         => "\xe2\x80\x88",
113
        'THIN SPACE'                => "\xe2\x80\x89",
114
        'HAIR SPACE'                => "\xe2\x80\x8a",
115
        'LINE SEPARATOR'            => "\xe2\x80\xa8",
116
        'PARAGRAPH SEPARATOR'       => "\xe2\x80\xa9",
117
        'ZERO WIDTH SPACE'          => "\xe2\x80\x8b",
118
        'NARROW NO-BREAK SPACE'     => "\xe2\x80\xaf",
119
        'MEDIUM MATHEMATICAL SPACE' => "\xe2\x81\x9f",
120
        'IDEOGRAPHIC SPACE'         => "\xe3\x80\x80",
121
        'HALFWIDTH HANGUL FILLER'   => "\xef\xbe\xa0",
122
    ];
123
124
    /**
125
     * @var array{upper: string[], lower: string[]}
126
     */
127
    private static $COMMON_CASE_FOLD = [
128
        'upper' => [
129
            'µ',
130
            'ſ',
131
            "\xCD\x85",
132
            'ς',
133
            'ẞ',
134
            "\xCF\x90",
135
            "\xCF\x91",
136
            "\xCF\x95",
137
            "\xCF\x96",
138
            "\xCF\xB0",
139
            "\xCF\xB1",
140
            "\xCF\xB5",
141
            "\xE1\xBA\x9B",
142
            "\xE1\xBE\xBE",
143
        ],
144
        'lower' => [
145
            'μ',
146
            's',
147
            'ι',
148
            'σ',
149
            'ß',
150
            'β',
151
            'θ',
152
            'φ',
153
            'π',
154
            'κ',
155
            'ρ',
156
            'ε',
157
            "\xE1\xB9\xA1",
158
            'ι',
159
        ],
160
    ];
161
162
    /**
163
     * @var array<string, mixed>
164
     */
165
    private static $SUPPORT = [];
166
167
    /**
168
     * @var array<string, string>|null
169
     */
170
    private static $BROKEN_UTF8_FIX;
171
172
    /**
173
     * @var array<int, string>|null
174
     */
175
    private static $WIN1252_TO_UTF8;
176
177
    /**
178
     * @var array<int ,string>|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<int at position 2 could not be parsed: Expected '>' at position 2, but found 'int'.
Loading history...
179
     */
180
    private static $INTL_TRANSLITERATOR_LIST;
181
182
    /**
183
     * @var array<string>|null
184
     */
185
    private static $ENCODINGS;
186
187
    /**
188
     * @var array<string ,int>|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string at position 2 could not be parsed: Expected '>' at position 2, but found 'string'.
Loading history...
189
     */
190
    private static $ORD;
191
192
    /**
193
     * @var array<string, string>|null
194
     */
195
    private static $EMOJI;
196
197
    /**
198
     * @var array<string>|null
199
     */
200
    private static $EMOJI_VALUES_CACHE;
201
202
    /**
203
     * @var array<string>|null
204
     */
205
    private static $EMOJI_KEYS_CACHE;
206
207
    /**
208
     * @var array<string>|null
209
     */
210
    private static $EMOJI_KEYS_REVERSIBLE_CACHE;
211
212
    /**
213
     * @var array<int, string>|null
214
     */
215
    private static $CHR;
216
217
    /**
218
     * __construct()
219
     */
220 33
    public function __construct()
221
    {
222 33
    }
223
224
    /**
225
     * Return the character at the specified position: $str[1] like functionality.
226
     *
227
     * @param string $str      <p>A UTF-8 string.</p>
228
     * @param int    $pos      <p>The position of character to return.</p>
229
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
230
     *
231
     * @return string single multi-byte character
232
     */
233 3
    public static function access(string $str, int $pos, string $encoding = 'UTF-8'): string
234
    {
235 3
        if ($str === '' || $pos < 0) {
236 2
            return '';
237
        }
238
239 3
        if ($encoding === 'UTF-8') {
240 3
            return (string) \mb_substr($str, $pos, 1);
241
        }
242
243
        return (string) self::substr($str, $pos, 1, $encoding);
244
    }
245
246
    /**
247
     * Prepends UTF-8 BOM character to the string and returns the whole string.
248
     *
249
     * INFO: If BOM already existed there, the Input string is returned.
250
     *
251
     * @param string $str <p>The input string.</p>
252
     *
253
     * @return string the output string that contains BOM
254
     */
255 2
    public static function add_bom_to_string(string $str): string
256
    {
257 2
        if (self::string_has_bom($str) === false) {
258 2
            $str = self::bom() . $str;
259
        }
260
261 2
        return $str;
262
    }
263
264
    /**
265
     * Changes all keys in an array.
266
     *
267
     * @param array<string, mixed> $array    <p>The array to work on</p>
268
     * @param int                  $case     [optional] <p> Either <strong>CASE_UPPER</strong><br>
269
     *                                       or <strong>CASE_LOWER</strong> (default)</p>
270
     * @param string               $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
271
     *
272
     * @return string[]
273
     *                  <p>An array with its keys lower- or uppercased.</p>
274
     */
275 2
    public static function array_change_key_case(
276
        array $array,
277
        int $case = \CASE_LOWER,
278
        string $encoding = 'UTF-8'
279
    ): array {
280
        if (
281 2
            $case !== \CASE_LOWER
282
            &&
283 2
            $case !== \CASE_UPPER
284
        ) {
285
            $case = \CASE_LOWER;
286
        }
287
288 2
        $return = [];
289 2
        foreach ($array as $key => &$value) {
290 2
            $key = $case === \CASE_LOWER
291 2
                ? self::strtolower((string) $key, $encoding)
292 2
                : self::strtoupper((string) $key, $encoding);
293
294 2
            $return[$key] = $value;
295
        }
296
297 2
        return $return;
298
    }
299
300
    /**
301
     * Returns the substring between $start and $end, if found, or an empty
302
     * string. An optional offset may be supplied from which to begin the
303
     * search for the start string.
304
     *
305
     * @param string $str
306
     * @param string $start    <p>Delimiter marking the start of the substring.</p>
307
     * @param string $end      <p>Delimiter marking the end of the substring.</p>
308
     * @param int    $offset   [optional] <p>Index from which to begin the search. Default: 0</p>
309
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
310
     *
311
     * @return string
312
     */
313 16
    public static function between(
314
        string $str,
315
        string $start,
316
        string $end,
317
        int $offset = 0,
318
        string $encoding = 'UTF-8'
319
    ): string {
320 16
        if ($encoding === 'UTF-8') {
321 8
            $start_position = \mb_strpos($str, $start, $offset);
322 8
            if ($start_position === false) {
323 1
                return '';
324
            }
325
326 7
            $substr_index = $start_position + (int) \mb_strlen($start);
327 7
            $end_position = \mb_strpos($str, $end, $substr_index);
328
            if (
329 7
                $end_position === false
330
                ||
331 7
                $end_position === $substr_index
332
            ) {
333 2
                return '';
334
            }
335
336 5
            return (string) \mb_substr($str, $substr_index, $end_position - $substr_index);
337
        }
338
339 8
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
340
341 8
        $start_position = self::strpos($str, $start, $offset, $encoding);
342 8
        if ($start_position === false) {
343 1
            return '';
344
        }
345
346 7
        $substr_index = $start_position + (int) self::strlen($start, $encoding);
347 7
        $end_position = self::strpos($str, $end, $substr_index, $encoding);
348
        if (
349 7
            $end_position === false
350
            ||
351 7
            $end_position === $substr_index
352
        ) {
353 2
            return '';
354
        }
355
356 5
        return (string) self::substr(
357 5
            $str,
358 5
            $substr_index,
359 5
            $end_position - $substr_index,
360 5
            $encoding
361
        );
362
    }
363
364
    /**
365
     * Convert binary into a string.
366
     *
367
     * @param mixed $bin 1|0
368
     *
369
     * @return string
370
     */
371 2
    public static function binary_to_str($bin): string
372
    {
373 2
        if (!isset($bin[0])) {
374
            return '';
375
        }
376
377 2
        $convert = \base_convert($bin, 2, 16);
378 2
        if ($convert === '0') {
379 1
            return '';
380
        }
381
382 2
        return \pack('H*', $convert);
383
    }
384
385
    /**
386
     * Returns the UTF-8 Byte Order Mark Character.
387
     *
388
     * INFO: take a look at UTF8::$bom for e.g. UTF-16 and UTF-32 BOM values
389
     *
390
     * @return string UTF-8 Byte Order Mark
391
     */
392 4
    public static function bom(): string
393
    {
394 4
        return "\xef\xbb\xbf";
395
    }
396
397
    /**
398
     * @alias of UTF8::chr_map()
399
     *
400
     * @param callable $callback
401
     * @param string   $str
402
     *
403
     * @return string[]
404
     *
405
     * @see UTF8::chr_map()
406
     */
407 2
    public static function callback($callback, string $str): array
408
    {
409 2
        return self::chr_map($callback, $str);
410
    }
411
412
    /**
413
     * Returns the character at $index, with indexes starting at 0.
414
     *
415
     * @param string $str      <p>The input string.</p>
416
     * @param int    $index    <p>Position of the character.</p>
417
     * @param string $encoding [optional] <p>Default is UTF-8</p>
418
     *
419
     * @return string the character at $index
420
     */
421 9
    public static function char_at(string $str, int $index, string $encoding = 'UTF-8'): string
422
    {
423 9
        if ($encoding === 'UTF-8') {
424 5
            return (string) \mb_substr($str, $index, 1);
425
        }
426
427 4
        return (string) self::substr($str, $index, 1, $encoding);
428
    }
429
430
    /**
431
     * Returns an array consisting of the characters in the string.
432
     *
433
     * @param string $str <p>The input string.</p>
434
     *
435
     * @return string[] an array of chars
436
     */
437 3
    public static function chars(string $str): array
438
    {
439 3
        return self::str_split($str);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::str_split($str) returns an array which contains values of type array which are incompatible with the documented value type string.
Loading history...
440
    }
441
442
    /**
443
     * This method will auto-detect your server environment for UTF-8 support.
444
     *
445
     * @return true|null
446
     *
447
     * @internal <p>You don't need to run it manually, it will be triggered if it's needed.</p>
448
     */
449 5
    public static function checkForSupport()
450
    {
451 5
        if (!isset(self::$SUPPORT['already_checked_via_portable_utf8'])) {
452
            self::$SUPPORT['already_checked_via_portable_utf8'] = true;
453
454
            // http://php.net/manual/en/book.mbstring.php
455
            self::$SUPPORT['mbstring'] = self::mbstring_loaded();
456
            self::$SUPPORT['mbstring_func_overload'] = self::mbstring_overloaded();
457
            if (self::$SUPPORT['mbstring'] === true) {
458
                \mb_internal_encoding('UTF-8');
459
                /** @noinspection UnusedFunctionResultInspection */
460
                /** @noinspection PhpComposerExtensionStubsInspection */
461
                \mb_regex_encoding('UTF-8');
462
                self::$SUPPORT['mbstring_internal_encoding'] = 'UTF-8';
463
            }
464
465
            // http://php.net/manual/en/book.iconv.php
466
            self::$SUPPORT['iconv'] = self::iconv_loaded();
467
468
            // http://php.net/manual/en/book.intl.php
469
            self::$SUPPORT['intl'] = self::intl_loaded();
470
471
            // http://php.net/manual/en/class.intlchar.php
472
            self::$SUPPORT['intlChar'] = self::intlChar_loaded();
473
474
            // http://php.net/manual/en/book.ctype.php
475
            self::$SUPPORT['ctype'] = self::ctype_loaded();
476
477
            // http://php.net/manual/en/class.finfo.php
478
            self::$SUPPORT['finfo'] = self::finfo_loaded();
479
480
            // http://php.net/manual/en/book.json.php
481
            self::$SUPPORT['json'] = self::json_loaded();
482
483
            // http://php.net/manual/en/book.pcre.php
484
            self::$SUPPORT['pcre_utf8'] = self::pcre_utf8_support();
485
486
            self::$SUPPORT['symfony_polyfill_used'] = self::symfony_polyfill_used();
487
            if (self::$SUPPORT['symfony_polyfill_used'] === true) {
488
                \mb_internal_encoding('UTF-8');
489
                self::$SUPPORT['mbstring_internal_encoding'] = 'UTF-8';
490
            }
491
492
            return true;
493
        }
494
495 5
        return null;
496
    }
497
498
    /**
499
     * Generates a UTF-8 encoded character from the given code point.
500
     *
501
     * INFO: opposite to UTF8::ord()
502
     *
503
     * @param int|string $code_point <p>The code point for which to generate a character.</p>
504
     * @param string     $encoding   [optional] <p>Default is UTF-8</p>
505
     *
506
     * @return string|null multi-byte character, returns null on failure or empty input
507
     */
508 21
    public static function chr($code_point, string $encoding = 'UTF-8')
509
    {
510
        // init
511 21
        static $CHAR_CACHE = [];
512
513 21
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
514 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
515
        }
516
517
        if (
518 21
            $encoding !== 'UTF-8'
519
            &&
520 21
            $encoding !== 'ISO-8859-1'
521
            &&
522 21
            $encoding !== 'WINDOWS-1252'
523
            &&
524 21
            self::$SUPPORT['mbstring'] === false
525
        ) {
526
            \trigger_error('UTF8::chr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
527
        }
528
529 21
        $cache_key = $code_point . $encoding;
530 21
        if (isset($CHAR_CACHE[$cache_key]) === true) {
531 19
            return $CHAR_CACHE[$cache_key];
532
        }
533
534 12
        if ($code_point <= 127) { // use "simple"-char only until "\x80"
535
536 12
            if (self::$CHR === null) {
537
                self::$CHR = self::getData('chr');
538
            }
539
540
            /**
541
             * @psalm-suppress PossiblyNullArrayAccess
542
             */
543 12
            $chr = self::$CHR[$code_point];
544
545 12
            if ($encoding !== 'UTF-8') {
546 1
                $chr = self::encode($encoding, $chr);
547
            }
548
549 12
            return $CHAR_CACHE[$cache_key] = $chr;
550
        }
551
552
        //
553
        // fallback via "IntlChar"
554
        //
555
556 5
        if (self::$SUPPORT['intlChar'] === true) {
557
            /** @noinspection PhpComposerExtensionStubsInspection */
558 5
            $chr = \IntlChar::chr($code_point);
559
560 5
            if ($encoding !== 'UTF-8') {
561
                $chr = self::encode($encoding, $chr);
562
            }
563
564 5
            return $CHAR_CACHE[$cache_key] = $chr;
565
        }
566
567
        //
568
        // fallback via vanilla php
569
        //
570
571
        if (self::$CHR === null) {
572
            self::$CHR = self::getData('chr');
573
        }
574
575
        $code_point = (int) $code_point;
576
        if ($code_point <= 0x7F) {
577
            /**
578
             * @psalm-suppress PossiblyNullArrayAccess
579
             */
580
            $chr = self::$CHR[$code_point];
581
        } elseif ($code_point <= 0x7FF) {
582
            /**
583
             * @psalm-suppress PossiblyNullArrayAccess
584
             */
585
            $chr = self::$CHR[($code_point >> 6) + 0xC0] .
586
                   self::$CHR[($code_point & 0x3F) + 0x80];
587
        } elseif ($code_point <= 0xFFFF) {
588
            /**
589
             * @psalm-suppress PossiblyNullArrayAccess
590
             */
591
            $chr = self::$CHR[($code_point >> 12) + 0xE0] .
592
                   self::$CHR[(($code_point >> 6) & 0x3F) + 0x80] .
593
                   self::$CHR[($code_point & 0x3F) + 0x80];
594
        } else {
595
            /**
596
             * @psalm-suppress PossiblyNullArrayAccess
597
             */
598
            $chr = self::$CHR[($code_point >> 18) + 0xF0] .
599
                   self::$CHR[(($code_point >> 12) & 0x3F) + 0x80] .
600
                   self::$CHR[(($code_point >> 6) & 0x3F) + 0x80] .
601
                   self::$CHR[($code_point & 0x3F) + 0x80];
602
        }
603
604
        if ($encoding !== 'UTF-8') {
605
            $chr = self::encode($encoding, $chr);
606
        }
607
608
        return $CHAR_CACHE[$cache_key] = $chr;
609
    }
610
611
    /**
612
     * Applies callback to all characters of a string.
613
     *
614
     * @param callable $callback <p>The callback function.</p>
615
     * @param string   $str      <p>UTF-8 string to run callback on.</p>
616
     *
617
     * @return string[]
618
     *                  <p>The outcome of the callback, as array.</p>
619
     */
620 2
    public static function chr_map($callback, string $str): array
621
    {
622 2
        return \array_map(
623 2
            $callback,
624 2
            self::str_split($str)
625
        );
626
    }
627
628
    /**
629
     * Generates an array of byte length of each character of a Unicode string.
630
     *
631
     * 1 byte => U+0000  - U+007F
632
     * 2 byte => U+0080  - U+07FF
633
     * 3 byte => U+0800  - U+FFFF
634
     * 4 byte => U+10000 - U+10FFFF
635
     *
636
     * @param string $str <p>The original unicode string.</p>
637
     *
638
     * @return int[] an array of byte lengths of each character
639
     */
640 4
    public static function chr_size_list(string $str): array
641
    {
642 4
        if ($str === '') {
643 4
            return [];
644
        }
645
646 4
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
647
            return \array_map(
648
                static function (string $data): int {
649
                    // "mb_" is available if overload is used, so use it ...
650
                    return \mb_strlen($data, 'CP850'); // 8-BIT
651
                },
652
                self::str_split($str)
653
            );
654
        }
655
656 4
        return \array_map('\strlen', self::str_split($str));
657
    }
658
659
    /**
660
     * Get a decimal code representation of a specific character.
661
     *
662
     * @param string $char <p>The input character.</p>
663
     *
664
     * @return int
665
     */
666 4
    public static function chr_to_decimal(string $char): int
667
    {
668 4
        if (self::$SUPPORT['iconv'] === true) {
669 4
            $chr_tmp = \iconv('UTF-8', 'UCS-4LE', $char);
670 4
            if ($chr_tmp !== false) {
671
                /** @noinspection OffsetOperationsInspection */
672 4
                return \unpack('V', $chr_tmp)[1];
673
            }
674
        }
675
676
        $code = self::ord($char[0]);
677
        $bytes = 1;
678
679
        if (!($code & 0x80)) {
680
            // 0xxxxxxx
681
            return $code;
682
        }
683
684
        if (($code & 0xe0) === 0xc0) {
685
            // 110xxxxx
686
            $bytes = 2;
687
            $code &= ~0xc0;
688
        } elseif (($code & 0xf0) === 0xe0) {
689
            // 1110xxxx
690
            $bytes = 3;
691
            $code &= ~0xe0;
692
        } elseif (($code & 0xf8) === 0xf0) {
693
            // 11110xxx
694
            $bytes = 4;
695
            $code &= ~0xf0;
696
        }
697
698
        for ($i = 2; $i <= $bytes; ++$i) {
699
            // 10xxxxxx
700
            $code = ($code << 6) + (self::ord($char[$i - 1]) & ~0x80);
701
        }
702
703
        return $code;
704
    }
705
706
    /**
707
     * Get hexadecimal code point (U+xxxx) of a UTF-8 encoded character.
708
     *
709
     * @param int|string $char   <p>The input character</p>
710
     * @param string     $prefix [optional]
711
     *
712
     * @return string The code point encoded as U+xxxx
713
     */
714 2
    public static function chr_to_hex($char, string $prefix = 'U+'): string
715
    {
716 2
        if ($char === '') {
717 2
            return '';
718
        }
719
720 2
        if ($char === '&#0;') {
721 2
            $char = '';
722
        }
723
724 2
        return self::int_to_hex(self::ord((string) $char), $prefix);
725
    }
726
727
    /**
728
     * alias for "UTF8::chr_to_decimal()"
729
     *
730
     * @param string $chr
731
     *
732
     * @return int
733
     *
734
     * @see UTF8::chr_to_decimal()
735
     * @deprecated <p>please use "UTF8::chr_to_decimal()"</p>
736
     */
737 2
    public static function chr_to_int(string $chr): int
738
    {
739 2
        return self::chr_to_decimal($chr);
740
    }
741
742
    /**
743
     * Splits a string into smaller chunks and multiple lines, using the specified line ending character.
744
     *
745
     * @param string $body         <p>The original string to be split.</p>
746
     * @param int    $chunk_length [optional] <p>The maximum character length of a chunk.</p>
747
     * @param string $end          [optional] <p>The character(s) to be inserted at the end of each chunk.</p>
748
     *
749
     * @return string the chunked string
750
     */
751 4
    public static function chunk_split(string $body, int $chunk_length = 76, string $end = "\r\n"): string
752
    {
753 4
        return \implode($end, self::str_split($body, $chunk_length));
754
    }
755
756
    /**
757
     * Accepts a string and removes all non-UTF-8 characters from it + extras if needed.
758
     *
759
     * @param string $str                                     <p>The string to be sanitized.</p>
760
     * @param bool   $remove_bom                              [optional] <p>Set to true, if you need to remove UTF-BOM.</p>
761
     * @param bool   $normalize_whitespace                    [optional] <p>Set to true, if you need to normalize the
762
     *                                                        whitespace.</p>
763
     * @param bool   $normalize_msword                        [optional] <p>Set to true, if you need to normalize MS Word chars
764
     *                                                        e.g.: "…"
765
     *                                                        => "..."</p>
766
     * @param bool   $keep_non_breaking_space                 [optional] <p>Set to true, to keep non-breaking-spaces, in
767
     *                                                        combination with
768
     *                                                        $normalize_whitespace</p>
769
     * @param bool   $replace_diamond_question_mark           [optional] <p>Set to true, if you need to remove diamond question
770
     *                                                        mark e.g.: "�"</p>
771
     * @param bool   $remove_invisible_characters             [optional] <p>Set to false, if you not want to remove invisible
772
     *                                                        characters e.g.: "\0"</p>
773
     * @param bool   $remove_invisible_characters_url_encoded [optional] <p>Set to true, if you not want to remove invisible
774
     *                                                        url encoded characters e.g.: "%0B"<br>
775
     *                                                        WARNING: maybe contains false-positives e.g. aa%0Baa -> aaaa.
776
     *                                                        </p>
777
     *
778
     * @return string clean UTF-8 encoded string
779
     */
780 87
    public static function clean(
781
        string $str,
782
        bool $remove_bom = false,
783
        bool $normalize_whitespace = false,
784
        bool $normalize_msword = false,
785
        bool $keep_non_breaking_space = false,
786
        bool $replace_diamond_question_mark = false,
787
        bool $remove_invisible_characters = true,
788
        bool $remove_invisible_characters_url_encoded = false
789
    ): string {
790
        // http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string
791
        // caused connection reset problem on larger strings
792
793 87
        $regex = '/
794
          (
795
            (?: [\x00-\x7F]               # single-byte sequences   0xxxxxxx
796
            |   [\xC0-\xDF][\x80-\xBF]    # double-byte sequences   110xxxxx 10xxxxxx
797
            |   [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences   1110xxxx 10xxxxxx * 2
798
            |   [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3
799
            ){1,100}                      # ...one or more times
800
          )
801
        | ( [\x80-\xBF] )                 # invalid byte in range 10000000 - 10111111
802
        | ( [\xC0-\xFF] )                 # invalid byte in range 11000000 - 11111111
803
        /x';
804
        /** @noinspection NotOptimalRegularExpressionsInspection */
805 87
        $str = (string) \preg_replace($regex, '$1', $str);
806
807 87
        if ($replace_diamond_question_mark === true) {
808 33
            $str = self::replace_diamond_question_mark($str, '');
809
        }
810
811 87
        if ($remove_invisible_characters === true) {
812 87
            $str = self::remove_invisible_characters($str, $remove_invisible_characters_url_encoded);
813
        }
814
815 87
        if ($normalize_whitespace === true) {
816 37
            $str = self::normalize_whitespace($str, $keep_non_breaking_space);
817
        }
818
819 87
        if ($normalize_msword === true) {
820 4
            $str = self::normalize_msword($str);
821
        }
822
823 87
        if ($remove_bom === true) {
824 37
            $str = self::remove_bom($str);
825
        }
826
827 87
        return $str;
828
    }
829
830
    /**
831
     * Clean-up a string and show only printable UTF-8 chars at the end  + fix UTF-8 encoding.
832
     *
833
     * @param string $str <p>The input string.</p>
834
     *
835
     * @return string
836
     */
837 33
    public static function cleanup($str): string
838
    {
839
        // init
840 33
        $str = (string) $str;
841
842 33
        if ($str === '') {
843 5
            return '';
844
        }
845
846
        // fixed ISO <-> UTF-8 Errors
847 33
        $str = self::fix_simple_utf8($str);
848
849
        // remove all none UTF-8 symbols
850
        // && remove diamond question mark (�)
851
        // && remove remove invisible characters (e.g. "\0")
852
        // && remove BOM
853
        // && normalize whitespace chars (but keep non-breaking-spaces)
854 33
        return self::clean(
855 33
            $str,
856 33
            true,
857 33
            true,
858 33
            false,
859 33
            true,
860 33
            true,
861 33
            true
862
        );
863
    }
864
865
    /**
866
     * Accepts a string or a array of strings and returns an array of Unicode code points.
867
     *
868
     * INFO: opposite to UTF8::string()
869
     *
870
     * @param string|string[] $arg     <p>A UTF-8 encoded string or an array of such strings.</p>
871
     * @param bool            $u_style <p>If True, will return code points in U+xxxx format,
872
     *                                 default, code points will be returned as integers.</p>
873
     *
874
     * @return array<int|string>
875
     *                           The array of code points:<br>
876
     *                           array<int> for $u_style === false<br>
877
     *                           array<string> for $u_style === true<br>
878
     */
879 12
    public static function codepoints($arg, bool $u_style = false): array
880
    {
881 12
        if (\is_string($arg) === true) {
882 12
            $arg = self::str_split($arg);
883
        }
884
885
        /**
886
         * @psalm-suppress DocblockTypeContradiction
887
         */
888 12
        if (!\is_array($arg)) {
0 ignored issues
show
introduced by
The condition is_array($arg) is always true.
Loading history...
889 4
            return [];
890
        }
891
892 12
        if ($arg === []) {
893 7
            return [];
894
        }
895
896 11
        $arg = \array_map(
897
            [
898 11
                self::class,
899
                'ord',
900
            ],
901 11
            $arg
902
        );
903
904 11
        if ($u_style === true) {
905 2
            $arg = \array_map(
906
                [
907 2
                    self::class,
908
                    'int_to_hex',
909
                ],
910 2
                $arg
911
            );
912
        }
913
914 11
        return $arg;
915
    }
916
917
    /**
918
     * Trims the string and replaces consecutive whitespace characters with a
919
     * single space. This includes tabs and newline characters, as well as
920
     * multibyte whitespace such as the thin space and ideographic space.
921
     *
922
     * @param string $str <p>The input string.</p>
923
     *
924
     * @return string string with a trimmed $str and condensed whitespace
925
     */
926 13
    public static function collapse_whitespace(string $str): string
927
    {
928 13
        if (self::$SUPPORT['mbstring'] === true) {
929
            /** @noinspection PhpComposerExtensionStubsInspection */
930 13
            return \trim((string) \mb_ereg_replace('[[:space:]]+', ' ', $str));
931
        }
932
933
        return \trim(self::regex_replace($str, '[[:space:]]+', ' '));
934
    }
935
936
    /**
937
     * Returns count of characters used in a string.
938
     *
939
     * @param string $str                     <p>The input string.</p>
940
     * @param bool   $clean_utf8              [optional] <p>Remove non UTF-8 chars from the string.</p>
941
     * @param bool   $try_to_use_mb_functions [optional] <p>Set to false, if you don't want to use
942
     *
943
     * @return int[] an associative array of Character as keys and
944
     *               their count as values
945
     */
946 19
    public static function count_chars(
947
        string $str,
948
        bool $clean_utf8 = false,
949
        bool $try_to_use_mb_functions = true
950
    ): array {
951 19
        return \array_count_values(
952 19
            self::str_split(
953 19
                $str,
954 19
                1,
955 19
                $clean_utf8,
956 19
                $try_to_use_mb_functions
957
            )
958
        );
959
    }
960
961
    /**
962
     * Remove css media-queries.
963
     *
964
     * @param string $str
965
     *
966
     * @return string
967
     */
968 1
    public static function css_stripe_media_queries(string $str): string
969
    {
970 1
        return (string) \preg_replace(
971 1
            '#@media\\s+(?:only\\s)?(?:[\\s{(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#isumU',
972 1
            '',
973 1
            $str
974
        );
975
    }
976
977
    /**
978
     * Checks whether ctype is available on the server.
979
     *
980
     * @return bool
981
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
982
     */
983
    public static function ctype_loaded(): bool
984
    {
985
        return \extension_loaded('ctype');
986
    }
987
988
    /**
989
     * Converts an int value into a UTF-8 character.
990
     *
991
     * @param mixed $int
992
     *
993
     * @return string
994
     */
995 20
    public static function decimal_to_chr($int): string
996
    {
997 20
        return self::html_entity_decode('&#' . $int . ';', \ENT_QUOTES | \ENT_HTML5);
998
    }
999
1000
    /**
1001
     * Decodes a MIME header field
1002
     *
1003
     * @param string $str
1004
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
1005
     *
1006
     * @return false|string
1007
     *                      A decoded MIME field on success,
1008
     *                      or false if an error occurs during the decoding
1009
     */
1010
    public static function decode_mimeheader($str, string $encoding = 'UTF-8')
1011
    {
1012
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
1013
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
1014
        }
1015
1016
        if (self::$SUPPORT['iconv'] === true) {
1017
            return \iconv_mime_decode($str, \ICONV_MIME_DECODE_CONTINUE_ON_ERROR, $encoding);
1018
        }
1019
1020
        if ($encoding !== 'UTF-8') {
1021
            $str = self::encode($encoding, $str);
1022
        }
1023
1024
        return \mb_decode_mimeheader($str);
1025
    }
1026
1027
    /**
1028
     * Decodes a string which was encoded by "UTF8::emoji_encode()".
1029
     *
1030
     * @param string $str                            <p>The input string.</p>
1031
     * @param bool   $use_reversible_string_mappings [optional] <p>
1032
     *                                               When <b>TRUE</b>, we se a reversible string mapping
1033
     *                                               between "emoji_encode" and "emoji_decode".</p>
1034
     *
1035
     * @return string
1036
     */
1037 9
    public static function emoji_decode(
1038
        string $str,
1039
        bool $use_reversible_string_mappings = false
1040
    ): string {
1041 9
        self::initEmojiData();
1042
1043 9
        if ($use_reversible_string_mappings === true) {
1044 9
            return (string) \str_replace(
1045 9
                (array) self::$EMOJI_KEYS_REVERSIBLE_CACHE,
1046 9
                (array) self::$EMOJI_VALUES_CACHE,
1047 9
                $str
1048
            );
1049
        }
1050
1051 1
        return (string) \str_replace(
1052 1
            (array) self::$EMOJI_KEYS_CACHE,
1053 1
            (array) self::$EMOJI_VALUES_CACHE,
1054 1
            $str
1055
        );
1056
    }
1057
1058
    /**
1059
     * Encode a string with emoji chars into a non-emoji string.
1060
     *
1061
     * @param string $str                            <p>The input string</p>
1062
     * @param bool   $use_reversible_string_mappings [optional] <p>
1063
     *                                               when <b>TRUE</b>, we se a reversible string mapping
1064
     *                                               between "emoji_encode" and "emoji_decode"</p>
1065
     *
1066
     * @return string
1067
     */
1068 12
    public static function emoji_encode(
1069
        string $str,
1070
        bool $use_reversible_string_mappings = false
1071
    ): string {
1072 12
        self::initEmojiData();
1073
1074 12
        if ($use_reversible_string_mappings === true) {
1075 9
            return (string) \str_replace(
1076 9
                (array) self::$EMOJI_VALUES_CACHE,
1077 9
                (array) self::$EMOJI_KEYS_REVERSIBLE_CACHE,
1078 9
                $str
1079
            );
1080
        }
1081
1082 4
        return (string) \str_replace(
1083 4
            (array) self::$EMOJI_VALUES_CACHE,
1084 4
            (array) self::$EMOJI_KEYS_CACHE,
1085 4
            $str
1086
        );
1087
    }
1088
1089
    /**
1090
     * Encode a string with a new charset-encoding.
1091
     *
1092
     * INFO:  This function will also try to fix broken / double encoding,
1093
     *        so you can call this function also on a UTF-8 string and you don't mess up the string.
1094
     *
1095
     * @param string $to_encoding                   <p>e.g. 'UTF-16', 'UTF-8', 'ISO-8859-1', etc.</p>
1096
     * @param string $str                           <p>The input string</p>
1097
     * @param bool   $auto_detect_the_from_encoding [optional] <p>Force the new encoding (we try to fix broken / double
1098
     *                                              encoding for UTF-8)<br> otherwise we auto-detect the current
1099
     *                                              string-encoding</p>
1100
     * @param string $from_encoding                 [optional] <p>e.g. 'UTF-16', 'UTF-8', 'ISO-8859-1', etc.<br>
1101
     *                                              A empty string will trigger the autodetect anyway.</p>
1102
     *
1103
     * @return string
1104
     *
1105
     * @psalm-suppress InvalidReturnStatement
1106
     */
1107 28
    public static function encode(
1108
        string $to_encoding,
1109
        string $str,
1110
        bool $auto_detect_the_from_encoding = true,
1111
        string $from_encoding = ''
1112
    ): string {
1113 28
        if ($str === '' || $to_encoding === '') {
1114 13
            return $str;
1115
        }
1116
1117 28
        if ($to_encoding !== 'UTF-8' && $to_encoding !== 'CP850') {
1118 7
            $to_encoding = self::normalize_encoding($to_encoding, 'UTF-8');
1119
        }
1120
1121 28
        if ($from_encoding && $from_encoding !== 'UTF-8' && $from_encoding !== 'CP850') {
1122 2
            $from_encoding = self::normalize_encoding($from_encoding, null);
1123
        }
1124
1125
        if (
1126 28
            $to_encoding
1127
            &&
1128 28
            $from_encoding
1129
            &&
1130 28
            $from_encoding === $to_encoding
1131
        ) {
1132
            return $str;
1133
        }
1134
1135 28
        if ($to_encoding === 'JSON') {
1136 1
            $return = self::json_encode($str);
1137 1
            if ($return === false) {
1138
                throw new \InvalidArgumentException('The input string [' . $str . '] can not be used for json_encode().');
1139
            }
1140
1141 1
            return $return;
1142
        }
1143 28
        if ($from_encoding === 'JSON') {
1144 1
            $str = self::json_decode($str);
1145 1
            $from_encoding = '';
1146
        }
1147
1148 28
        if ($to_encoding === 'BASE64') {
1149 2
            return \base64_encode($str);
1150
        }
1151 28
        if ($from_encoding === 'BASE64') {
1152 2
            $str = \base64_decode($str, true);
1153 2
            $from_encoding = '';
1154
        }
1155
1156 28
        if ($to_encoding === 'HTML-ENTITIES') {
1157 2
            return self::html_encode($str, true, 'UTF-8');
1158
        }
1159 28
        if ($from_encoding === 'HTML-ENTITIES') {
1160 2
            $str = self::html_entity_decode($str, \ENT_COMPAT, 'UTF-8');
1161 2
            $from_encoding = '';
1162
        }
1163
1164 28
        $from_encoding_auto_detected = false;
1165
        if (
1166 28
            $auto_detect_the_from_encoding === true
1167
            ||
1168 28
            !$from_encoding
1169
        ) {
1170 28
            $from_encoding_auto_detected = self::str_detect_encoding($str);
1171
        }
1172
1173
        // DEBUG
1174
        //var_dump($to_encoding, $from_encoding, $from_encoding_auto_detected, $str, "\n\n");
1175
1176 28
        if ($from_encoding_auto_detected !== false) {
1177
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
1178 24
            $from_encoding = $from_encoding_auto_detected;
1179 7
        } elseif ($auto_detect_the_from_encoding === true) {
1180
            // fallback for the "autodetect"-mode
1181 7
            return self::to_utf8($str);
1182
        }
1183
1184
        if (
1185 24
            !$from_encoding
1186
            ||
1187 24
            $from_encoding === $to_encoding
1188
        ) {
1189 15
            return $str;
1190
        }
1191
1192
        if (
1193 19
            $to_encoding === 'UTF-8'
1194
            &&
1195
            (
1196 17
                $from_encoding === 'WINDOWS-1252'
1197
                ||
1198 19
                $from_encoding === 'ISO-8859-1'
1199
            )
1200
        ) {
1201 13
            return self::to_utf8($str);
1202
        }
1203
1204
        if (
1205 12
            $to_encoding === 'ISO-8859-1'
1206
            &&
1207
            (
1208 6
                $from_encoding === 'WINDOWS-1252'
1209
                ||
1210 12
                $from_encoding === 'UTF-8'
1211
            )
1212
        ) {
1213 6
            return self::to_iso8859($str);
1214
        }
1215
1216
        if (
1217 10
            $to_encoding !== 'UTF-8'
1218
            &&
1219 10
            $to_encoding !== 'ISO-8859-1'
1220
            &&
1221 10
            $to_encoding !== 'WINDOWS-1252'
1222
            &&
1223 10
            self::$SUPPORT['mbstring'] === false
1224
        ) {
1225
            \trigger_error('UTF8::encode() without mbstring cannot handle "' . $to_encoding . '" encoding', \E_USER_WARNING);
1226
        }
1227
1228 10
        if (self::$SUPPORT['mbstring'] === true) {
1229
            // warning: do not use the symfony polyfill here
1230 10
            $str_encoded = \mb_convert_encoding(
1231 10
                $str,
1232 10
                $to_encoding,
1233 10
                $from_encoding
1234
            );
1235
1236 10
            if ($str_encoded) {
1237 10
                return $str_encoded;
1238
            }
1239
        }
1240
1241
        $return = \iconv($from_encoding, $to_encoding, $str);
1242
        if ($return !== false) {
1243
            return $return;
1244
        }
1245
1246
        return $str;
1247
    }
1248
1249
    /**
1250
     * @param string $str
1251
     * @param string $from_charset      [optional] <p>Set the input charset.</p>
1252
     * @param string $to_charset        [optional] <p>Set the output charset.</p>
1253
     * @param string $transfer_encoding [optional] <p>Set the transfer encoding.</p>
1254
     * @param string $linefeed          [optional] <p>Set the used linefeed.</p>
1255
     * @param int    $indent            [optional] <p>Set the max length indent.</p>
1256
     *
1257
     * @return false|string
1258
     *                      <p>An encoded MIME field on success,
1259
     *                      or false if an error occurs during the encoding.</p>
1260
     */
1261
    public static function encode_mimeheader(
1262
        $str,
1263
        $from_charset = 'UTF-8',
1264
        $to_charset = 'UTF-8',
1265
        $transfer_encoding = 'Q',
1266
        $linefeed = '\\r\\n',
1267
        $indent = 76
1268
    ) {
1269
        if ($from_charset !== 'UTF-8' && $from_charset !== 'CP850') {
1270
            $from_charset = self::normalize_encoding($from_charset, 'UTF-8');
1271
        }
1272
1273
        if ($to_charset !== 'UTF-8' && $to_charset !== 'CP850') {
1274
            $to_charset = self::normalize_encoding($to_charset, 'UTF-8');
1275
        }
1276
1277
        return \iconv_mime_encode(
1278
            '',
1279
            $str,
1280
            [
1281
                'scheme'           => $transfer_encoding,
1282
                'line-length'      => $indent,
1283
                'input-charset'    => $from_charset,
1284
                'output-charset'   => $to_charset,
1285
                'line-break-chars' => $linefeed,
1286
            ]
1287
        );
1288
    }
1289
1290
    /**
1291
     * Create an extract from a sentence, so if the search-string was found, it try to centered in the output.
1292
     *
1293
     * @param string   $str                       <p>The input string.</p>
1294
     * @param string   $search                    <p>The searched string.</p>
1295
     * @param int|null $length                    [optional] <p>Default: null === text->length / 2</p>
1296
     * @param string   $replacer_for_skipped_text [optional] <p>Default: …</p>
1297
     * @param string   $encoding                  [optional] <p>Set the charset for e.g. "mb_" function</p>
1298
     *
1299
     * @return string
1300
     */
1301 1
    public static function extract_text(
1302
        string $str,
1303
        string $search = '',
1304
        int $length = null,
1305
        string $replacer_for_skipped_text = '…',
1306
        string $encoding = 'UTF-8'
1307
    ): string {
1308 1
        if ($str === '') {
1309 1
            return '';
1310
        }
1311
1312 1
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
1313
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
1314
        }
1315
1316 1
        $trim_chars = "\t\r\n -_()!~?=+/*\\,.:;\"'[]{}`&";
1317
1318 1
        if ($length === null) {
1319 1
            $length = (int) \round((int) self::strlen($str, $encoding) / 2, 0);
1320
        }
1321
1322 1
        if ($search === '') {
1323 1
            if ($encoding === 'UTF-8') {
1324 1
                if ($length > 0) {
1325 1
                    $string_length = (int) \mb_strlen($str);
1326 1
                    $end = ($length - 1) > $string_length ? $string_length : ($length - 1);
1327
                } else {
1328 1
                    $end = 0;
1329
                }
1330
1331 1
                $pos = (int) \min(
1332 1
                    \mb_strpos($str, ' ', $end),
1333 1
                    \mb_strpos($str, '.', $end)
1334
                );
1335
            } else {
1336
                if ($length > 0) {
1337
                    $string_length = (int) self::strlen($str, $encoding);
1338
                    $end = ($length - 1) > $string_length ? $string_length : ($length - 1);
1339
                } else {
1340
                    $end = 0;
1341
                }
1342
1343
                $pos = (int) \min(
1344
                    self::strpos($str, ' ', $end, $encoding),
1345
                    self::strpos($str, '.', $end, $encoding)
1346
                );
1347
            }
1348
1349 1
            if ($pos) {
1350 1
                if ($encoding === 'UTF-8') {
1351 1
                    $str_sub = \mb_substr($str, 0, $pos);
1352
                } else {
1353
                    $str_sub = self::substr($str, 0, $pos, $encoding);
1354
                }
1355
1356 1
                if ($str_sub === false) {
1357
                    return '';
1358
                }
1359
1360 1
                return \rtrim($str_sub, $trim_chars) . $replacer_for_skipped_text;
1361
            }
1362
1363
            return $str;
1364
        }
1365
1366 1
        if ($encoding === 'UTF-8') {
1367 1
            $word_position = (int) \mb_stripos($str, $search);
1368 1
            $half_side = (int) ($word_position - $length / 2 + (int) \mb_strlen($search) / 2);
1369
        } else {
1370
            $word_position = (int) self::stripos($str, $search, 0, $encoding);
1371
            $half_side = (int) ($word_position - $length / 2 + (int) self::strlen($search, $encoding) / 2);
1372
        }
1373
1374 1
        $pos_start = 0;
1375 1
        if ($half_side > 0) {
1376 1
            if ($encoding === 'UTF-8') {
1377 1
                $half_text = \mb_substr($str, 0, $half_side);
1378
            } else {
1379
                $half_text = self::substr($str, 0, $half_side, $encoding);
1380
            }
1381 1
            if ($half_text !== false) {
1382 1
                if ($encoding === 'UTF-8') {
1383 1
                    $pos_start = (int) \max(
1384 1
                        \mb_strrpos($half_text, ' '),
1385 1
                        \mb_strrpos($half_text, '.')
1386
                    );
1387
                } else {
1388
                    $pos_start = (int) \max(
1389
                        self::strrpos($half_text, ' ', 0, $encoding),
1390
                        self::strrpos($half_text, '.', 0, $encoding)
1391
                    );
1392
                }
1393
            }
1394
        }
1395
1396 1
        if ($word_position && $half_side > 0) {
1397 1
            $offset = $pos_start + $length - 1;
1398 1
            $real_length = (int) self::strlen($str, $encoding);
1399
1400 1
            if ($offset > $real_length) {
1401
                $offset = $real_length;
1402
            }
1403
1404 1
            if ($encoding === 'UTF-8') {
1405 1
                $pos_end = (int) \min(
1406 1
                    \mb_strpos($str, ' ', $offset),
1407 1
                    \mb_strpos($str, '.', $offset)
1408 1
                ) - $pos_start;
1409
            } else {
1410
                $pos_end = (int) \min(
1411
                    self::strpos($str, ' ', $offset, $encoding),
1412
                    self::strpos($str, '.', $offset, $encoding)
1413
                ) - $pos_start;
1414
            }
1415
1416 1
            if (!$pos_end || $pos_end <= 0) {
1417 1
                if ($encoding === 'UTF-8') {
1418 1
                    $str_sub = \mb_substr($str, $pos_start, (int) \mb_strlen($str));
1419
                } else {
1420
                    $str_sub = self::substr($str, $pos_start, (int) self::strlen($str, $encoding), $encoding);
1421
                }
1422 1
                if ($str_sub !== false) {
1423 1
                    $extract = $replacer_for_skipped_text . \ltrim($str_sub, $trim_chars);
1424
                } else {
1425 1
                    $extract = '';
1426
                }
1427
            } else {
1428 1
                if ($encoding === 'UTF-8') {
1429 1
                    $str_sub = \mb_substr($str, $pos_start, $pos_end);
1430
                } else {
1431
                    $str_sub = self::substr($str, $pos_start, $pos_end, $encoding);
1432
                }
1433 1
                if ($str_sub !== false) {
1434 1
                    $extract = $replacer_for_skipped_text . \trim($str_sub, $trim_chars) . $replacer_for_skipped_text;
1435
                } else {
1436 1
                    $extract = '';
1437
                }
1438
            }
1439
        } else {
1440 1
            $offset = $length - 1;
1441 1
            $true_length = (int) self::strlen($str, $encoding);
1442
1443 1
            if ($offset > $true_length) {
1444
                $offset = $true_length;
1445
            }
1446
1447 1
            if ($encoding === 'UTF-8') {
1448 1
                $pos_end = (int) \min(
1449 1
                    \mb_strpos($str, ' ', $offset),
1450 1
                    \mb_strpos($str, '.', $offset)
1451
                );
1452
            } else {
1453
                $pos_end = (int) \min(
1454
                    self::strpos($str, ' ', $offset, $encoding),
1455
                    self::strpos($str, '.', $offset, $encoding)
1456
                );
1457
            }
1458
1459 1
            if ($pos_end) {
1460 1
                if ($encoding === 'UTF-8') {
1461 1
                    $str_sub = \mb_substr($str, 0, $pos_end);
1462
                } else {
1463
                    $str_sub = self::substr($str, 0, $pos_end, $encoding);
1464
                }
1465 1
                if ($str_sub !== false) {
1466 1
                    $extract = \rtrim($str_sub, $trim_chars) . $replacer_for_skipped_text;
1467
                } else {
1468 1
                    $extract = '';
1469
                }
1470
            } else {
1471 1
                $extract = $str;
1472
            }
1473
        }
1474
1475 1
        return $extract;
1476
    }
1477
1478
    /**
1479
     * Reads entire file into a string.
1480
     *
1481
     * WARNING: Do not use UTF-8 Option ($convert_to_utf8) for binary files (e.g.: images) !!!
1482
     *
1483
     * @see http://php.net/manual/en/function.file-get-contents.php
1484
     *
1485
     * @param string        $filename         <p>
1486
     *                                        Name of the file to read.
1487
     *                                        </p>
1488
     * @param bool          $use_include_path [optional] <p>
1489
     *                                        Prior to PHP 5, this parameter is called
1490
     *                                        use_include_path and is a bool.
1491
     *                                        As of PHP 5 the FILE_USE_INCLUDE_PATH can be used
1492
     *                                        to trigger include path
1493
     *                                        search.
1494
     *                                        </p>
1495
     * @param resource|null $context          [optional] <p>
1496
     *                                        A valid context resource created with
1497
     *                                        stream_context_create. If you don't need to use a
1498
     *                                        custom context, you can skip this parameter by &null;.
1499
     *                                        </p>
1500
     * @param int|null      $offset           [optional] <p>
1501
     *                                        The offset where the reading starts.
1502
     *                                        </p>
1503
     * @param int|null      $max_length       [optional] <p>
1504
     *                                        Maximum length of data read. The default is to read until end
1505
     *                                        of file is reached.
1506
     *                                        </p>
1507
     * @param int           $timeout          <p>The time in seconds for the timeout.</p>
1508
     * @param bool          $convert_to_utf8  <strong>WARNING!!!</strong> <p>Maybe you can't use this option for
1509
     *                                        some files, because they used non default utf-8 chars. Binary files
1510
     *                                        like images or pdf will not be converted.</p>
1511
     * @param string        $from_encoding    [optional] <p>e.g. 'UTF-16', 'UTF-8', 'ISO-8859-1', etc.<br>
1512
     *                                        A empty string will trigger the autodetect anyway.</p>
1513
     *
1514
     * @return false|string
1515
     *                      <p>The function returns the read data as string or <b>false</b> on failure.</p>
1516
     */
1517 12
    public static function file_get_contents(
1518
        string $filename,
1519
        bool $use_include_path = false,
1520
        $context = null,
1521
        int $offset = null,
1522
        int $max_length = null,
1523
        int $timeout = 10,
1524
        bool $convert_to_utf8 = true,
1525
        string $from_encoding = ''
1526
    ) {
1527
        // init
1528 12
        $filename = \filter_var($filename, \FILTER_SANITIZE_STRING);
1529
        /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
1530 12
        if ($filename === false) {
1531
            return false;
1532
        }
1533
1534 12
        if ($timeout && $context === null) {
1535 9
            $context = \stream_context_create(
1536
                [
1537
                    'http' => [
1538 9
                        'timeout' => $timeout,
1539
                    ],
1540
                ]
1541
            );
1542
        }
1543
1544 12
        if ($offset === null) {
1545 12
            $offset = 0;
1546
        }
1547
1548 12
        if (\is_int($max_length) === true) {
1549 2
            $data = \file_get_contents($filename, $use_include_path, $context, $offset, $max_length);
1550
        } else {
1551 12
            $data = \file_get_contents($filename, $use_include_path, $context, $offset);
1552
        }
1553
1554
        // return false on error
1555 12
        if ($data === false) {
1556
            return false;
1557
        }
1558
1559 12
        if ($convert_to_utf8 === true) {
1560
            if (
1561 12
                self::is_binary($data, true) !== true
1562
                ||
1563 9
                self::is_utf16($data, false) !== false
1564
                ||
1565 12
                self::is_utf32($data, false) !== false
1566
            ) {
1567 9
                $data = self::encode('UTF-8', $data, false, $from_encoding);
1568 9
                $data = self::cleanup($data);
1569
            }
1570
        }
1571
1572 12
        return $data;
1573
    }
1574
1575
    /**
1576
     * Checks if a file starts with BOM (Byte Order Mark) character.
1577
     *
1578
     * @param string $file_path <p>Path to a valid file.</p>
1579
     *
1580
     * @throws \RuntimeException if file_get_contents() returned false
1581
     *
1582
     * @return bool
1583
     *              <p><strong>true</strong> if the file has BOM at the start, <strong>false</strong> otherwise</p>
1584
     */
1585 2
    public static function file_has_bom(string $file_path): bool
1586
    {
1587 2
        $file_content = \file_get_contents($file_path);
1588 2
        if ($file_content === false) {
1589
            throw new \RuntimeException('file_get_contents() returned false for:' . $file_path);
1590
        }
1591
1592 2
        return self::string_has_bom($file_content);
1593
    }
1594
1595
    /**
1596
     * Normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1597
     *
1598
     * @param mixed  $var
1599
     * @param int    $normalization_form
1600
     * @param string $leading_combining
1601
     *
1602
     * @return mixed
1603
     */
1604 62
    public static function filter(
1605
        $var,
1606
        int $normalization_form = \Normalizer::NFC,
1607
        string $leading_combining = '◌'
1608
    ) {
1609 62
        switch (\gettype($var)) {
1610 62
            case 'array':
1611
                /** @noinspection ForeachSourceInspection */
1612 6
                foreach ($var as $k => &$v) {
1613 6
                    $v = self::filter($v, $normalization_form, $leading_combining);
1614
                }
1615 6
                unset($v);
1616
1617 6
                break;
1618 62
            case 'object':
1619
                /** @noinspection ForeachSourceInspection */
1620 4
                foreach ($var as $k => &$v) {
1621 4
                    $v = self::filter($v, $normalization_form, $leading_combining);
1622
                }
1623 4
                unset($v);
1624
1625 4
                break;
1626 62
            case 'string':
1627
1628 62
                if (\strpos($var, "\r") !== false) {
1629
                    // Workaround https://bugs.php.net/65732
1630 3
                    $var = self::normalize_line_ending($var);
1631
                }
1632
1633 62
                if (ASCII::is_ascii($var) === false) {
1634 32
                    if (\Normalizer::isNormalized($var, $normalization_form)) {
1635 27
                        $n = '-';
1636
                    } else {
1637 12
                        $n = \Normalizer::normalize($var, $normalization_form);
1638
1639 12
                        if (isset($n[0])) {
1640 7
                            $var = $n;
1641
                        } else {
1642 8
                            $var = self::encode('UTF-8', $var, true);
1643
                        }
1644
                    }
1645
1646
                    if (
1647 32
                        $var[0] >= "\x80"
1648
                        &&
1649 32
                        isset($n[0], $leading_combining[0])
1650
                        &&
1651 32
                        \preg_match('/^\\p{Mn}/u', $var)
1652
                    ) {
1653
                        // Prevent leading combining chars
1654
                        // for NFC-safe concatenations.
1655 3
                        $var = $leading_combining . $var;
1656
                    }
1657
                }
1658
1659 62
                break;
1660
        }
1661
1662 62
        return $var;
1663
    }
1664
1665
    /**
1666
     * "filter_input()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1667
     *
1668
     * Gets a specific external variable by name and optionally filters it
1669
     *
1670
     * @see http://php.net/manual/en/function.filter-input.php
1671
     *
1672
     * @param int    $type          <p>
1673
     *                              One of <b>INPUT_GET</b>, <b>INPUT_POST</b>,
1674
     *                              <b>INPUT_COOKIE</b>, <b>INPUT_SERVER</b>, or
1675
     *                              <b>INPUT_ENV</b>.
1676
     *                              </p>
1677
     * @param string $variable_name <p>
1678
     *                              Name of a variable to get.
1679
     *                              </p>
1680
     * @param int    $filter        [optional] <p>
1681
     *                              The ID of the filter to apply. The
1682
     *                              manual page lists the available filters.
1683
     *                              </p>
1684
     * @param mixed  $options       [optional] <p>
1685
     *                              Associative array of options or bitwise disjunction of flags. If filter
1686
     *                              accepts options, flags can be provided in "flags" field of array.
1687
     *                              </p>
1688
     *
1689
     * @return mixed Value of the requested variable on success, <b>FALSE</b> if the filter fails, or <b>NULL</b> if the
1690
     *               <i>variable_name</i> variable is not set. If the flag <b>FILTER_NULL_ON_FAILURE</b> is used, it
1691
     *               returns <b>FALSE</b> if the variable is not set and <b>NULL</b> if the filter fails.
1692
     */
1693
    public static function filter_input(
1694
        int $type,
1695
        string $variable_name,
1696
        int $filter = \FILTER_DEFAULT,
1697
        $options = null
1698
    ) {
1699
        if ($options === null || \func_num_args() < 4) {
1700
            $var = \filter_input($type, $variable_name, $filter);
1701
        } else {
1702
            $var = \filter_input($type, $variable_name, $filter, $options);
1703
        }
1704
1705
        return self::filter($var);
1706
    }
1707
1708
    /**
1709
     * "filter_input_array()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1710
     *
1711
     * Gets external variables and optionally filters them
1712
     *
1713
     * @see http://php.net/manual/en/function.filter-input-array.php
1714
     *
1715
     * @param int   $type       <p>
1716
     *                          One of <b>INPUT_GET</b>, <b>INPUT_POST</b>,
1717
     *                          <b>INPUT_COOKIE</b>, <b>INPUT_SERVER</b>, or
1718
     *                          <b>INPUT_ENV</b>.
1719
     *                          </p>
1720
     * @param mixed $definition [optional] <p>
1721
     *                          An array defining the arguments. A valid key is a string
1722
     *                          containing a variable name and a valid value is either a filter type, or an array
1723
     *                          optionally specifying the filter, flags and options. If the value is an
1724
     *                          array, valid keys are filter which specifies the
1725
     *                          filter type,
1726
     *                          flags which specifies any flags that apply to the
1727
     *                          filter, and options which specifies any options that
1728
     *                          apply to the filter. See the example below for a better understanding.
1729
     *                          </p>
1730
     *                          <p>
1731
     *                          This parameter can be also an integer holding a filter constant. Then all values in the
1732
     *                          input array are filtered by this filter.
1733
     *                          </p>
1734
     * @param bool  $add_empty  [optional] <p>
1735
     *                          Add missing keys as <b>NULL</b> to the return value.
1736
     *                          </p>
1737
     *
1738
     * @return mixed An array containing the values of the requested variables on success, or <b>FALSE</b> on failure.
1739
     *               An array value will be <b>FALSE</b> if the filter fails, or <b>NULL</b> if the variable is not
1740
     *               set. Or if the flag <b>FILTER_NULL_ON_FAILURE</b> is used, it returns <b>FALSE</b> if the variable
1741
     *               is not set and <b>NULL</b> if the filter fails.
1742
     */
1743
    public static function filter_input_array(
1744
        int $type,
1745
        $definition = null,
1746
        bool $add_empty = true
1747
    ) {
1748
        if ($definition === null || \func_num_args() < 2) {
1749
            $a = \filter_input_array($type);
1750
        } else {
1751
            $a = \filter_input_array($type, $definition, $add_empty);
1752
        }
1753
1754
        return self::filter($a);
1755
    }
1756
1757
    /**
1758
     * "filter_var()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1759
     *
1760
     * Filters a variable with a specified filter
1761
     *
1762
     * @see http://php.net/manual/en/function.filter-var.php
1763
     *
1764
     * @param mixed $variable <p>
1765
     *                        Value to filter.
1766
     *                        </p>
1767
     * @param int   $filter   [optional] <p>
1768
     *                        The ID of the filter to apply. The
1769
     *                        manual page lists the available filters.
1770
     *                        </p>
1771
     * @param mixed $options  [optional] <p>
1772
     *                        Associative array of options or bitwise disjunction of flags. If filter
1773
     *                        accepts options, flags can be provided in "flags" field of array. For
1774
     *                        the "callback" filter, callable type should be passed. The
1775
     *                        callback must accept one argument, the value to be filtered, and return
1776
     *                        the value after filtering/sanitizing it.
1777
     *                        </p>
1778
     *                        <p>
1779
     *                        <code>
1780
     *                        // for filters that accept options, use this format
1781
     *                        $options = array(
1782
     *                        'options' => array(
1783
     *                        'default' => 3, // value to return if the filter fails
1784
     *                        // other options here
1785
     *                        'min_range' => 0
1786
     *                        ),
1787
     *                        'flags' => FILTER_FLAG_ALLOW_OCTAL,
1788
     *                        );
1789
     *                        $var = filter_var('0755', FILTER_VALIDATE_INT, $options);
1790
     *                        // for filter that only accept flags, you can pass them directly
1791
     *                        $var = filter_var('oops', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
1792
     *                        // for filter that only accept flags, you can also pass as an array
1793
     *                        $var = filter_var('oops', FILTER_VALIDATE_BOOLEAN,
1794
     *                        array('flags' => FILTER_NULL_ON_FAILURE));
1795
     *                        // callback validate filter
1796
     *                        function foo($value)
1797
     *                        {
1798
     *                        // Expected format: Surname, GivenNames
1799
     *                        if (strpos($value, ", ") === false) return false;
1800
     *                        list($surname, $givennames) = explode(", ", $value, 2);
1801
     *                        $empty = (empty($surname) || empty($givennames));
1802
     *                        $notstrings = (!is_string($surname) || !is_string($givennames));
1803
     *                        if ($empty || $notstrings) {
1804
     *                        return false;
1805
     *                        } else {
1806
     *                        return $value;
1807
     *                        }
1808
     *                        }
1809
     *                        $var = filter_var('Doe, Jane Sue', FILTER_CALLBACK, array('options' => 'foo'));
1810
     *                        </code>
1811
     *                        </p>
1812
     *
1813
     * @return mixed the filtered data, or <b>FALSE</b> if the filter fails
1814
     */
1815 2
    public static function filter_var(
1816
        $variable,
1817
        int $filter = \FILTER_DEFAULT,
1818
        $options = null
1819
    ) {
1820 2
        if (\func_num_args() < 3) {
1821 2
            $variable = \filter_var($variable, $filter);
1822
        } else {
1823 2
            $variable = \filter_var($variable, $filter, $options);
1824
        }
1825
1826 2
        return self::filter($variable);
1827
    }
1828
1829
    /**
1830
     * "filter_var_array()"-wrapper with normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
1831
     *
1832
     * Gets multiple variables and optionally filters them
1833
     *
1834
     * @see http://php.net/manual/en/function.filter-var-array.php
1835
     *
1836
     * @param array<mixed> $data       <p>
1837
     *                                 An array with string keys containing the data to filter.
1838
     *                                 </p>
1839
     * @param mixed        $definition [optional] <p>
1840
     *                                 An array defining the arguments. A valid key is a string
1841
     *                                 containing a variable name and a valid value is either a
1842
     *                                 filter type, or an
1843
     *                                 array optionally specifying the filter, flags and options.
1844
     *                                 If the value is an array, valid keys are filter
1845
     *                                 which specifies the filter type,
1846
     *                                 flags which specifies any flags that apply to the
1847
     *                                 filter, and options which specifies any options that
1848
     *                                 apply to the filter. See the example below for a better understanding.
1849
     *                                 </p>
1850
     *                                 <p>
1851
     *                                 This parameter can be also an integer holding a filter constant. Then all values in the
1852
     *                                 input array are filtered by this filter.
1853
     *                                 </p>
1854
     * @param bool         $add_empty  [optional] <p>
1855
     *                                 Add missing keys as <b>NULL</b> to the return value.
1856
     *                                 </p>
1857
     *
1858
     * @return mixed an array containing the values of the requested variables on success, or <b>FALSE</b> on failure.
1859
     *               An array value will be <b>FALSE</b> if the filter fails, or <b>NULL</b> if the variable is not
1860
     *               set
1861
     */
1862 2
    public static function filter_var_array(
1863
        array $data,
1864
        $definition = null,
1865
        bool $add_empty = true
1866
    ) {
1867 2
        if (\func_num_args() < 2) {
1868 2
            $a = \filter_var_array($data);
1869
        } else {
1870 2
            $a = \filter_var_array($data, $definition, $add_empty);
1871
        }
1872
1873 2
        return self::filter($a);
1874
    }
1875
1876
    /**
1877
     * Checks whether finfo is available on the server.
1878
     *
1879
     * @return bool
1880
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
1881
     */
1882
    public static function finfo_loaded(): bool
1883
    {
1884
        return \class_exists('finfo');
1885
    }
1886
1887
    /**
1888
     * Returns the first $n characters of the string.
1889
     *
1890
     * @param string $str      <p>The input string.</p>
1891
     * @param int    $n        <p>Number of characters to retrieve from the start.</p>
1892
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
1893
     *
1894
     * @return string
1895
     */
1896 13
    public static function first_char(
1897
        string $str,
1898
        int $n = 1,
1899
        string $encoding = 'UTF-8'
1900
    ): string {
1901 13
        if ($str === '' || $n <= 0) {
1902 5
            return '';
1903
        }
1904
1905 8
        if ($encoding === 'UTF-8') {
1906 4
            return (string) \mb_substr($str, 0, $n);
1907
        }
1908
1909 4
        return (string) self::substr($str, 0, $n, $encoding);
1910
    }
1911
1912
    /**
1913
     * Check if the number of Unicode characters isn't greater than the specified integer.
1914
     *
1915
     * @param string $str      the original string to be checked
1916
     * @param int    $box_size the size in number of chars to be checked against string
1917
     *
1918
     * @return bool true if string is less than or equal to $box_size, false otherwise
1919
     */
1920 2
    public static function fits_inside(string $str, int $box_size): bool
1921
    {
1922 2
        return (int) self::strlen($str) <= $box_size;
1923
    }
1924
1925
    /**
1926
     * Try to fix simple broken UTF-8 strings.
1927
     *
1928
     * INFO: Take a look at "UTF8::fix_utf8()" if you need a more advanced fix for broken UTF-8 strings.
1929
     *
1930
     * If you received an UTF-8 string that was converted from Windows-1252 as it was ISO-8859-1
1931
     * (ignoring Windows-1252 chars from 80 to 9F) use this function to fix it.
1932
     * See: http://en.wikipedia.org/wiki/Windows-1252
1933
     *
1934
     * @param string $str <p>The input string</p>
1935
     *
1936
     * @return string
1937
     */
1938 47
    public static function fix_simple_utf8(string $str): string
1939
    {
1940 47
        if ($str === '') {
1941 4
            return '';
1942
        }
1943
1944 47
        static $BROKEN_UTF8_TO_UTF8_KEYS_CACHE = null;
1945 47
        static $BROKEN_UTF8_TO_UTF8_VALUES_CACHE = null;
1946
1947 47
        if ($BROKEN_UTF8_TO_UTF8_KEYS_CACHE === null) {
1948 1
            if (self::$BROKEN_UTF8_FIX === null) {
1949 1
                self::$BROKEN_UTF8_FIX = self::getData('utf8_fix');
1950
            }
1951
1952 1
            $BROKEN_UTF8_TO_UTF8_KEYS_CACHE = \array_keys(self::$BROKEN_UTF8_FIX);
1953 1
            $BROKEN_UTF8_TO_UTF8_VALUES_CACHE = \array_values(self::$BROKEN_UTF8_FIX);
1954
        }
1955
1956 47
        return \str_replace($BROKEN_UTF8_TO_UTF8_KEYS_CACHE, $BROKEN_UTF8_TO_UTF8_VALUES_CACHE, $str);
1957
    }
1958
1959
    /**
1960
     * Fix a double (or multiple) encoded UTF8 string.
1961
     *
1962
     * @param string|string[] $str you can use a string or an array of strings
1963
     *
1964
     * @return string|string[]
1965
     *                         Will return the fixed input-"array" or
1966
     *                         the fixed input-"string"
1967
     *
1968
     * @psalm-suppress InvalidReturnType
1969
     */
1970 2
    public static function fix_utf8($str)
1971
    {
1972 2
        if (\is_array($str) === true) {
1973 2
            foreach ($str as $k => &$v) {
1974 2
                $v = self::fix_utf8($v);
1975
            }
1976 2
            unset($v);
1977
1978
            /**
1979
             * @psalm-suppress InvalidReturnStatement
1980
             */
1981 2
            return $str;
1982
        }
1983
1984 2
        $str = (string) $str;
1985 2
        $last = '';
1986 2
        while ($last !== $str) {
1987 2
            $last = $str;
1988
            /**
1989
             * @psalm-suppress PossiblyInvalidArgument
1990
             */
1991 2
            $str = self::to_utf8(
1992 2
                self::utf8_decode($str, true)
1993
            );
1994
        }
1995
1996
        /**
1997
         * @psalm-suppress InvalidReturnStatement
1998
         */
1999 2
        return $str;
2000
    }
2001
2002
    /**
2003
     * Get character of a specific character.
2004
     *
2005
     * @param string $char
2006
     *
2007
     * @return string 'RTL' or 'LTR'
2008
     */
2009 2
    public static function getCharDirection(string $char): string
2010
    {
2011 2
        if (self::$SUPPORT['intlChar'] === true) {
2012
            /** @noinspection PhpComposerExtensionStubsInspection */
2013 2
            $tmp_return = \IntlChar::charDirection($char);
2014
2015
            // from "IntlChar"-Class
2016
            $char_direction = [
2017 2
                'RTL' => [1, 13, 14, 15, 21],
2018
                'LTR' => [0, 11, 12, 20],
2019
            ];
2020
2021 2
            if (\in_array($tmp_return, $char_direction['LTR'], true)) {
2022
                return 'LTR';
2023
            }
2024
2025 2
            if (\in_array($tmp_return, $char_direction['RTL'], true)) {
2026 2
                return 'RTL';
2027
            }
2028
        }
2029
2030 2
        $c = static::chr_to_decimal($char);
2031
2032 2
        if (!($c >= 0x5be && $c <= 0x10b7f)) {
2033 2
            return 'LTR';
2034
        }
2035
2036 2
        if ($c <= 0x85e) {
2037 2
            if ($c === 0x5be ||
2038 2
                $c === 0x5c0 ||
2039 2
                $c === 0x5c3 ||
2040 2
                $c === 0x5c6 ||
2041 2
                ($c >= 0x5d0 && $c <= 0x5ea) ||
2042 2
                ($c >= 0x5f0 && $c <= 0x5f4) ||
2043 2
                $c === 0x608 ||
2044 2
                $c === 0x60b ||
2045 2
                $c === 0x60d ||
2046 2
                $c === 0x61b ||
2047 2
                ($c >= 0x61e && $c <= 0x64a) ||
2048
                ($c >= 0x66d && $c <= 0x66f) ||
2049
                ($c >= 0x671 && $c <= 0x6d5) ||
2050
                ($c >= 0x6e5 && $c <= 0x6e6) ||
2051
                ($c >= 0x6ee && $c <= 0x6ef) ||
2052
                ($c >= 0x6fa && $c <= 0x70d) ||
2053
                $c === 0x710 ||
2054
                ($c >= 0x712 && $c <= 0x72f) ||
2055
                ($c >= 0x74d && $c <= 0x7a5) ||
2056
                $c === 0x7b1 ||
2057
                ($c >= 0x7c0 && $c <= 0x7ea) ||
2058
                ($c >= 0x7f4 && $c <= 0x7f5) ||
2059
                $c === 0x7fa ||
2060
                ($c >= 0x800 && $c <= 0x815) ||
2061
                $c === 0x81a ||
2062
                $c === 0x824 ||
2063
                $c === 0x828 ||
2064
                ($c >= 0x830 && $c <= 0x83e) ||
2065
                ($c >= 0x840 && $c <= 0x858) ||
2066 2
                $c === 0x85e
2067
            ) {
2068 2
                return 'RTL';
2069
            }
2070 2
        } elseif ($c === 0x200f) {
2071
            return 'RTL';
2072 2
        } elseif ($c >= 0xfb1d) {
2073 2
            if ($c === 0xfb1d ||
2074 2
                ($c >= 0xfb1f && $c <= 0xfb28) ||
2075 2
                ($c >= 0xfb2a && $c <= 0xfb36) ||
2076 2
                ($c >= 0xfb38 && $c <= 0xfb3c) ||
2077 2
                $c === 0xfb3e ||
2078 2
                ($c >= 0xfb40 && $c <= 0xfb41) ||
2079 2
                ($c >= 0xfb43 && $c <= 0xfb44) ||
2080 2
                ($c >= 0xfb46 && $c <= 0xfbc1) ||
2081 2
                ($c >= 0xfbd3 && $c <= 0xfd3d) ||
2082 2
                ($c >= 0xfd50 && $c <= 0xfd8f) ||
2083 2
                ($c >= 0xfd92 && $c <= 0xfdc7) ||
2084 2
                ($c >= 0xfdf0 && $c <= 0xfdfc) ||
2085 2
                ($c >= 0xfe70 && $c <= 0xfe74) ||
2086 2
                ($c >= 0xfe76 && $c <= 0xfefc) ||
2087 2
                ($c >= 0x10800 && $c <= 0x10805) ||
2088 2
                $c === 0x10808 ||
2089 2
                ($c >= 0x1080a && $c <= 0x10835) ||
2090 2
                ($c >= 0x10837 && $c <= 0x10838) ||
2091 2
                $c === 0x1083c ||
2092 2
                ($c >= 0x1083f && $c <= 0x10855) ||
2093 2
                ($c >= 0x10857 && $c <= 0x1085f) ||
2094 2
                ($c >= 0x10900 && $c <= 0x1091b) ||
2095 2
                ($c >= 0x10920 && $c <= 0x10939) ||
2096 2
                $c === 0x1093f ||
2097 2
                $c === 0x10a00 ||
2098 2
                ($c >= 0x10a10 && $c <= 0x10a13) ||
2099 2
                ($c >= 0x10a15 && $c <= 0x10a17) ||
2100 2
                ($c >= 0x10a19 && $c <= 0x10a33) ||
2101 2
                ($c >= 0x10a40 && $c <= 0x10a47) ||
2102 2
                ($c >= 0x10a50 && $c <= 0x10a58) ||
2103 2
                ($c >= 0x10a60 && $c <= 0x10a7f) ||
2104 2
                ($c >= 0x10b00 && $c <= 0x10b35) ||
2105 2
                ($c >= 0x10b40 && $c <= 0x10b55) ||
2106 2
                ($c >= 0x10b58 && $c <= 0x10b72) ||
2107 2
                ($c >= 0x10b78 && $c <= 0x10b7f)
2108
            ) {
2109 2
                return 'RTL';
2110
            }
2111
        }
2112
2113 2
        return 'LTR';
2114
    }
2115
2116
    /**
2117
     * Check for php-support.
2118
     *
2119
     * @param string|null $key
2120
     *
2121
     * @return mixed
2122
     *               Return the full support-"array", if $key === null<br>
2123
     *               return bool-value, if $key is used and available<br>
2124
     *               otherwise return <strong>null</strong>
2125
     */
2126 27
    public static function getSupportInfo(string $key = null)
2127
    {
2128 27
        if ($key === null) {
2129 4
            return self::$SUPPORT;
2130
        }
2131
2132 25
        if (self::$INTL_TRANSLITERATOR_LIST === null) {
2133 1
            self::$INTL_TRANSLITERATOR_LIST = self::getData('transliterator_list');
2134
        }
2135
        // compatibility fix for old versions
2136 25
        self::$SUPPORT['intl__transliterator_list_ids'] = self::$INTL_TRANSLITERATOR_LIST;
2137
2138 25
        return self::$SUPPORT[$key] ?? null;
2139
    }
2140
2141
    /**
2142
     * Warning: this method only works for some file-types (png, jpg)
2143
     *          if you need more supported types, please use e.g. "finfo"
2144
     *
2145
     * @param string $str
2146
     * @param array  $fallback <p>with this keys: 'ext', 'mime', 'type'
2147
     *
2148
     * @return array<string, string|null>
2149
     *                       <p>with this keys: 'ext', 'mime', 'type'</p>
2150
     *
2151
     * @phpstan-param array{ext: null|string, mime: null|string, type: null|string} $fallback
2152
     */
2153 39
    public static function get_file_type(
2154
        string $str,
2155
        array $fallback = [
2156
            'ext'  => null,
2157
            'mime' => 'application/octet-stream',
2158
            'type' => null,
2159
        ]
2160
    ): array {
2161 39
        if ($str === '') {
2162
            return $fallback;
2163
        }
2164
2165
        /** @var false|string $str_info - needed for PhpStan (stubs error) */
2166 39
        $str_info = \substr($str, 0, 2);
2167 39
        if ($str_info === false || \strlen($str_info) !== 2) {
2168 11
            return $fallback;
2169
        }
2170
2171
        // DEBUG
2172
        //var_dump($str_info);
2173
2174 35
        $str_info = \unpack('C2chars', $str_info);
2175
        /** @var array|false $str_info - needed for PhpStan (stubs error) */
2176 35
        if ($str_info === false) {
2177
            return $fallback;
2178
        }
2179
        /** @noinspection OffsetOperationsInspection */
2180 35
        $type_code = (int) ($str_info['chars1'] . $str_info['chars2']);
2181
2182
        // DEBUG
2183
        //var_dump($type_code);
2184
2185
        //
2186
        // info: https://en.wikipedia.org/wiki/Magic_number_%28programming%29#Format_indicator
2187
        //
2188
        switch ($type_code) {
2189
            // WARNING: do not add too simple comparisons, because of false-positive results:
2190
            //
2191
            // 3780 => 'pdf', 7790 => 'exe', 7784 => 'midi', 8075 => 'zip',
2192
            // 8297 => 'rar', 7173 => 'gif', 7373 => 'tiff' 6677 => 'bmp', ...
2193
            //
2194 35
            case 255216:
2195
                $ext = 'jpg';
2196
                $mime = 'image/jpeg';
2197
                $type = 'binary';
2198
2199
                break;
2200 35
            case 13780:
2201 7
                $ext = 'png';
2202 7
                $mime = 'image/png';
2203 7
                $type = 'binary';
2204
2205 7
                break;
2206
            default:
2207 34
                return $fallback;
2208
        }
2209
2210
        return [
2211 7
            'ext'  => $ext,
2212 7
            'mime' => $mime,
2213 7
            'type' => $type,
2214
        ];
2215
    }
2216
2217
    /**
2218
     * @param int    $length         <p>Length of the random string.</p>
2219
     * @param string $possible_chars [optional] <p>Characters string for the random selection.</p>
2220
     * @param string $encoding       [optional] <p>Set the charset for e.g. "mb_" function</p>
2221
     *
2222
     * @return string
2223
     */
2224 1
    public static function get_random_string(
2225
        int $length,
2226
        string $possible_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
2227
        string $encoding = 'UTF-8'
2228
    ): string {
2229
        // init
2230 1
        $i = 0;
2231 1
        $str = '';
2232
2233
        //
2234
        // add random chars
2235
        //
2236
2237 1
        if ($encoding === 'UTF-8') {
2238 1
            $max_length = (int) \mb_strlen($possible_chars);
2239 1
            if ($max_length === 0) {
2240 1
                return '';
2241
            }
2242
2243 1
            while ($i < $length) {
2244
                try {
2245 1
                    $rand_int = \random_int(0, $max_length - 1);
2246
                } catch (\Exception $e) {
2247
                    /** @noinspection RandomApiMigrationInspection */
2248
                    $rand_int = \mt_rand(0, $max_length - 1);
2249
                }
2250 1
                $char = \mb_substr($possible_chars, $rand_int, 1);
2251 1
                if ($char !== false) {
2252 1
                    $str .= $char;
2253 1
                    ++$i;
2254
                }
2255
            }
2256
        } else {
2257
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2258
2259
            $max_length = (int) self::strlen($possible_chars, $encoding);
2260
            if ($max_length === 0) {
2261
                return '';
2262
            }
2263
2264
            while ($i < $length) {
2265
                try {
2266
                    $rand_int = \random_int(0, $max_length - 1);
2267
                } catch (\Exception $e) {
2268
                    /** @noinspection RandomApiMigrationInspection */
2269
                    $rand_int = \mt_rand(0, $max_length - 1);
2270
                }
2271
                $char = self::substr($possible_chars, $rand_int, 1, $encoding);
2272
                if ($char !== false) {
2273
                    $str .= $char;
2274
                    ++$i;
2275
                }
2276
            }
2277
        }
2278
2279 1
        return $str;
2280
    }
2281
2282
    /**
2283
     * @param int|string $entropy_extra [optional] <p>Extra entropy via a string or int value.</p>
2284
     * @param bool       $use_md5       [optional] <p>Return the unique identifier as md5-hash? Default: true</p>
2285
     *
2286
     * @return string
2287
     */
2288 1
    public static function get_unique_string($entropy_extra = '', bool $use_md5 = true): string
2289
    {
2290 1
        $unique_helper = \random_int(0, \mt_getrandmax()) .
2291 1
                        \session_id() .
2292 1
                        ($_SERVER['REMOTE_ADDR'] ?? '') .
2293 1
                        ($_SERVER['SERVER_ADDR'] ?? '') .
2294 1
                        $entropy_extra;
2295
2296 1
        $unique_string = \uniqid($unique_helper, true);
2297
2298 1
        if ($use_md5) {
2299 1
            $unique_string = \md5($unique_string . $unique_helper);
2300
        }
2301
2302 1
        return $unique_string;
2303
    }
2304
2305
    /**
2306
     * alias for "UTF8::string_has_bom()"
2307
     *
2308
     * @param string $str
2309
     *
2310
     * @return bool
2311
     *
2312
     * @see UTF8::string_has_bom()
2313
     * @deprecated <p>please use "UTF8::string_has_bom()"</p>
2314
     */
2315 2
    public static function hasBom(string $str): bool
2316
    {
2317 2
        return self::string_has_bom($str);
2318
    }
2319
2320
    /**
2321
     * Returns true if the string contains a lower case char, false otherwise.
2322
     *
2323
     * @param string $str <p>The input string.</p>
2324
     *
2325
     * @return bool
2326
     *              <p>Whether or not the string contains a lower case character.</p>
2327
     */
2328 47
    public static function has_lowercase(string $str): bool
2329
    {
2330 47
        if (self::$SUPPORT['mbstring'] === true) {
2331
            /** @noinspection PhpComposerExtensionStubsInspection */
2332 47
            return \mb_ereg_match('.*[[:lower:]]', $str);
2333
        }
2334
2335
        return self::str_matches_pattern($str, '.*[[:lower:]]');
2336
    }
2337
2338
    /**
2339
     * Returns true if the string contains whitespace, false otherwise.
2340
     *
2341
     * @param string $str <p>The input string.</p>
2342
     *
2343
     * @return bool
2344
     *              <p>Whether or not the string contains whitespace.</p>
2345
     */
2346 11
    public static function has_whitespace(string $str): bool
2347
    {
2348 11
        if (self::$SUPPORT['mbstring'] === true) {
2349
            /** @noinspection PhpComposerExtensionStubsInspection */
2350 11
            return \mb_ereg_match('.*[[:space:]]', $str);
2351
        }
2352
2353
        return self::str_matches_pattern($str, '.*[[:space:]]');
2354
    }
2355
2356
    /**
2357
     * Returns true if the string contains an upper case char, false otherwise.
2358
     *
2359
     * @param string $str <p>The input string.</p>
2360
     *
2361
     * @return bool whether or not the string contains an upper case character
2362
     */
2363 12
    public static function has_uppercase(string $str): bool
2364
    {
2365 12
        if (self::$SUPPORT['mbstring'] === true) {
2366
            /** @noinspection PhpComposerExtensionStubsInspection */
2367 12
            return \mb_ereg_match('.*[[:upper:]]', $str);
2368
        }
2369
2370
        return self::str_matches_pattern($str, '.*[[:upper:]]');
2371
    }
2372
2373
    /**
2374
     * Converts a hexadecimal value into a UTF-8 character.
2375
     *
2376
     * @param string $hexdec <p>The hexadecimal value.</p>
2377
     *
2378
     * @return false|string one single UTF-8 character
2379
     */
2380 4
    public static function hex_to_chr(string $hexdec)
2381
    {
2382 4
        return self::decimal_to_chr(\hexdec($hexdec));
2383
    }
2384
2385
    /**
2386
     * Converts hexadecimal U+xxxx code point representation to integer.
2387
     *
2388
     * INFO: opposite to UTF8::int_to_hex()
2389
     *
2390
     * @param string $hexdec <p>The hexadecimal code point representation.</p>
2391
     *
2392
     * @return false|int the code point, or false on failure
2393
     */
2394 2
    public static function hex_to_int($hexdec)
2395
    {
2396
        // init
2397 2
        $hexdec = (string) $hexdec;
2398
2399 2
        if ($hexdec === '') {
2400 2
            return false;
2401
        }
2402
2403 2
        if (\preg_match('/^(?:\\\u|U\+|)([a-zA-Z0-9]{4,6})$/', $hexdec, $match)) {
2404 2
            return \intval($match[1], 16);
2405
        }
2406
2407 2
        return false;
2408
    }
2409
2410
    /**
2411
     * alias for "UTF8::html_entity_decode()"
2412
     *
2413
     * @param string $str
2414
     * @param int    $flags
2415
     * @param string $encoding
2416
     *
2417
     * @return string
2418
     *
2419
     * @see UTF8::html_entity_decode()
2420
     * @deprecated <p>please use "UTF8::html_entity_decode()"</p>
2421
     */
2422 2
    public static function html_decode(
2423
        string $str,
2424
        int $flags = null,
2425
        string $encoding = 'UTF-8'
2426
    ): string {
2427 2
        return self::html_entity_decode($str, $flags, $encoding);
2428
    }
2429
2430
    /**
2431
     * Converts a UTF-8 string to a series of HTML numbered entities.
2432
     *
2433
     * INFO: opposite to UTF8::html_decode()
2434
     *
2435
     * @param string $str              <p>The Unicode string to be encoded as numbered entities.</p>
2436
     * @param bool   $keep_ascii_chars [optional] <p>Keep ASCII chars.</p>
2437
     * @param string $encoding         [optional] <p>Set the charset for e.g. "mb_" function</p>
2438
     *
2439
     * @return string HTML numbered entities
2440
     */
2441 14
    public static function html_encode(
2442
        string $str,
2443
        bool $keep_ascii_chars = false,
2444
        string $encoding = 'UTF-8'
2445
    ): string {
2446 14
        if ($str === '') {
2447 4
            return '';
2448
        }
2449
2450 14
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2451 4
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2452
        }
2453
2454
        // INFO: http://stackoverflow.com/questions/35854535/better-explanation-of-convmap-in-mb-encode-numericentity
2455 14
        if (self::$SUPPORT['mbstring'] === true) {
2456 14
            $start_code = 0x00;
2457 14
            if ($keep_ascii_chars === true) {
2458 13
                $start_code = 0x80;
2459
            }
2460
2461 14
            if ($encoding === 'UTF-8') {
2462
                /** @var false|string|null $return - needed for PhpStan (stubs error) */
2463 14
                $return = \mb_encode_numericentity(
2464 14
                    $str,
2465 14
                    [$start_code, 0xfffff, 0, 0xfffff, 0]
2466
                );
2467 14
                if ($return !== null && $return !== false) {
2468 14
                    return $return;
2469
                }
2470
            }
2471
2472
            /** @var false|string|null $return - needed for PhpStan (stubs error) */
2473 4
            $return = \mb_encode_numericentity(
2474 4
                $str,
2475 4
                [$start_code, 0xfffff, 0, 0xfffff, 0],
2476 4
                $encoding
2477
            );
2478 4
            if ($return !== null && $return !== false) {
2479 4
                return $return;
2480
            }
2481
        }
2482
2483
        //
2484
        // fallback via vanilla php
2485
        //
2486
2487
        return \implode(
2488
            '',
2489
            \array_map(
2490
                static function (string $chr) use ($keep_ascii_chars, $encoding): string {
2491
                    return self::single_chr_html_encode($chr, $keep_ascii_chars, $encoding);
2492
                },
2493
                self::str_split($str)
2494
            )
2495
        );
2496
    }
2497
2498
    /**
2499
     * UTF-8 version of html_entity_decode()
2500
     *
2501
     * The reason we are not using html_entity_decode() by itself is because
2502
     * while it is not technically correct to leave out the semicolon
2503
     * at the end of an entity most browsers will still interpret the entity
2504
     * correctly. html_entity_decode() does not convert entities without
2505
     * semicolons, so we are left with our own little solution here. Bummer.
2506
     *
2507
     * Convert all HTML entities to their applicable characters
2508
     *
2509
     * INFO: opposite to UTF8::html_encode()
2510
     *
2511
     * @see http://php.net/manual/en/function.html-entity-decode.php
2512
     *
2513
     * @param string $str      <p>
2514
     *                         The input string.
2515
     *                         </p>
2516
     * @param int    $flags    [optional] <p>
2517
     *                         A bitmask of one or more of the following flags, which specify how to handle quotes
2518
     *                         and which document type to use. The default is ENT_COMPAT | ENT_HTML401.
2519
     *                         <table>
2520
     *                         Available <i>flags</i> constants
2521
     *                         <tr valign="top">
2522
     *                         <td>Constant Name</td>
2523
     *                         <td>Description</td>
2524
     *                         </tr>
2525
     *                         <tr valign="top">
2526
     *                         <td><b>ENT_COMPAT</b></td>
2527
     *                         <td>Will convert double-quotes and leave single-quotes alone.</td>
2528
     *                         </tr>
2529
     *                         <tr valign="top">
2530
     *                         <td><b>ENT_QUOTES</b></td>
2531
     *                         <td>Will convert both double and single quotes.</td>
2532
     *                         </tr>
2533
     *                         <tr valign="top">
2534
     *                         <td><b>ENT_NOQUOTES</b></td>
2535
     *                         <td>Will leave both double and single quotes unconverted.</td>
2536
     *                         </tr>
2537
     *                         <tr valign="top">
2538
     *                         <td><b>ENT_HTML401</b></td>
2539
     *                         <td>
2540
     *                         Handle code as HTML 4.01.
2541
     *                         </td>
2542
     *                         </tr>
2543
     *                         <tr valign="top">
2544
     *                         <td><b>ENT_XML1</b></td>
2545
     *                         <td>
2546
     *                         Handle code as XML 1.
2547
     *                         </td>
2548
     *                         </tr>
2549
     *                         <tr valign="top">
2550
     *                         <td><b>ENT_XHTML</b></td>
2551
     *                         <td>
2552
     *                         Handle code as XHTML.
2553
     *                         </td>
2554
     *                         </tr>
2555
     *                         <tr valign="top">
2556
     *                         <td><b>ENT_HTML5</b></td>
2557
     *                         <td>
2558
     *                         Handle code as HTML 5.
2559
     *                         </td>
2560
     *                         </tr>
2561
     *                         </table>
2562
     *                         </p>
2563
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
2564
     *
2565
     * @return string the decoded string
2566
     */
2567 51
    public static function html_entity_decode(
2568
        string $str,
2569
        int $flags = null,
2570
        string $encoding = 'UTF-8'
2571
    ): string {
2572
        if (
2573 51
            !isset($str[3]) // examples: &; || &x;
2574
            ||
2575 51
            \strpos($str, '&') === false // no "&"
2576
        ) {
2577 24
            return $str;
2578
        }
2579
2580 49
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2581 9
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2582
        }
2583
2584 49
        if ($flags === null) {
2585 11
            $flags = \ENT_QUOTES | \ENT_HTML5;
2586
        }
2587
2588
        if (
2589 49
            $encoding !== 'UTF-8'
2590
            &&
2591 49
            $encoding !== 'ISO-8859-1'
2592
            &&
2593 49
            $encoding !== 'WINDOWS-1252'
2594
            &&
2595 49
            self::$SUPPORT['mbstring'] === false
2596
        ) {
2597
            \trigger_error('UTF8::html_entity_decode() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
2598
        }
2599
2600
        do {
2601 49
            $str_compare = $str;
2602
2603 49
            if (\strpos($str, '&') !== false) {
2604 49
                if (\strpos($str, '&#') !== false) {
2605
                    // decode also numeric & UTF16 two byte entities
2606 41
                    $str = (string) \preg_replace(
2607 41
                        '/(&#(?:x0*[0-9a-fA-F]{2,6}(?![0-9a-fA-F;])|(?:0*\d{2,6}(?![0-9;]))))/S',
2608 41
                        '$1;',
2609 41
                        $str
2610
                    );
2611
                }
2612
2613 49
                $str = \html_entity_decode(
2614 49
                    $str,
2615 49
                    $flags,
2616 49
                    $encoding
2617
                );
2618
            }
2619 49
        } while ($str_compare !== $str);
2620
2621 49
        return $str;
2622
    }
2623
2624
    /**
2625
     * Create a escape html version of the string via "UTF8::htmlspecialchars()".
2626
     *
2627
     * @param string $str
2628
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
2629
     *
2630
     * @return string
2631
     */
2632 6
    public static function html_escape(string $str, string $encoding = 'UTF-8'): string
2633
    {
2634 6
        return self::htmlspecialchars(
2635 6
            $str,
2636 6
            \ENT_QUOTES | \ENT_SUBSTITUTE,
2637 6
            $encoding
2638
        );
2639
    }
2640
2641
    /**
2642
     * Remove empty html-tag.
2643
     *
2644
     * e.g.: <tag></tag>
2645
     *
2646
     * @param string $str
2647
     *
2648
     * @return string
2649
     */
2650 1
    public static function html_stripe_empty_tags(string $str): string
2651
    {
2652 1
        return (string) \preg_replace(
2653 1
            '/<[^\\/>]*?>\\s*?<\\/[^>]*?>/u',
2654 1
            '',
2655 1
            $str
2656
        );
2657
    }
2658
2659
    /**
2660
     * Convert all applicable characters to HTML entities: UTF-8 version of htmlentities()
2661
     *
2662
     * @see http://php.net/manual/en/function.htmlentities.php
2663
     *
2664
     * @param string $str           <p>
2665
     *                              The input string.
2666
     *                              </p>
2667
     * @param int    $flags         [optional] <p>
2668
     *                              A bitmask of one or more of the following flags, which specify how to handle
2669
     *                              quotes, invalid code unit sequences and the used document type. The default is
2670
     *                              ENT_COMPAT | ENT_HTML401.
2671
     *                              <table>
2672
     *                              Available <i>flags</i> constants
2673
     *                              <tr valign="top">
2674
     *                              <td>Constant Name</td>
2675
     *                              <td>Description</td>
2676
     *                              </tr>
2677
     *                              <tr valign="top">
2678
     *                              <td><b>ENT_COMPAT</b></td>
2679
     *                              <td>Will convert double-quotes and leave single-quotes alone.</td>
2680
     *                              </tr>
2681
     *                              <tr valign="top">
2682
     *                              <td><b>ENT_QUOTES</b></td>
2683
     *                              <td>Will convert both double and single quotes.</td>
2684
     *                              </tr>
2685
     *                              <tr valign="top">
2686
     *                              <td><b>ENT_NOQUOTES</b></td>
2687
     *                              <td>Will leave both double and single quotes unconverted.</td>
2688
     *                              </tr>
2689
     *                              <tr valign="top">
2690
     *                              <td><b>ENT_IGNORE</b></td>
2691
     *                              <td>
2692
     *                              Silently discard invalid code unit sequences instead of returning
2693
     *                              an empty string. Using this flag is discouraged as it
2694
     *                              may have security implications.
2695
     *                              </td>
2696
     *                              </tr>
2697
     *                              <tr valign="top">
2698
     *                              <td><b>ENT_SUBSTITUTE</b></td>
2699
     *                              <td>
2700
     *                              Replace invalid code unit sequences with a Unicode Replacement Character
2701
     *                              U+FFFD (UTF-8) or &#38;#38;#FFFD; (otherwise) instead of returning an empty
2702
     *                              string.
2703
     *                              </td>
2704
     *                              </tr>
2705
     *                              <tr valign="top">
2706
     *                              <td><b>ENT_DISALLOWED</b></td>
2707
     *                              <td>
2708
     *                              Replace invalid code points for the given document type with a
2709
     *                              Unicode Replacement Character U+FFFD (UTF-8) or &#38;#38;#FFFD;
2710
     *                              (otherwise) instead of leaving them as is. This may be useful, for
2711
     *                              instance, to ensure the well-formedness of XML documents with
2712
     *                              embedded external content.
2713
     *                              </td>
2714
     *                              </tr>
2715
     *                              <tr valign="top">
2716
     *                              <td><b>ENT_HTML401</b></td>
2717
     *                              <td>
2718
     *                              Handle code as HTML 4.01.
2719
     *                              </td>
2720
     *                              </tr>
2721
     *                              <tr valign="top">
2722
     *                              <td><b>ENT_XML1</b></td>
2723
     *                              <td>
2724
     *                              Handle code as XML 1.
2725
     *                              </td>
2726
     *                              </tr>
2727
     *                              <tr valign="top">
2728
     *                              <td><b>ENT_XHTML</b></td>
2729
     *                              <td>
2730
     *                              Handle code as XHTML.
2731
     *                              </td>
2732
     *                              </tr>
2733
     *                              <tr valign="top">
2734
     *                              <td><b>ENT_HTML5</b></td>
2735
     *                              <td>
2736
     *                              Handle code as HTML 5.
2737
     *                              </td>
2738
     *                              </tr>
2739
     *                              </table>
2740
     *                              </p>
2741
     * @param string $encoding      [optional] <p>
2742
     *                              Like <b>htmlspecialchars</b>,
2743
     *                              <b>htmlentities</b> takes an optional third argument
2744
     *                              <i>encoding</i> which defines encoding used in
2745
     *                              conversion.
2746
     *                              Although this argument is technically optional, you are highly
2747
     *                              encouraged to specify the correct value for your code.
2748
     *                              </p>
2749
     * @param bool   $double_encode [optional] <p>
2750
     *                              When <i>double_encode</i> is turned off PHP will not
2751
     *                              encode existing html entities. The default is to convert everything.
2752
     *                              </p>
2753
     *
2754
     * @return string
2755
     *                <p>
2756
     *                The encoded string.
2757
     *                <br><br>
2758
     *                If the input <i>string</i> contains an invalid code unit
2759
     *                sequence within the given <i>encoding</i> an empty string
2760
     *                will be returned, unless either the <b>ENT_IGNORE</b> or
2761
     *                <b>ENT_SUBSTITUTE</b> flags are set.
2762
     *                </p>
2763
     */
2764 9
    public static function htmlentities(
2765
        string $str,
2766
        int $flags = \ENT_COMPAT,
2767
        string $encoding = 'UTF-8',
2768
        bool $double_encode = true
2769
    ): string {
2770 9
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2771 7
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2772
        }
2773
2774 9
        $str = \htmlentities(
2775 9
            $str,
2776 9
            $flags,
2777 9
            $encoding,
2778 9
            $double_encode
2779
        );
2780
2781
        /**
2782
         * PHP doesn't replace a backslash to its html entity since this is something
2783
         * that's mostly used to escape characters when inserting in a database. Since
2784
         * we're using a decent database layer, we don't need this shit and we're replacing
2785
         * the double backslashes by its' html entity equivalent.
2786
         *
2787
         * https://github.com/forkcms/library/blob/master/spoon/filter/filter.php#L303
2788
         */
2789 9
        $str = \str_replace('\\', '&#92;', $str);
2790
2791 9
        return self::html_encode($str, true, $encoding);
2792
    }
2793
2794
    /**
2795
     * Convert only special characters to HTML entities: UTF-8 version of htmlspecialchars()
2796
     *
2797
     * INFO: Take a look at "UTF8::htmlentities()"
2798
     *
2799
     * @see http://php.net/manual/en/function.htmlspecialchars.php
2800
     *
2801
     * @param string $str           <p>
2802
     *                              The string being converted.
2803
     *                              </p>
2804
     * @param int    $flags         [optional] <p>
2805
     *                              A bitmask of one or more of the following flags, which specify how to handle
2806
     *                              quotes, invalid code unit sequences and the used document type. The default is
2807
     *                              ENT_COMPAT | ENT_HTML401.
2808
     *                              <table>
2809
     *                              Available <i>flags</i> constants
2810
     *                              <tr valign="top">
2811
     *                              <td>Constant Name</td>
2812
     *                              <td>Description</td>
2813
     *                              </tr>
2814
     *                              <tr valign="top">
2815
     *                              <td><b>ENT_COMPAT</b></td>
2816
     *                              <td>Will convert double-quotes and leave single-quotes alone.</td>
2817
     *                              </tr>
2818
     *                              <tr valign="top">
2819
     *                              <td><b>ENT_QUOTES</b></td>
2820
     *                              <td>Will convert both double and single quotes.</td>
2821
     *                              </tr>
2822
     *                              <tr valign="top">
2823
     *                              <td><b>ENT_NOQUOTES</b></td>
2824
     *                              <td>Will leave both double and single quotes unconverted.</td>
2825
     *                              </tr>
2826
     *                              <tr valign="top">
2827
     *                              <td><b>ENT_IGNORE</b></td>
2828
     *                              <td>
2829
     *                              Silently discard invalid code unit sequences instead of returning
2830
     *                              an empty string. Using this flag is discouraged as it
2831
     *                              may have security implications.
2832
     *                              </td>
2833
     *                              </tr>
2834
     *                              <tr valign="top">
2835
     *                              <td><b>ENT_SUBSTITUTE</b></td>
2836
     *                              <td>
2837
     *                              Replace invalid code unit sequences with a Unicode Replacement Character
2838
     *                              U+FFFD (UTF-8) or &#38;#38;#FFFD; (otherwise) instead of returning an empty
2839
     *                              string.
2840
     *                              </td>
2841
     *                              </tr>
2842
     *                              <tr valign="top">
2843
     *                              <td><b>ENT_DISALLOWED</b></td>
2844
     *                              <td>
2845
     *                              Replace invalid code points for the given document type with a
2846
     *                              Unicode Replacement Character U+FFFD (UTF-8) or &#38;#38;#FFFD;
2847
     *                              (otherwise) instead of leaving them as is. This may be useful, for
2848
     *                              instance, to ensure the well-formedness of XML documents with
2849
     *                              embedded external content.
2850
     *                              </td>
2851
     *                              </tr>
2852
     *                              <tr valign="top">
2853
     *                              <td><b>ENT_HTML401</b></td>
2854
     *                              <td>
2855
     *                              Handle code as HTML 4.01.
2856
     *                              </td>
2857
     *                              </tr>
2858
     *                              <tr valign="top">
2859
     *                              <td><b>ENT_XML1</b></td>
2860
     *                              <td>
2861
     *                              Handle code as XML 1.
2862
     *                              </td>
2863
     *                              </tr>
2864
     *                              <tr valign="top">
2865
     *                              <td><b>ENT_XHTML</b></td>
2866
     *                              <td>
2867
     *                              Handle code as XHTML.
2868
     *                              </td>
2869
     *                              </tr>
2870
     *                              <tr valign="top">
2871
     *                              <td><b>ENT_HTML5</b></td>
2872
     *                              <td>
2873
     *                              Handle code as HTML 5.
2874
     *                              </td>
2875
     *                              </tr>
2876
     *                              </table>
2877
     *                              </p>
2878
     * @param string $encoding      [optional] <p>
2879
     *                              Defines encoding used in conversion.
2880
     *                              </p>
2881
     *                              <p>
2882
     *                              For the purposes of this function, the encodings
2883
     *                              ISO-8859-1, ISO-8859-15,
2884
     *                              UTF-8, cp866,
2885
     *                              cp1251, cp1252, and
2886
     *                              KOI8-R are effectively equivalent, provided the
2887
     *                              <i>string</i> itself is valid for the encoding, as
2888
     *                              the characters affected by <b>htmlspecialchars</b> occupy
2889
     *                              the same positions in all of these encodings.
2890
     *                              </p>
2891
     * @param bool   $double_encode [optional] <p>
2892
     *                              When <i>double_encode</i> is turned off PHP will not
2893
     *                              encode existing html entities, the default is to convert everything.
2894
     *                              </p>
2895
     *
2896
     * @return string the converted string.
2897
     *                </p>
2898
     *                <p>
2899
     *                If the input <i>string</i> contains an invalid code unit
2900
     *                sequence within the given <i>encoding</i> an empty string
2901
     *                will be returned, unless either the <b>ENT_IGNORE</b> or
2902
     *                <b>ENT_SUBSTITUTE</b> flags are set
2903
     */
2904 8
    public static function htmlspecialchars(
2905
        string $str,
2906
        int $flags = \ENT_COMPAT,
2907
        string $encoding = 'UTF-8',
2908
        bool $double_encode = true
2909
    ): string {
2910 8
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
2911 8
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
2912
        }
2913
2914 8
        return \htmlspecialchars(
2915 8
            $str,
2916 8
            $flags,
2917 8
            $encoding,
2918 8
            $double_encode
2919
        );
2920
    }
2921
2922
    /**
2923
     * Checks whether iconv is available on the server.
2924
     *
2925
     * @return bool
2926
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
2927
     */
2928
    public static function iconv_loaded(): bool
2929
    {
2930
        return \extension_loaded('iconv');
2931
    }
2932
2933
    /**
2934
     * alias for "UTF8::decimal_to_chr()"
2935
     *
2936
     * @param mixed $int
2937
     *
2938
     * @return string
2939
     *
2940
     * @see UTF8::decimal_to_chr()
2941
     * @deprecated <p>please use "UTF8::decimal_to_chr()"</p>
2942
     */
2943 4
    public static function int_to_chr($int): string
2944
    {
2945 4
        return self::decimal_to_chr($int);
2946
    }
2947
2948
    /**
2949
     * Converts Integer to hexadecimal U+xxxx code point representation.
2950
     *
2951
     * INFO: opposite to UTF8::hex_to_int()
2952
     *
2953
     * @param int    $int    <p>The integer to be converted to hexadecimal code point.</p>
2954
     * @param string $prefix [optional]
2955
     *
2956
     * @return string the code point, or empty string on failure
2957
     */
2958 6
    public static function int_to_hex(int $int, string $prefix = 'U+'): string
2959
    {
2960 6
        $hex = \dechex($int);
2961
2962 6
        $hex = (\strlen($hex) < 4 ? \substr('0000' . $hex, -4) : $hex);
2963
2964 6
        return $prefix . $hex . '';
2965
    }
2966
2967
    /**
2968
     * Checks whether intl-char is available on the server.
2969
     *
2970
     * @return bool
2971
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
2972
     */
2973
    public static function intlChar_loaded(): bool
2974
    {
2975
        return \class_exists('IntlChar');
2976
    }
2977
2978
    /**
2979
     * Checks whether intl is available on the server.
2980
     *
2981
     * @return bool
2982
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
2983
     */
2984 5
    public static function intl_loaded(): bool
2985
    {
2986 5
        return \extension_loaded('intl');
2987
    }
2988
2989
    /**
2990
     * alias for "UTF8::is_ascii()"
2991
     *
2992
     * @param string $str
2993
     *
2994
     * @return bool
2995
     *
2996
     * @see UTF8::is_ascii()
2997
     * @deprecated <p>please use "UTF8::is_ascii()"</p>
2998
     */
2999 2
    public static function isAscii(string $str): bool
3000
    {
3001 2
        return ASCII::is_ascii($str);
3002
    }
3003
3004
    /**
3005
     * alias for "UTF8::is_base64()"
3006
     *
3007
     * @param string $str
3008
     *
3009
     * @return bool
3010
     *
3011
     * @see UTF8::is_base64()
3012
     * @deprecated <p>please use "UTF8::is_base64()"</p>
3013
     */
3014 2
    public static function isBase64($str): bool
3015
    {
3016 2
        return self::is_base64($str);
3017
    }
3018
3019
    /**
3020
     * alias for "UTF8::is_binary()"
3021
     *
3022
     * @param mixed $str
3023
     * @param bool  $strict
3024
     *
3025
     * @return bool
3026
     *
3027
     * @see UTF8::is_binary()
3028
     * @deprecated <p>please use "UTF8::is_binary()"</p>
3029
     */
3030 4
    public static function isBinary($str, $strict = false): bool
3031
    {
3032 4
        return self::is_binary($str, $strict);
3033
    }
3034
3035
    /**
3036
     * alias for "UTF8::is_bom()"
3037
     *
3038
     * @param string $utf8_chr
3039
     *
3040
     * @return bool
3041
     *
3042
     * @see UTF8::is_bom()
3043
     * @deprecated <p>please use "UTF8::is_bom()"</p>
3044
     */
3045 2
    public static function isBom(string $utf8_chr): bool
3046
    {
3047 2
        return self::is_bom($utf8_chr);
3048
    }
3049
3050
    /**
3051
     * alias for "UTF8::is_html()"
3052
     *
3053
     * @param string $str
3054
     *
3055
     * @return bool
3056
     *
3057
     * @see UTF8::is_html()
3058
     * @deprecated <p>please use "UTF8::is_html()"</p>
3059
     */
3060 2
    public static function isHtml(string $str): bool
3061
    {
3062 2
        return self::is_html($str);
3063
    }
3064
3065
    /**
3066
     * alias for "UTF8::is_json()"
3067
     *
3068
     * @param string $str
3069
     *
3070
     * @return bool
3071
     *
3072
     * @see UTF8::is_json()
3073
     * @deprecated <p>please use "UTF8::is_json()"</p>
3074
     */
3075
    public static function isJson(string $str): bool
3076
    {
3077
        return self::is_json($str);
3078
    }
3079
3080
    /**
3081
     * alias for "UTF8::is_utf16()"
3082
     *
3083
     * @param mixed $str
3084
     *
3085
     * @return false|int
3086
     *                   <strong>false</strong> if is't not UTF16,<br>
3087
     *                   <strong>1</strong> for UTF-16LE,<br>
3088
     *                   <strong>2</strong> for UTF-16BE
3089
     *
3090
     * @see UTF8::is_utf16()
3091
     * @deprecated <p>please use "UTF8::is_utf16()"</p>
3092
     */
3093 2
    public static function isUtf16($str)
3094
    {
3095 2
        return self::is_utf16($str);
3096
    }
3097
3098
    /**
3099
     * alias for "UTF8::is_utf32()"
3100
     *
3101
     * @param mixed $str
3102
     *
3103
     * @return false|int
3104
     *                   <strong>false</strong> if is't not UTF16,
3105
     *                   <strong>1</strong> for UTF-32LE,
3106
     *                   <strong>2</strong> for UTF-32BE
3107
     *
3108
     * @see UTF8::is_utf32()
3109
     * @deprecated <p>please use "UTF8::is_utf32()"</p>
3110
     */
3111 2
    public static function isUtf32($str)
3112
    {
3113 2
        return self::is_utf32($str);
3114
    }
3115
3116
    /**
3117
     * alias for "UTF8::is_utf8()"
3118
     *
3119
     * @param string $str
3120
     * @param bool   $strict
3121
     *
3122
     * @return bool
3123
     *
3124
     * @see UTF8::is_utf8()
3125
     * @deprecated <p>please use "UTF8::is_utf8()"</p>
3126
     */
3127 17
    public static function isUtf8($str, $strict = false): bool
3128
    {
3129 17
        return self::is_utf8($str, $strict);
3130
    }
3131
3132
    /**
3133
     * Returns true if the string contains only alphabetic chars, false otherwise.
3134
     *
3135
     * @param string $str <p>The input string.</p>
3136
     *
3137
     * @return bool
3138
     *              <p>Whether or not $str contains only alphabetic chars.</p>
3139
     */
3140 10
    public static function is_alpha(string $str): bool
3141
    {
3142 10
        if (self::$SUPPORT['mbstring'] === true) {
3143
            /** @noinspection PhpComposerExtensionStubsInspection */
3144 10
            return \mb_ereg_match('^[[:alpha:]]*$', $str);
3145
        }
3146
3147
        return self::str_matches_pattern($str, '^[[:alpha:]]*$');
3148
    }
3149
3150
    /**
3151
     * Returns true if the string contains only alphabetic and numeric chars, false otherwise.
3152
     *
3153
     * @param string $str <p>The input string.</p>
3154
     *
3155
     * @return bool
3156
     *              <p>Whether or not $str contains only alphanumeric chars.</p>
3157
     */
3158 13
    public static function is_alphanumeric(string $str): bool
3159
    {
3160 13
        if (self::$SUPPORT['mbstring'] === true) {
3161
            /** @noinspection PhpComposerExtensionStubsInspection */
3162 13
            return \mb_ereg_match('^[[:alnum:]]*$', $str);
3163
        }
3164
3165
        return self::str_matches_pattern($str, '^[[:alnum:]]*$');
3166
    }
3167
3168
    /**
3169
     * Checks if a string is 7 bit ASCII.
3170
     *
3171
     * @param string $str <p>The string to check.</p>
3172
     *
3173
     * @return bool
3174
     *              <p>
3175
     *              <strong>true</strong> if it is ASCII<br>
3176
     *              <strong>false</strong> otherwise
3177
     *              </p>
3178
     */
3179 8
    public static function is_ascii(string $str): bool
3180
    {
3181 8
        return ASCII::is_ascii($str);
3182
    }
3183
3184
    /**
3185
     * Returns true if the string is base64 encoded, false otherwise.
3186
     *
3187
     * @param mixed|string $str                   <p>The input string.</p>
3188
     * @param bool         $empty_string_is_valid [optional] <p>Is an empty string valid base64 or not?</p>
3189
     *
3190
     * @return bool whether or not $str is base64 encoded
3191
     */
3192 16
    public static function is_base64($str, $empty_string_is_valid = false): bool
3193
    {
3194
        if (
3195 16
            $empty_string_is_valid === false
3196
            &&
3197 16
            $str === ''
3198
        ) {
3199 3
            return false;
3200
        }
3201
3202
        /**
3203
         * @psalm-suppress RedundantConditionGivenDocblockType
3204
         */
3205 15
        if (\is_string($str) === false) {
3206 2
            return false;
3207
        }
3208
3209 15
        $base64String = \base64_decode($str, true);
3210
3211 15
        return $base64String !== false && \base64_encode($base64String) === $str;
3212
    }
3213
3214
    /**
3215
     * Check if the input is binary... (is look like a hack).
3216
     *
3217
     * @param mixed $input
3218
     * @param bool  $strict
3219
     *
3220
     * @return bool
3221
     */
3222 39
    public static function is_binary($input, bool $strict = false): bool
3223
    {
3224 39
        $input = (string) $input;
3225 39
        if ($input === '') {
3226 10
            return false;
3227
        }
3228
3229 39
        if (\preg_match('~^[01]+$~', $input)) {
3230 13
            return true;
3231
        }
3232
3233 39
        $ext = self::get_file_type($input);
3234 39
        if ($ext['type'] === 'binary') {
3235 7
            return true;
3236
        }
3237
3238 38
        $test_length = \strlen($input);
3239 38
        $test_null_counting = \substr_count($input, "\x0", 0, $test_length);
3240 38
        if (($test_null_counting / $test_length) > 0.25) {
3241 15
            return true;
3242
        }
3243
3244 34
        if ($strict === true) {
3245 34
            if (self::$SUPPORT['finfo'] === false) {
3246
                throw new \RuntimeException('ext-fileinfo: is not installed');
3247
            }
3248
3249
            /** @noinspection PhpComposerExtensionStubsInspection */
3250 34
            $finfo_encoding = (new \finfo(\FILEINFO_MIME_ENCODING))->buffer($input);
3251 34
            if ($finfo_encoding && $finfo_encoding === 'binary') {
3252 15
                return true;
3253
            }
3254
        }
3255
3256 30
        return false;
3257
    }
3258
3259
    /**
3260
     * Check if the file is binary.
3261
     *
3262
     * @param string $file
3263
     *
3264
     * @return bool
3265
     */
3266 6
    public static function is_binary_file($file): bool
3267
    {
3268
        // init
3269 6
        $block = '';
3270
3271 6
        $fp = \fopen($file, 'rb');
3272 6
        if (\is_resource($fp)) {
3273 6
            $block = \fread($fp, 512);
3274 6
            \fclose($fp);
3275
        }
3276
3277 6
        if ($block === '') {
3278 2
            return false;
3279
        }
3280
3281 6
        return self::is_binary($block, true);
3282
    }
3283
3284
    /**
3285
     * Returns true if the string contains only whitespace chars, false otherwise.
3286
     *
3287
     * @param string $str <p>The input string.</p>
3288
     *
3289
     * @return bool
3290
     *              <p>Whether or not $str contains only whitespace characters.</p>
3291
     */
3292 15
    public static function is_blank(string $str): bool
3293
    {
3294 15
        if (self::$SUPPORT['mbstring'] === true) {
3295
            /** @noinspection PhpComposerExtensionStubsInspection */
3296 15
            return \mb_ereg_match('^[[:space:]]*$', $str);
3297
        }
3298
3299
        return self::str_matches_pattern($str, '^[[:space:]]*$');
3300
    }
3301
3302
    /**
3303
     * Checks if the given string is equal to any "Byte Order Mark".
3304
     *
3305
     * WARNING: Use "UTF8::string_has_bom()" if you will check BOM in a string.
3306
     *
3307
     * @param string $str <p>The input string.</p>
3308
     *
3309
     * @return bool
3310
     *              <p><strong>true</strong> if the $utf8_chr is Byte Order Mark, <strong>false</strong> otherwise.</p>
3311
     */
3312 2
    public static function is_bom($str): bool
3313
    {
3314
        /** @noinspection PhpUnusedLocalVariableInspection */
3315 2
        foreach (self::$BOM as $bom_string => &$bom_byte_length) {
3316 2
            if ($str === $bom_string) {
3317 2
                return true;
3318
            }
3319
        }
3320
3321 2
        return false;
3322
    }
3323
3324
    /**
3325
     * Determine whether the string is considered to be empty.
3326
     *
3327
     * A variable is considered empty if it does not exist or if its value equals FALSE.
3328
     * empty() does not generate a warning if the variable does not exist.
3329
     *
3330
     * @param mixed $str
3331
     *
3332
     * @return bool whether or not $str is empty()
3333
     */
3334
    public static function is_empty($str): bool
3335
    {
3336
        return empty($str);
3337
    }
3338
3339
    /**
3340
     * Returns true if the string contains only hexadecimal chars, false otherwise.
3341
     *
3342
     * @param string $str <p>The input string.</p>
3343
     *
3344
     * @return bool
3345
     *              <p>Whether or not $str contains only hexadecimal chars.</p>
3346
     */
3347 13
    public static function is_hexadecimal(string $str): bool
3348
    {
3349 13
        if (self::$SUPPORT['mbstring'] === true) {
3350
            /** @noinspection PhpComposerExtensionStubsInspection */
3351 13
            return \mb_ereg_match('^[[:xdigit:]]*$', $str);
3352
        }
3353
3354
        return self::str_matches_pattern($str, '^[[:xdigit:]]*$');
3355
    }
3356
3357
    /**
3358
     * Check if the string contains any HTML tags.
3359
     *
3360
     * @param string $str <p>The input string.</p>
3361
     *
3362
     * @return bool
3363
     *              <p>Whether or not $str contains html elements.</p>
3364
     */
3365 3
    public static function is_html(string $str): bool
3366
    {
3367 3
        if ($str === '') {
3368 3
            return false;
3369
        }
3370
3371
        // init
3372 3
        $matches = [];
3373
3374 3
        $str = self::emoji_encode($str); // hack for emoji support :/
3375
3376 3
        \preg_match("/<\\/?\\w+(?:(?:\\s+\\w+(?:\\s*=\\s*(?:\".*?\"|'.*?'|[^'\">\\s]+))?)*\\s*|\\s*)\\/?>/u", $str, $matches);
3377
3378 3
        return $matches !== [];
3379
    }
3380
3381
    /**
3382
     * Try to check if "$str" is a JSON-string.
3383
     *
3384
     * @param string $str                                    <p>The input string.</p>
3385
     * @param bool   $only_array_or_object_results_are_valid [optional] <p>Only array and objects are valid json results.</p>
3386
     *
3387
     * @return bool
3388
     *              <p>Whether or not the $str is in JSON format.</p>
3389
     */
3390 42
    public static function is_json(
3391
        string $str,
3392
        $only_array_or_object_results_are_valid = true
3393
    ): bool {
3394 42
        if ($str === '') {
3395 4
            return false;
3396
        }
3397
3398 40
        if (self::$SUPPORT['json'] === false) {
3399
            throw new \RuntimeException('ext-json: is not installed');
3400
        }
3401
3402 40
        $json = self::json_decode($str);
3403 40
        if ($json === null && \strtoupper($str) !== 'NULL') {
3404 18
            return false;
3405
        }
3406
3407
        if (
3408 24
            $only_array_or_object_results_are_valid === true
3409
            &&
3410 24
            \is_object($json) === false
3411
            &&
3412 24
            \is_array($json) === false
3413
        ) {
3414 5
            return false;
3415
        }
3416
3417
        /** @noinspection PhpComposerExtensionStubsInspection */
3418 19
        return \json_last_error() === \JSON_ERROR_NONE;
3419
    }
3420
3421
    /**
3422
     * @param string $str <p>The input string.</p>
3423
     *
3424
     * @return bool
3425
     *              <p>Whether or not $str contains only lowercase chars.</p>
3426
     */
3427 8
    public static function is_lowercase(string $str): bool
3428
    {
3429 8
        if (self::$SUPPORT['mbstring'] === true) {
3430
            /** @noinspection PhpComposerExtensionStubsInspection */
3431 8
            return \mb_ereg_match('^[[:lower:]]*$', $str);
3432
        }
3433
3434
        return self::str_matches_pattern($str, '^[[:lower:]]*$');
3435
    }
3436
3437
    /**
3438
     * Returns true if the string is serialized, false otherwise.
3439
     *
3440
     * @param string $str <p>The input string.</p>
3441
     *
3442
     * @return bool
3443
     *              <p>Whether or not $str is serialized.</p>
3444
     */
3445 7
    public static function is_serialized(string $str): bool
3446
    {
3447 7
        if ($str === '') {
3448 1
            return false;
3449
        }
3450
3451
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
3452
        /** @noinspection UnserializeExploitsInspection */
3453 6
        return $str === 'b:0;'
3454
               ||
3455 6
               @\unserialize($str) !== false;
3456
    }
3457
3458
    /**
3459
     * Returns true if the string contains only lower case chars, false
3460
     * otherwise.
3461
     *
3462
     * @param string $str <p>The input string.</p>
3463
     *
3464
     * @return bool
3465
     *              <p>Whether or not $str contains only lower case characters.</p>
3466
     */
3467 8
    public static function is_uppercase(string $str): bool
3468
    {
3469 8
        if (self::$SUPPORT['mbstring'] === true) {
3470
            /** @noinspection PhpComposerExtensionStubsInspection */
3471 8
            return \mb_ereg_match('^[[:upper:]]*$', $str);
3472
        }
3473
3474
        return self::str_matches_pattern($str, '^[[:upper:]]*$');
3475
    }
3476
3477
    /**
3478
     * Check if the string is UTF-16.
3479
     *
3480
     * @param mixed $str                       <p>The input string.</p>
3481
     * @param bool  $check_if_string_is_binary
3482
     *
3483
     * @return false|int
3484
     *                   <strong>false</strong> if is't not UTF-16,<br>
3485
     *                   <strong>1</strong> for UTF-16LE,<br>
3486
     *                   <strong>2</strong> for UTF-16BE
3487
     */
3488 22
    public static function is_utf16($str, $check_if_string_is_binary = true)
3489
    {
3490
        // init
3491 22
        $str = (string) $str;
3492 22
        $str_chars = [];
3493
3494
        if (
3495 22
            $check_if_string_is_binary === true
3496
            &&
3497 22
            self::is_binary($str, true) === false
3498
        ) {
3499 2
            return false;
3500
        }
3501
3502 22
        if (self::$SUPPORT['mbstring'] === false) {
3503 3
            \trigger_error('UTF8::is_utf16() without mbstring may did not work correctly', \E_USER_WARNING);
3504
        }
3505
3506 22
        $str = self::remove_bom($str);
3507
3508 22
        $maybe_utf16le = 0;
3509 22
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-16LE');
3510 22
        if ($test) {
3511 15
            $test2 = \mb_convert_encoding($test, 'UTF-16LE', 'UTF-8');
3512 15
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-16LE');
3513 15
            if ($test3 === $test) {
3514
                /**
3515
                 * @psalm-suppress RedundantCondition
3516
                 */
3517 15
                if ($str_chars === []) {
3518 15
                    $str_chars = self::count_chars($str, true, false);
3519
                }
3520 15
                foreach (self::count_chars($test3) as $test3char => &$test3charEmpty) {
0 ignored issues
show
Bug introduced by
The expression self::count_chars($test3) cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
3521 15
                    if (\in_array($test3char, $str_chars, true) === true) {
3522 15
                        ++$maybe_utf16le;
3523
                    }
3524
                }
3525 15
                unset($test3charEmpty);
3526
            }
3527
        }
3528
3529 22
        $maybe_utf16be = 0;
3530 22
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-16BE');
3531 22
        if ($test) {
3532 15
            $test2 = \mb_convert_encoding($test, 'UTF-16BE', 'UTF-8');
3533 15
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-16BE');
3534 15
            if ($test3 === $test) {
3535 15
                if ($str_chars === []) {
3536 7
                    $str_chars = self::count_chars($str, true, false);
3537
                }
3538 15
                foreach (self::count_chars($test3) as $test3char => &$test3charEmpty) {
0 ignored issues
show
Bug introduced by
The expression self::count_chars($test3) cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
3539 15
                    if (\in_array($test3char, $str_chars, true) === true) {
3540 15
                        ++$maybe_utf16be;
3541
                    }
3542
                }
3543 15
                unset($test3charEmpty);
3544
            }
3545
        }
3546
3547 22
        if ($maybe_utf16be !== $maybe_utf16le) {
3548 7
            if ($maybe_utf16le > $maybe_utf16be) {
3549 5
                return 1;
3550
            }
3551
3552 6
            return 2;
3553
        }
3554
3555 18
        return false;
3556
    }
3557
3558
    /**
3559
     * Check if the string is UTF-32.
3560
     *
3561
     * @param mixed $str                       <p>The input string.</p>
3562
     * @param bool  $check_if_string_is_binary
3563
     *
3564
     * @return false|int
3565
     *                   <strong>false</strong> if is't not UTF-32,<br>
3566
     *                   <strong>1</strong> for UTF-32LE,<br>
3567
     *                   <strong>2</strong> for UTF-32BE
3568
     */
3569 20
    public static function is_utf32($str, $check_if_string_is_binary = true)
3570
    {
3571
        // init
3572 20
        $str = (string) $str;
3573 20
        $str_chars = [];
3574
3575
        if (
3576 20
            $check_if_string_is_binary === true
3577
            &&
3578 20
            self::is_binary($str, true) === false
3579
        ) {
3580 2
            return false;
3581
        }
3582
3583 20
        if (self::$SUPPORT['mbstring'] === false) {
3584 3
            \trigger_error('UTF8::is_utf32() without mbstring may did not work correctly', \E_USER_WARNING);
3585
        }
3586
3587 20
        $str = self::remove_bom($str);
3588
3589 20
        $maybe_utf32le = 0;
3590 20
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-32LE');
3591 20
        if ($test) {
3592 13
            $test2 = \mb_convert_encoding($test, 'UTF-32LE', 'UTF-8');
3593 13
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-32LE');
3594 13
            if ($test3 === $test) {
3595
                /**
3596
                 * @psalm-suppress RedundantCondition
3597
                 */
3598 13
                if ($str_chars === []) {
3599 13
                    $str_chars = self::count_chars($str, true, false);
3600
                }
3601 13
                foreach (self::count_chars($test3) as $test3char => &$test3charEmpty) {
0 ignored issues
show
Bug introduced by
The expression self::count_chars($test3) cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
3602 13
                    if (\in_array($test3char, $str_chars, true) === true) {
3603 13
                        ++$maybe_utf32le;
3604
                    }
3605
                }
3606 13
                unset($test3charEmpty);
3607
            }
3608
        }
3609
3610 20
        $maybe_utf32be = 0;
3611 20
        $test = \mb_convert_encoding($str, 'UTF-8', 'UTF-32BE');
3612 20
        if ($test) {
3613 13
            $test2 = \mb_convert_encoding($test, 'UTF-32BE', 'UTF-8');
3614 13
            $test3 = \mb_convert_encoding($test2, 'UTF-8', 'UTF-32BE');
3615 13
            if ($test3 === $test) {
3616 13
                if ($str_chars === []) {
3617 7
                    $str_chars = self::count_chars($str, true, false);
3618
                }
3619 13
                foreach (self::count_chars($test3) as $test3char => &$test3charEmpty) {
0 ignored issues
show
Bug introduced by
The expression self::count_chars($test3) cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
3620 13
                    if (\in_array($test3char, $str_chars, true) === true) {
3621 13
                        ++$maybe_utf32be;
3622
                    }
3623
                }
3624 13
                unset($test3charEmpty);
3625
            }
3626
        }
3627
3628 20
        if ($maybe_utf32be !== $maybe_utf32le) {
3629 3
            if ($maybe_utf32le > $maybe_utf32be) {
3630 2
                return 1;
3631
            }
3632
3633 3
            return 2;
3634
        }
3635
3636 20
        return false;
3637
    }
3638
3639
    /**
3640
     * Checks whether the passed input contains only byte sequences that appear valid UTF-8.
3641
     *
3642
     * @param int|string|string[]|null $str    <p>The input to be checked.</p>
3643
     * @param bool                     $strict <p>Check also if the string is not UTF-16 or UTF-32.</p>
3644
     *
3645
     * @return bool
3646
     */
3647 82
    public static function is_utf8($str, bool $strict = false): bool
3648
    {
3649 82
        if (\is_array($str) === true) {
3650 2
            foreach ($str as &$v) {
3651 2
                if (self::is_utf8($v, $strict) === false) {
3652 2
                    return false;
3653
                }
3654
            }
3655
3656
            return true;
3657
        }
3658
3659 82
        return self::is_utf8_string((string) $str, $strict);
3660
    }
3661
3662
    /**
3663
     * (PHP 5 &gt;= 5.2.0, PECL json &gt;= 1.2.0)<br/>
3664
     * Decodes a JSON string
3665
     *
3666
     * @see http://php.net/manual/en/function.json-decode.php
3667
     *
3668
     * @param string $json    <p>
3669
     *                        The <i>json</i> string being decoded.
3670
     *                        </p>
3671
     *                        <p>
3672
     *                        This function only works with UTF-8 encoded strings.
3673
     *                        </p>
3674
     *                        <p>PHP implements a superset of
3675
     *                        JSON - it will also encode and decode scalar types and <b>NULL</b>. The JSON standard
3676
     *                        only supports these values when they are nested inside an array or an object.
3677
     *                        </p>
3678
     * @param bool   $assoc   [optional] <p>
3679
     *                        When <b>TRUE</b>, returned objects will be converted into
3680
     *                        associative arrays.
3681
     *                        </p>
3682
     * @param int    $depth   [optional] <p>
3683
     *                        User specified recursion depth.
3684
     *                        </p>
3685
     * @param int    $options [optional] <p>
3686
     *                        Bitmask of JSON decode options. Currently only
3687
     *                        <b>JSON_BIGINT_AS_STRING</b>
3688
     *                        is supported (default is to cast large integers as floats)
3689
     *                        </p>
3690
     *
3691
     * @return mixed
3692
     *               The value encoded in <i>json</i> in appropriate PHP type. Values true, false and
3693
     *               null (case-insensitive) are returned as <b>TRUE</b>, <b>FALSE</b> and <b>NULL</b> respectively.
3694
     *               <b>NULL</b> is returned if the <i>json</i> cannot be decoded or if the encoded data
3695
     *               is deeper than the recursion limit.
3696
     */
3697 43
    public static function json_decode(
3698
        string $json,
3699
        bool $assoc = false,
3700
        int $depth = 512,
3701
        int $options = 0
3702
    ) {
3703 43
        $json = self::filter($json);
3704
3705 43
        if (self::$SUPPORT['json'] === false) {
3706
            throw new \RuntimeException('ext-json: is not installed');
3707
        }
3708
3709
        /** @noinspection PhpComposerExtensionStubsInspection */
3710 43
        return \json_decode($json, $assoc, $depth, $options);
3711
    }
3712
3713
    /**
3714
     * (PHP 5 &gt;= 5.2.0, PECL json &gt;= 1.2.0)<br/>
3715
     * Returns the JSON representation of a value.
3716
     *
3717
     * @see http://php.net/manual/en/function.json-encode.php
3718
     *
3719
     * @param mixed $value   <p>
3720
     *                       The <i>value</i> being encoded. Can be any type except
3721
     *                       a resource.
3722
     *                       </p>
3723
     *                       <p>
3724
     *                       All string data must be UTF-8 encoded.
3725
     *                       </p>
3726
     *                       <p>PHP implements a superset of
3727
     *                       JSON - it will also encode and decode scalar types and <b>NULL</b>. The JSON standard
3728
     *                       only supports these values when they are nested inside an array or an object.
3729
     *                       </p>
3730
     * @param int   $options [optional] <p>
3731
     *                       Bitmask consisting of <b>JSON_HEX_QUOT</b>,
3732
     *                       <b>JSON_HEX_TAG</b>,
3733
     *                       <b>JSON_HEX_AMP</b>,
3734
     *                       <b>JSON_HEX_APOS</b>,
3735
     *                       <b>JSON_NUMERIC_CHECK</b>,
3736
     *                       <b>JSON_PRETTY_PRINT</b>,
3737
     *                       <b>JSON_UNESCAPED_SLASHES</b>,
3738
     *                       <b>JSON_FORCE_OBJECT</b>,
3739
     *                       <b>JSON_UNESCAPED_UNICODE</b>. The behaviour of these
3740
     *                       constants is described on
3741
     *                       the JSON constants page.
3742
     *                       </p>
3743
     * @param int   $depth   [optional] <p>
3744
     *                       Set the maximum depth. Must be greater than zero.
3745
     *                       </p>
3746
     *
3747
     * @return false|string
3748
     *                      A JSON encoded <strong>string</strong> on success or<br>
3749
     *                      <strong>FALSE</strong> on failure
3750
     */
3751 5
    public static function json_encode($value, int $options = 0, int $depth = 512)
3752
    {
3753 5
        $value = self::filter($value);
3754
3755 5
        if (self::$SUPPORT['json'] === false) {
3756
            throw new \RuntimeException('ext-json: is not installed');
3757
        }
3758
3759
        /** @noinspection PhpComposerExtensionStubsInspection */
3760 5
        return \json_encode($value, $options, $depth);
3761
    }
3762
3763
    /**
3764
     * Checks whether JSON is available on the server.
3765
     *
3766
     * @return bool
3767
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
3768
     */
3769
    public static function json_loaded(): bool
3770
    {
3771
        return \function_exists('json_decode');
3772
    }
3773
3774
    /**
3775
     * Makes string's first char lowercase.
3776
     *
3777
     * @param string      $str                           <p>The input string</p>
3778
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
3779
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
3780
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
3781
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
3782
     *
3783
     * @return string the resulting string
3784
     */
3785 46
    public static function lcfirst(
3786
        string $str,
3787
        string $encoding = 'UTF-8',
3788
        bool $clean_utf8 = false,
3789
        string $lang = null,
3790
        bool $try_to_keep_the_string_length = false
3791
    ): string {
3792 46
        if ($clean_utf8 === true) {
3793
            $str = self::clean($str);
3794
        }
3795
3796 46
        $use_mb_functions = ($lang === null && $try_to_keep_the_string_length === false);
3797
3798 46
        if ($encoding === 'UTF-8') {
3799 43
            $str_part_two = (string) \mb_substr($str, 1);
3800
3801 43
            if ($use_mb_functions === true) {
3802 43
                $str_part_one = \mb_strtolower(
3803 43
                    (string) \mb_substr($str, 0, 1)
3804
                );
3805
            } else {
3806
                $str_part_one = self::strtolower(
3807
                    (string) \mb_substr($str, 0, 1),
3808
                    $encoding,
3809
                    false,
3810
                    $lang,
3811 43
                    $try_to_keep_the_string_length
3812
                );
3813
            }
3814
        } else {
3815 3
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
3816
3817 3
            $str_part_two = (string) self::substr($str, 1, null, $encoding);
3818
3819 3
            $str_part_one = self::strtolower(
3820 3
                (string) self::substr($str, 0, 1, $encoding),
3821 3
                $encoding,
3822 3
                false,
3823 3
                $lang,
3824 3
                $try_to_keep_the_string_length
3825
            );
3826
        }
3827
3828 46
        return $str_part_one . $str_part_two;
3829
    }
3830
3831
    /**
3832
     * alias for "UTF8::lcfirst()"
3833
     *
3834
     * @param string      $str
3835
     * @param string      $encoding
3836
     * @param bool        $clean_utf8
3837
     * @param string|null $lang
3838
     * @param bool        $try_to_keep_the_string_length
3839
     *
3840
     * @return string
3841
     *
3842
     * @see UTF8::lcfirst()
3843
     * @deprecated <p>please use "UTF8::lcfirst()"</p>
3844
     */
3845 2
    public static function lcword(
3846
        string $str,
3847
        string $encoding = 'UTF-8',
3848
        bool $clean_utf8 = false,
3849
        string $lang = null,
3850
        bool $try_to_keep_the_string_length = false
3851
    ): string {
3852 2
        return self::lcfirst(
3853 2
            $str,
3854 2
            $encoding,
3855 2
            $clean_utf8,
3856 2
            $lang,
3857 2
            $try_to_keep_the_string_length
3858
        );
3859
    }
3860
3861
    /**
3862
     * Lowercase for all words in the string.
3863
     *
3864
     * @param string      $str                           <p>The input string.</p>
3865
     * @param string[]    $exceptions                    [optional] <p>Exclusion for some words.</p>
3866
     * @param string      $char_list                     [optional] <p>Additional chars that contains to words and do not start
3867
     *                                                   a new word.</p>
3868
     * @param string      $encoding                      [optional] <p>Set the charset.</p>
3869
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
3870
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
3871
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
3872
     *
3873
     * @return string
3874
     */
3875 2
    public static function lcwords(
3876
        string $str,
3877
        array $exceptions = [],
3878
        string $char_list = '',
3879
        string $encoding = 'UTF-8',
3880
        bool $clean_utf8 = false,
3881
        string $lang = null,
3882
        bool $try_to_keep_the_string_length = false
3883
    ): string {
3884 2
        if (!$str) {
3885 2
            return '';
3886
        }
3887
3888 2
        $words = self::str_to_words($str, $char_list);
3889 2
        $use_exceptions = $exceptions !== [];
3890
3891 2
        $words_str = '';
3892 2
        foreach ($words as &$word) {
3893 2
            if (!$word) {
3894 2
                continue;
3895
            }
3896
3897
            if (
3898 2
                $use_exceptions === false
3899
                ||
3900 2
                !\in_array($word, $exceptions, true)
3901
            ) {
3902 2
                $words_str .= self::lcfirst($word, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
3903
            } else {
3904 2
                $words_str .= $word;
3905
            }
3906
        }
3907
3908 2
        return $words_str;
3909
    }
3910
3911
    /**
3912
     * alias for "UTF8::lcfirst()"
3913
     *
3914
     * @param string      $str
3915
     * @param string      $encoding
3916
     * @param bool        $clean_utf8
3917
     * @param string|null $lang
3918
     * @param bool        $try_to_keep_the_string_length
3919
     *
3920
     * @return string
3921
     *
3922
     * @see UTF8::lcfirst()
3923
     * @deprecated <p>please use "UTF8::lcfirst()"</p>
3924
     */
3925 5
    public static function lowerCaseFirst(
3926
        string $str,
3927
        string $encoding = 'UTF-8',
3928
        bool $clean_utf8 = false,
3929
        string $lang = null,
3930
        bool $try_to_keep_the_string_length = false
3931
    ): string {
3932 5
        return self::lcfirst(
3933 5
            $str,
3934 5
            $encoding,
3935 5
            $clean_utf8,
3936 5
            $lang,
3937 5
            $try_to_keep_the_string_length
3938
        );
3939
    }
3940
3941
    /**
3942
     * Strip whitespace or other characters from the beginning of a UTF-8 string.
3943
     *
3944
     * @param string      $str   <p>The string to be trimmed</p>
3945
     * @param string|null $chars <p>Optional characters to be stripped</p>
3946
     *
3947
     * @return string the string with unwanted characters stripped from the left
3948
     */
3949 22
    public static function ltrim(string $str = '', string $chars = null): string
3950
    {
3951 22
        if ($str === '') {
3952 3
            return '';
3953
        }
3954
3955 21
        if (self::$SUPPORT['mbstring'] === true) {
3956 21
            if ($chars) {
3957
                /** @noinspection PregQuoteUsageInspection */
3958 10
                $chars = \preg_quote($chars);
3959 10
                $pattern = "^[${chars}]+";
3960
            } else {
3961 14
                $pattern = '^[\\s]+';
3962
            }
3963
3964
            /** @noinspection PhpComposerExtensionStubsInspection */
3965 21
            return (string) \mb_ereg_replace($pattern, '', $str);
3966
        }
3967
3968
        if ($chars) {
3969
            $chars = \preg_quote($chars, '/');
3970
            $pattern = "^[${chars}]+";
3971
        } else {
3972
            $pattern = '^[\\s]+';
3973
        }
3974
3975
        return self::regex_replace($str, $pattern, '', '', '/');
3976
    }
3977
3978
    /**
3979
     * Returns the UTF-8 character with the maximum code point in the given data.
3980
     *
3981
     * @param array<string>|string $arg <p>A UTF-8 encoded string or an array of such strings.</p>
3982
     *
3983
     * @return string|null the character with the highest code point than others, returns null on failure or empty input
3984
     */
3985 2
    public static function max($arg)
3986
    {
3987 2
        if (\is_array($arg) === true) {
3988 2
            $arg = \implode('', $arg);
3989
        }
3990
3991 2
        $codepoints = self::codepoints($arg, false);
3992 2
        if ($codepoints === []) {
3993 2
            return null;
3994
        }
3995
3996 2
        $codepoint_max = \max($codepoints);
3997
3998 2
        return self::chr($codepoint_max);
3999
    }
4000
4001
    /**
4002
     * Calculates and returns the maximum number of bytes taken by any
4003
     * UTF-8 encoded character in the given string.
4004
     *
4005
     * @param string $str <p>The original Unicode string.</p>
4006
     *
4007
     * @return int
4008
     *             <p>Max byte lengths of the given chars.</p>
4009
     */
4010 2
    public static function max_chr_width(string $str): int
4011
    {
4012 2
        $bytes = self::chr_size_list($str);
4013 2
        if ($bytes !== []) {
4014 2
            return (int) \max($bytes);
4015
        }
4016
4017 2
        return 0;
4018
    }
4019
4020
    /**
4021
     * Checks whether mbstring is available on the server.
4022
     *
4023
     * @return bool
4024
     *              <strong>true</strong> if available, <strong>false</strong> otherwise
4025
     */
4026 26
    public static function mbstring_loaded(): bool
4027
    {
4028 26
        return \extension_loaded('mbstring');
4029
    }
4030
4031
    /**
4032
     * Returns the UTF-8 character with the minimum code point in the given data.
4033
     *
4034
     * @param mixed $arg <strong>A UTF-8 encoded string or an array of such strings.</strong>
4035
     *
4036
     * @return string|null the character with the lowest code point than others, returns null on failure or empty input
4037
     */
4038 2
    public static function min($arg)
4039
    {
4040 2
        if (\is_array($arg) === true) {
4041 2
            $arg = \implode('', $arg);
4042
        }
4043
4044 2
        $codepoints = self::codepoints($arg, false);
4045 2
        if ($codepoints === []) {
4046 2
            return null;
4047
        }
4048
4049 2
        $codepoint_min = \min($codepoints);
4050
4051 2
        return self::chr($codepoint_min);
4052
    }
4053
4054
    /**
4055
     * alias for "UTF8::normalize_encoding()"
4056
     *
4057
     * @param mixed $encoding
4058
     * @param mixed $fallback
4059
     *
4060
     * @return mixed
4061
     *
4062
     * @see UTF8::normalize_encoding()
4063
     * @deprecated <p>please use "UTF8::normalize_encoding()"</p>
4064
     */
4065 2
    public static function normalizeEncoding($encoding, $fallback = '')
4066
    {
4067 2
        return self::normalize_encoding($encoding, $fallback);
4068
    }
4069
4070
    /**
4071
     * Normalize the encoding-"name" input.
4072
     *
4073
     * @param mixed $encoding <p>e.g.: ISO, UTF8, WINDOWS-1251 etc.</p>
4074
     * @param mixed $fallback <p>e.g.: UTF-8</p>
4075
     *
4076
     * @return mixed e.g.: ISO-8859-1, UTF-8, WINDOWS-1251 etc.<br>Will return a empty string as fallback (by default)
4077
     */
4078 331
    public static function normalize_encoding($encoding, $fallback = '')
4079
    {
4080 331
        static $STATIC_NORMALIZE_ENCODING_CACHE = [];
4081
4082
        // init
4083 331
        $encoding = (string) $encoding;
4084
4085 331
        if (!$encoding) {
4086 285
            return $fallback;
4087
        }
4088
4089
        if (
4090 51
            $encoding === 'UTF-8'
4091
            ||
4092 51
            $encoding === 'UTF8'
4093
        ) {
4094 28
            return 'UTF-8';
4095
        }
4096
4097
        if (
4098 43
            $encoding === '8BIT'
4099
            ||
4100 43
            $encoding === 'BINARY'
4101
        ) {
4102
            return 'CP850';
4103
        }
4104
4105
        if (
4106 43
            $encoding === 'HTML'
4107
            ||
4108 43
            $encoding === 'HTML-ENTITIES'
4109
        ) {
4110 2
            return 'HTML-ENTITIES';
4111
        }
4112
4113
        if (
4114 43
            $encoding === 'ISO'
4115
            ||
4116 43
            $encoding === 'ISO-8859-1'
4117
        ) {
4118 39
            return 'ISO-8859-1';
4119
        }
4120
4121
        if (
4122 12
            $encoding === '1' // only a fallback, for non "strict_types" usage ...
4123
            ||
4124 12
            $encoding === '0' // only a fallback, for non "strict_types" usage ...
4125
        ) {
4126 1
            return $fallback;
4127
        }
4128
4129 11
        if (isset($STATIC_NORMALIZE_ENCODING_CACHE[$encoding])) {
4130 8
            return $STATIC_NORMALIZE_ENCODING_CACHE[$encoding];
4131
        }
4132
4133 5
        if (self::$ENCODINGS === null) {
4134 1
            self::$ENCODINGS = self::getData('encodings');
4135
        }
4136
4137 5
        if (\in_array($encoding, self::$ENCODINGS, true)) {
4138 3
            $STATIC_NORMALIZE_ENCODING_CACHE[$encoding] = $encoding;
4139
4140 3
            return $encoding;
4141
        }
4142
4143 4
        $encoding_original = $encoding;
4144 4
        $encoding = \strtoupper($encoding);
4145 4
        $encoding_upper_helper = (string) \preg_replace('/[^a-zA-Z0-9]/u', '', $encoding);
4146
4147
        $equivalences = [
4148 4
            'ISO8859'     => 'ISO-8859-1',
4149
            'ISO88591'    => 'ISO-8859-1',
4150
            'ISO'         => 'ISO-8859-1',
4151
            'LATIN'       => 'ISO-8859-1',
4152
            'LATIN1'      => 'ISO-8859-1', // Western European
4153
            'ISO88592'    => 'ISO-8859-2',
4154
            'LATIN2'      => 'ISO-8859-2', // Central European
4155
            'ISO88593'    => 'ISO-8859-3',
4156
            'LATIN3'      => 'ISO-8859-3', // Southern European
4157
            'ISO88594'    => 'ISO-8859-4',
4158
            'LATIN4'      => 'ISO-8859-4', // Northern European
4159
            'ISO88595'    => 'ISO-8859-5',
4160
            'ISO88596'    => 'ISO-8859-6', // Greek
4161
            'ISO88597'    => 'ISO-8859-7',
4162
            'ISO88598'    => 'ISO-8859-8', // Hebrew
4163
            'ISO88599'    => 'ISO-8859-9',
4164
            'LATIN5'      => 'ISO-8859-9', // Turkish
4165
            'ISO885911'   => 'ISO-8859-11',
4166
            'TIS620'      => 'ISO-8859-11', // Thai
4167
            'ISO885910'   => 'ISO-8859-10',
4168
            'LATIN6'      => 'ISO-8859-10', // Nordic
4169
            'ISO885913'   => 'ISO-8859-13',
4170
            'LATIN7'      => 'ISO-8859-13', // Baltic
4171
            'ISO885914'   => 'ISO-8859-14',
4172
            'LATIN8'      => 'ISO-8859-14', // Celtic
4173
            'ISO885915'   => 'ISO-8859-15',
4174
            'LATIN9'      => 'ISO-8859-15', // Western European (with some extra chars e.g. €)
4175
            'ISO885916'   => 'ISO-8859-16',
4176
            'LATIN10'     => 'ISO-8859-16', // Southeast European
4177
            'CP1250'      => 'WINDOWS-1250',
4178
            'WIN1250'     => 'WINDOWS-1250',
4179
            'WINDOWS1250' => 'WINDOWS-1250',
4180
            'CP1251'      => 'WINDOWS-1251',
4181
            'WIN1251'     => 'WINDOWS-1251',
4182
            'WINDOWS1251' => 'WINDOWS-1251',
4183
            'CP1252'      => 'WINDOWS-1252',
4184
            'WIN1252'     => 'WINDOWS-1252',
4185
            'WINDOWS1252' => 'WINDOWS-1252',
4186
            'CP1253'      => 'WINDOWS-1253',
4187
            'WIN1253'     => 'WINDOWS-1253',
4188
            'WINDOWS1253' => 'WINDOWS-1253',
4189
            'CP1254'      => 'WINDOWS-1254',
4190
            'WIN1254'     => 'WINDOWS-1254',
4191
            'WINDOWS1254' => 'WINDOWS-1254',
4192
            'CP1255'      => 'WINDOWS-1255',
4193
            'WIN1255'     => 'WINDOWS-1255',
4194
            'WINDOWS1255' => 'WINDOWS-1255',
4195
            'CP1256'      => 'WINDOWS-1256',
4196
            'WIN1256'     => 'WINDOWS-1256',
4197
            'WINDOWS1256' => 'WINDOWS-1256',
4198
            'CP1257'      => 'WINDOWS-1257',
4199
            'WIN1257'     => 'WINDOWS-1257',
4200
            'WINDOWS1257' => 'WINDOWS-1257',
4201
            'CP1258'      => 'WINDOWS-1258',
4202
            'WIN1258'     => 'WINDOWS-1258',
4203
            'WINDOWS1258' => 'WINDOWS-1258',
4204
            'UTF16'       => 'UTF-16',
4205
            'UTF32'       => 'UTF-32',
4206
            'UTF8'        => 'UTF-8',
4207
            'UTF'         => 'UTF-8',
4208
            'UTF7'        => 'UTF-7',
4209
            '8BIT'        => 'CP850',
4210
            'BINARY'      => 'CP850',
4211
        ];
4212
4213 4
        if (!empty($equivalences[$encoding_upper_helper])) {
4214 3
            $encoding = $equivalences[$encoding_upper_helper];
4215
        }
4216
4217 4
        $STATIC_NORMALIZE_ENCODING_CACHE[$encoding_original] = $encoding;
4218
4219 4
        return $encoding;
4220
    }
4221
4222
    /**
4223
     * Standardize line ending to unix-like.
4224
     *
4225
     * @param string $str      <p>The input string.</p>
4226
     * @param string $replacer <p>The replacer char e.g. "\n" (Linux) or "\r\n" (Windows). You can also use \PHP_EOL here.</p>
4227
     *
4228
     * @return string
4229
     *                <p>A string with normalized line ending.</p>
4230
     */
4231 5
    public static function normalize_line_ending(string $str, $replacer = "\n"): string
4232
    {
4233 5
        return \str_replace(["\r\n", "\r", "\n"], $replacer, $str);
4234
    }
4235
4236
    /**
4237
     * Normalize some MS Word special characters.
4238
     *
4239
     * @param string $str <p>The string to be normalized.</p>
4240
     *
4241
     * @return string
4242
     *                <p>A string with normalized characters for commonly used chars in Word documents.</p>
4243
     */
4244 10
    public static function normalize_msword(string $str): string
4245
    {
4246 10
        return ASCII::normalize_msword($str);
4247
    }
4248
4249
    /**
4250
     * Normalize the whitespace.
4251
     *
4252
     * @param string $str                        <p>The string to be normalized.</p>
4253
     * @param bool   $keep_non_breaking_space    [optional] <p>Set to true, to keep non-breaking-spaces.</p>
4254
     * @param bool   $keep_bidi_unicode_controls [optional] <p>Set to true, to keep non-printable (for the web)
4255
     *                                           bidirectional text chars.</p>
4256
     *
4257
     * @return string
4258
     *                <p>A string with normalized whitespace.</p>
4259
     */
4260 61
    public static function normalize_whitespace(
4261
        string $str,
4262
        bool $keep_non_breaking_space = false,
4263
        bool $keep_bidi_unicode_controls = false
4264
    ): string {
4265 61
        return ASCII::normalize_whitespace(
4266 61
            $str,
4267 61
            $keep_non_breaking_space,
4268 61
            $keep_bidi_unicode_controls
4269
        );
4270
    }
4271
4272
    /**
4273
     * Calculates Unicode code point of the given UTF-8 encoded character.
4274
     *
4275
     * INFO: opposite to UTF8::chr()
4276
     *
4277
     * @param string $chr      <p>The character of which to calculate code point.<p/>
4278
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
4279
     *
4280
     * @return int
4281
     *             <p>Unicode code point of the given character,<br>
4282
     *             0 on invalid UTF-8 byte sequence</p>
4283
     */
4284 26
    public static function ord($chr, string $encoding = 'UTF-8'): int
4285
    {
4286 26
        static $CHAR_CACHE = [];
4287
4288
        // init
4289 26
        $chr = (string) $chr;
4290
4291 26
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
4292 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
4293
        }
4294
4295 26
        $cache_key = $chr . $encoding;
4296 26
        if (isset($CHAR_CACHE[$cache_key]) === true) {
4297 26
            return $CHAR_CACHE[$cache_key];
4298
        }
4299
4300
        // check again, if it's still not UTF-8
4301 10
        if ($encoding !== 'UTF-8') {
4302 3
            $chr = self::encode($encoding, $chr);
4303
        }
4304
4305 10
        if (self::$ORD === null) {
4306
            self::$ORD = self::getData('ord');
4307
        }
4308
4309 10
        if (isset(self::$ORD[$chr])) {
4310 10
            return $CHAR_CACHE[$cache_key] = self::$ORD[$chr];
4311
        }
4312
4313
        //
4314
        // fallback via "IntlChar"
4315
        //
4316
4317 6
        if (self::$SUPPORT['intlChar'] === true) {
4318
            /** @noinspection PhpComposerExtensionStubsInspection */
4319 5
            $code = \IntlChar::ord($chr);
4320 5
            if ($code) {
4321 5
                return $CHAR_CACHE[$cache_key] = $code;
4322
            }
4323
        }
4324
4325
        //
4326
        // fallback via vanilla php
4327
        //
4328
4329
        /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
4330 1
        $chr = \unpack('C*', (string) \substr($chr, 0, 4));
4331
        /** @noinspection OffsetOperationsInspection */
4332 1
        $code = $chr ? $chr[1] : 0;
4333
4334
        /** @noinspection OffsetOperationsInspection */
4335 1
        if ($code >= 0xF0 && isset($chr[4])) {
4336
            /** @noinspection UnnecessaryCastingInspection */
4337
            /** @noinspection OffsetOperationsInspection */
4338
            return $CHAR_CACHE[$cache_key] = (int) ((($code - 0xF0) << 18) + (($chr[2] - 0x80) << 12) + (($chr[3] - 0x80) << 6) + $chr[4] - 0x80);
4339
        }
4340
4341
        /** @noinspection OffsetOperationsInspection */
4342 1
        if ($code >= 0xE0 && isset($chr[3])) {
4343
            /** @noinspection UnnecessaryCastingInspection */
4344
            /** @noinspection OffsetOperationsInspection */
4345 1
            return $CHAR_CACHE[$cache_key] = (int) ((($code - 0xE0) << 12) + (($chr[2] - 0x80) << 6) + $chr[3] - 0x80);
4346
        }
4347
4348
        /** @noinspection OffsetOperationsInspection */
4349 1
        if ($code >= 0xC0 && isset($chr[2])) {
4350
            /** @noinspection UnnecessaryCastingInspection */
4351
            /** @noinspection OffsetOperationsInspection */
4352 1
            return $CHAR_CACHE[$cache_key] = (int) ((($code - 0xC0) << 6) + $chr[2] - 0x80);
4353
        }
4354
4355
        return $CHAR_CACHE[$cache_key] = $code;
4356
    }
4357
4358
    /**
4359
     * Parses the string into an array (into the the second parameter).
4360
     *
4361
     * WARNING: Unlike "parse_str()", this method does not (re-)place variables in the current scope,
4362
     *          if the second parameter is not set!
4363
     *
4364
     * @see http://php.net/manual/en/function.parse-str.php
4365
     *
4366
     * @param string $str        <p>The input string.</p>
4367
     * @param array  $result     <p>The result will be returned into this reference parameter.</p>
4368
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
4369
     *
4370
     * @return bool
4371
     *              <p>Will return <strong>false</strong> if php can't parse the string and we haven't any $result.</p>
4372
     */
4373 2
    public static function parse_str(string $str, &$result, bool $clean_utf8 = false): bool
4374
    {
4375 2
        if ($clean_utf8 === true) {
4376 2
            $str = self::clean($str);
4377
        }
4378
4379 2
        if (self::$SUPPORT['mbstring'] === true) {
4380 2
            $return = \mb_parse_str($str, $result);
4381
4382 2
            return $return !== false && $result !== [];
4383
        }
4384
4385
        /** @noinspection PhpVoidFunctionResultUsedInspection */
4386
        \parse_str($str, $result);
4387
4388
        return $result !== [];
4389
    }
4390
4391
    /**
4392
     * Checks if \u modifier is available that enables Unicode support in PCRE.
4393
     *
4394
     * @return bool
4395
     *              <p>
4396
     *              <strong>true</strong> if support is available,<br>
4397
     *              <strong>false</strong> otherwise
4398
     *              </p>
4399
     */
4400 102
    public static function pcre_utf8_support(): bool
4401
    {
4402
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
4403 102
        return (bool) @\preg_match('//u', '');
4404
    }
4405
4406
    /**
4407
     * Create an array containing a range of UTF-8 characters.
4408
     *
4409
     * @param mixed     $var1      <p>Numeric or hexadecimal code points, or a UTF-8 character to start from.</p>
4410
     * @param mixed     $var2      <p>Numeric or hexadecimal code points, or a UTF-8 character to end at.</p>
4411
     * @param bool      $use_ctype <p>use ctype to detect numeric and hexadecimal, otherwise we will use a simple "is_numeric"</p>
4412
     * @param string    $encoding  [optional] <p>Set the charset for e.g. "mb_" function</p>
4413
     * @param float|int $step      [optional] <p>
4414
     *                             If a step value is given, it will be used as the
4415
     *                             increment between elements in the sequence. step
4416
     *                             should be given as a positive number. If not specified,
4417
     *                             step will default to 1.
4418
     *                             </p>
4419
     *
4420
     * @return string[]
4421
     */
4422 2
    public static function range(
4423
        $var1,
4424
        $var2,
4425
        bool $use_ctype = true,
4426
        string $encoding = 'UTF-8',
4427
        $step = 1
4428
    ): array {
4429 2
        if (!$var1 || !$var2) {
4430 2
            return [];
4431
        }
4432
4433 2
        if ($step !== 1) {
4434
            /**
4435
             * @psalm-suppress RedundantConditionGivenDocblockType
4436
             */
4437 1
            if (!\is_numeric($step)) {
0 ignored issues
show
introduced by
The condition is_numeric($step) is always true.
Loading history...
4438
                throw new \InvalidArgumentException('$step need to be a number, type given: ' . \gettype($step));
4439
            }
4440
4441
            /**
4442
             * @psalm-suppress RedundantConditionGivenDocblockType - false-positive from psalm?
4443
             */
4444 1
            if ($step <= 0) {
4445
                throw new \InvalidArgumentException('$step need to be a positive number, given: ' . $step);
4446
            }
4447
        }
4448
4449 2
        if ($use_ctype && self::$SUPPORT['ctype'] === false) {
4450
            throw new \RuntimeException('ext-ctype: is not installed');
4451
        }
4452
4453 2
        $is_digit = false;
4454 2
        $is_xdigit = false;
4455
4456
        /** @noinspection PhpComposerExtensionStubsInspection */
4457 2
        if ($use_ctype && \ctype_digit((string) $var1) && \ctype_digit((string) $var2)) {
4458 2
            $is_digit = true;
4459 2
            $start = (int) $var1;
4460 2
        } /** @noinspection PhpComposerExtensionStubsInspection */ elseif ($use_ctype && \ctype_xdigit($var1) && \ctype_xdigit($var2)) {
4461
            $is_xdigit = true;
4462
            $start = (int) self::hex_to_int($var1);
4463 2
        } elseif (!$use_ctype && \is_numeric($var1)) {
4464 1
            $start = (int) $var1;
4465
        } else {
4466 2
            $start = self::ord($var1);
4467
        }
4468
4469 2
        if (!$start) {
4470
            return [];
4471
        }
4472
4473 2
        if ($is_digit) {
4474 2
            $end = (int) $var2;
4475 2
        } elseif ($is_xdigit) {
4476
            $end = (int) self::hex_to_int($var2);
4477 2
        } elseif (!$use_ctype && \is_numeric($var2)) {
4478 1
            $end = (int) $var2;
4479
        } else {
4480 2
            $end = self::ord($var2);
4481
        }
4482
4483 2
        if (!$end) {
4484
            return [];
4485
        }
4486
4487 2
        $array = [];
4488 2
        foreach (\range($start, $end, $step) as $i) {
4489 2
            $array[] = (string) self::chr((int) $i, $encoding);
4490
        }
4491
4492 2
        return $array;
4493
    }
4494
4495
    /**
4496
     * Multi decode HTML entity + fix urlencoded-win1252-chars.
4497
     *
4498
     * e.g:
4499
     * 'test+test'                     => 'test+test'
4500
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4501
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4502
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4503
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4504
     * 'Düsseldorf'                   => 'Düsseldorf'
4505
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4506
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4507
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4508
     *
4509
     * @param string $str          <p>The input string.</p>
4510
     * @param bool   $multi_decode <p>Decode as often as possible.</p>
4511
     *
4512
     * @return string
4513
     *                <p>The decoded URL, as a string.</p>
4514
     */
4515 7
    public static function rawurldecode(string $str, bool $multi_decode = true): string
4516
    {
4517 7
        if ($str === '') {
4518 4
            return '';
4519
        }
4520
4521
        if (
4522 7
            \strpos($str, '&') === false
4523
            &&
4524 7
            \strpos($str, '%') === false
4525
            &&
4526 7
            \strpos($str, '+') === false
4527
            &&
4528 7
            \strpos($str, '\u') === false
4529
        ) {
4530 4
            return self::fix_simple_utf8($str);
4531
        }
4532
4533 7
        $str = self::urldecode_unicode_helper($str);
4534
4535 7
        if ($multi_decode) {
4536
            do {
4537 6
                $str_compare = $str;
4538
4539
                /**
4540
                 * @psalm-suppress PossiblyInvalidArgument
4541
                 */
4542 6
                $str = self::fix_simple_utf8(
4543 6
                    \rawurldecode(
4544 6
                        self::html_entity_decode(
4545 6
                            self::to_utf8($str),
4546 6
                            \ENT_QUOTES | \ENT_HTML5
4547
                        )
4548
                    )
4549
                );
4550 6
            } while ($str_compare !== $str);
4551
        } else {
4552
            /**
4553
             * @psalm-suppress PossiblyInvalidArgument
4554
             */
4555 1
            $str = self::fix_simple_utf8(
4556 1
                \rawurldecode(
4557 1
                    self::html_entity_decode(
4558 1
                        self::to_utf8($str),
4559 1
                        \ENT_QUOTES | \ENT_HTML5
4560
                    )
4561
                )
4562
            );
4563
        }
4564
4565 7
        return $str;
4566
    }
4567
4568
    /**
4569
     * Replaces all occurrences of $pattern in $str by $replacement.
4570
     *
4571
     * @param string $str         <p>The input string.</p>
4572
     * @param string $pattern     <p>The regular expression pattern.</p>
4573
     * @param string $replacement <p>The string to replace with.</p>
4574
     * @param string $options     [optional] <p>Matching conditions to be used.</p>
4575
     * @param string $delimiter   [optional] <p>Delimiter the the regex. Default: '/'</p>
4576
     *
4577
     * @return string
4578
     */
4579 18
    public static function regex_replace(
4580
        string $str,
4581
        string $pattern,
4582
        string $replacement,
4583
        string $options = '',
4584
        string $delimiter = '/'
4585
    ): string {
4586 18
        if ($options === 'msr') {
4587 9
            $options = 'ms';
4588
        }
4589
4590
        // fallback
4591 18
        if (!$delimiter) {
4592
            $delimiter = '/';
4593
        }
4594
4595 18
        return (string) \preg_replace(
4596 18
            $delimiter . $pattern . $delimiter . 'u' . $options,
4597 18
            $replacement,
4598 18
            $str
4599
        );
4600
    }
4601
4602
    /**
4603
     * alias for "UTF8::remove_bom()"
4604
     *
4605
     * @param string $str
4606
     *
4607
     * @return string
4608
     *
4609
     * @see UTF8::remove_bom()
4610
     * @deprecated <p>please use "UTF8::remove_bom()"</p>
4611
     */
4612
    public static function removeBOM(string $str): string
4613
    {
4614
        return self::remove_bom($str);
4615
    }
4616
4617
    /**
4618
     * Remove the BOM from UTF-8 / UTF-16 / UTF-32 strings.
4619
     *
4620
     * @param string $str <p>The input string.</p>
4621
     *
4622
     * @return string
4623
     *                <p>A string without UTF-BOM.</p>
4624
     */
4625 55
    public static function remove_bom(string $str): string
4626
    {
4627 55
        if ($str === '') {
4628 9
            return '';
4629
        }
4630
4631 55
        $str_length = \strlen($str);
4632 55
        foreach (self::$BOM as $bom_string => $bom_byte_length) {
4633 55
            if (\strpos($str, $bom_string, 0) === 0) {
4634
                /** @var false|string $str_tmp - needed for PhpStan (stubs error) */
4635 11
                $str_tmp = \substr($str, $bom_byte_length, $str_length);
4636 11
                if ($str_tmp === false) {
4637
                    return '';
4638
                }
4639
4640 11
                $str_length -= (int) $bom_byte_length;
4641
4642 55
                $str = (string) $str_tmp;
4643
            }
4644
        }
4645
4646 55
        return $str;
4647
    }
4648
4649
    /**
4650
     * Removes duplicate occurrences of a string in another string.
4651
     *
4652
     * @param string          $str  <p>The base string.</p>
4653
     * @param string|string[] $what <p>String to search for in the base string.</p>
4654
     *
4655
     * @return string
4656
     *                <p>A string with removed duplicates.</p>
4657
     */
4658 2
    public static function remove_duplicates(string $str, $what = ' '): string
4659
    {
4660 2
        if (\is_string($what) === true) {
4661 2
            $what = [$what];
4662
        }
4663
4664
        /**
4665
         * @psalm-suppress RedundantConditionGivenDocblockType
4666
         */
4667 2
        if (\is_array($what) === true) {
0 ignored issues
show
introduced by
The condition is_array($what) === true is always true.
Loading history...
4668 2
            foreach ($what as $item) {
4669 2
                $str = (string) \preg_replace('/(' . \preg_quote($item, '/') . ')+/u', $item, $str);
4670
            }
4671
        }
4672
4673 2
        return $str;
4674
    }
4675
4676
    /**
4677
     * Remove html via "strip_tags()" from the string.
4678
     *
4679
     * @param string $str            <p>The input string.</p>
4680
     * @param string $allowable_tags [optional] <p>You can use the optional second parameter to specify tags which should
4681
     *                               not be stripped. Default: null
4682
     *                               </p>
4683
     *
4684
     * @return string
4685
     *                <p>A string with without html tags.</p>
4686
     */
4687 6
    public static function remove_html(string $str, string $allowable_tags = ''): string
4688
    {
4689 6
        return \strip_tags($str, $allowable_tags);
4690
    }
4691
4692
    /**
4693
     * Remove all breaks [<br> | \r\n | \r | \n | ...] from the string.
4694
     *
4695
     * @param string $str         <p>The input string.</p>
4696
     * @param string $replacement [optional] <p>Default is a empty string.</p>
4697
     *
4698
     * @return string
4699
     *                <p>A string without breaks.</p>
4700
     */
4701 6
    public static function remove_html_breaks(string $str, string $replacement = ''): string
4702
    {
4703 6
        return (string) \preg_replace("#/\r\n|\r|\n|<br.*/?>#isU", $replacement, $str);
4704
    }
4705
4706
    /**
4707
     * Remove invisible characters from a string.
4708
     *
4709
     * e.g.: This prevents sandwiching null characters between ascii characters, like Java\0script.
4710
     *
4711
     * copy&past from https://github.com/bcit-ci/CodeIgniter/blob/develop/system/core/Common.php
4712
     *
4713
     * @param string $str         <p>The input string.</p>
4714
     * @param bool   $url_encoded [optional] <p>
4715
     *                            Try to remove url encoded control character.
4716
     *                            WARNING: maybe contains false-positives e.g. aa%0Baa -> aaaa.
4717
     *                            <br>
4718
     *                            Default: false
4719
     *                            </p>
4720
     * @param string $replacement [optional] <p>The replacement character.</p>
4721
     *
4722
     * @return string
4723
     *                <p>A string without invisible chars.</p>
4724
     */
4725 89
    public static function remove_invisible_characters(
4726
        string $str,
4727
        bool $url_encoded = false,
4728
        string $replacement = ''
4729
    ): string {
4730 89
        return ASCII::remove_invisible_characters(
4731 89
            $str,
4732 89
            $url_encoded,
4733 89
            $replacement
4734
        );
4735
    }
4736
4737
    /**
4738
     * Returns a new string with the prefix $substring removed, if present.
4739
     *
4740
     * @param string $str       <p>The input string.</p>
4741
     * @param string $substring <p>The prefix to remove.</p>
4742
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
4743
     *
4744
     * @return string
4745
     *                <p>A string without the prefix $substring.</p>
4746
     */
4747 12
    public static function remove_left(
4748
        string $str,
4749
        string $substring,
4750
        string $encoding = 'UTF-8'
4751
    ): string {
4752 12
        if ($substring && \strpos($str, $substring) === 0) {
4753 6
            if ($encoding === 'UTF-8') {
4754 4
                return (string) \mb_substr(
4755 4
                    $str,
4756 4
                    (int) \mb_strlen($substring)
4757
                );
4758
            }
4759
4760 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
4761
4762 2
            return (string) self::substr(
4763 2
                $str,
4764 2
                (int) self::strlen($substring, $encoding),
4765 2
                null,
4766 2
                $encoding
4767
            );
4768
        }
4769
4770 6
        return $str;
4771
    }
4772
4773
    /**
4774
     * Returns a new string with the suffix $substring removed, if present.
4775
     *
4776
     * @param string $str
4777
     * @param string $substring <p>The suffix to remove.</p>
4778
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
4779
     *
4780
     * @return string
4781
     *                <p>A string having a $str without the suffix $substring.</p>
4782
     */
4783 12
    public static function remove_right(
4784
        string $str,
4785
        string $substring,
4786
        string $encoding = 'UTF-8'
4787
    ): string {
4788 12
        if ($substring && \substr($str, -\strlen($substring)) === $substring) {
4789 6
            if ($encoding === 'UTF-8') {
4790 4
                return (string) \mb_substr(
4791 4
                    $str,
4792 4
                    0,
4793 4
                    (int) \mb_strlen($str) - (int) \mb_strlen($substring)
4794
                );
4795
            }
4796
4797 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
4798
4799 2
            return (string) self::substr(
4800 2
                $str,
4801 2
                0,
4802 2
                (int) self::strlen($str, $encoding) - (int) self::strlen($substring, $encoding),
4803 2
                $encoding
4804
            );
4805
        }
4806
4807 6
        return $str;
4808
    }
4809
4810
    /**
4811
     * Replaces all occurrences of $search in $str by $replacement.
4812
     *
4813
     * @param string $str            <p>The input string.</p>
4814
     * @param string $search         <p>The needle to search for.</p>
4815
     * @param string $replacement    <p>The string to replace with.</p>
4816
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
4817
     *
4818
     * @return string
4819
     *                <p>A string with replaced parts.</p>
4820
     */
4821 29
    public static function replace(
4822
        string $str,
4823
        string $search,
4824
        string $replacement,
4825
        bool $case_sensitive = true
4826
    ): string {
4827 29
        if ($case_sensitive) {
4828 22
            return \str_replace($search, $replacement, $str);
4829
        }
4830
4831 7
        return self::str_ireplace($search, $replacement, $str);
4832
    }
4833
4834
    /**
4835
     * Replaces all occurrences of $search in $str by $replacement.
4836
     *
4837
     * @param string       $str            <p>The input string.</p>
4838
     * @param array        $search         <p>The elements to search for.</p>
4839
     * @param array|string $replacement    <p>The string to replace with.</p>
4840
     * @param bool         $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
4841
     *
4842
     * @return string
4843
     *                <p>A string with replaced parts.</p>
4844
     */
4845 30
    public static function replace_all(
4846
        string $str,
4847
        array $search,
4848
        $replacement,
4849
        bool $case_sensitive = true
4850
    ): string {
4851 30
        if ($case_sensitive) {
4852 23
            return \str_replace($search, $replacement, $str);
4853
        }
4854
4855 7
        return self::str_ireplace($search, $replacement, $str);
4856
    }
4857
4858
    /**
4859
     * Replace the diamond question mark (�) and invalid-UTF8 chars with the replacement.
4860
     *
4861
     * @param string $str                        <p>The input string</p>
4862
     * @param string $replacement_char           <p>The replacement character.</p>
4863
     * @param bool   $process_invalid_utf8_chars <p>Convert invalid UTF-8 chars </p>
4864
     *
4865
     * @return string
4866
     *                <p>A string without diamond question marks (�).</p>
4867
     */
4868 35
    public static function replace_diamond_question_mark(
4869
        string $str,
4870
        string $replacement_char = '',
4871
        bool $process_invalid_utf8_chars = true
4872
    ): string {
4873 35
        if ($str === '') {
4874 9
            return '';
4875
        }
4876
4877 35
        if ($process_invalid_utf8_chars === true) {
4878 35
            $replacement_char_helper = $replacement_char;
4879 35
            if ($replacement_char === '') {
4880 35
                $replacement_char_helper = 'none';
4881
            }
4882
4883 35
            if (self::$SUPPORT['mbstring'] === false) {
4884
                // if there is no native support for "mbstring",
4885
                // then we need to clean the string before ...
4886
                $str = self::clean($str);
4887
            }
4888
4889 35
            $save = \mb_substitute_character();
4890 35
            \mb_substitute_character($replacement_char_helper);
4891
            // the polyfill maybe return false, so cast to string
4892 35
            $str = (string) \mb_convert_encoding($str, 'UTF-8', 'UTF-8');
4893 35
            \mb_substitute_character($save);
4894
        }
4895
4896 35
        return \str_replace(
4897
            [
4898 35
                "\xEF\xBF\xBD",
4899
                '�',
4900
            ],
4901
            [
4902 35
                $replacement_char,
4903 35
                $replacement_char,
4904
            ],
4905 35
            $str
4906
        );
4907
    }
4908
4909
    /**
4910
     * Strip whitespace or other characters from the end of a UTF-8 string.
4911
     *
4912
     * @param string      $str   <p>The string to be trimmed.</p>
4913
     * @param string|null $chars <p>Optional characters to be stripped.</p>
4914
     *
4915
     * @return string
4916
     *                <p>A string with unwanted characters stripped from the right.</p>
4917
     */
4918 20
    public static function rtrim(string $str = '', string $chars = null): string
4919
    {
4920 20
        if ($str === '') {
4921 3
            return '';
4922
        }
4923
4924 19
        if (self::$SUPPORT['mbstring'] === true) {
4925 19
            if ($chars) {
4926
                /** @noinspection PregQuoteUsageInspection */
4927 8
                $chars = \preg_quote($chars);
4928 8
                $pattern = "[${chars}]+$";
4929
            } else {
4930 14
                $pattern = '[\\s]+$';
4931
            }
4932
4933
            /** @noinspection PhpComposerExtensionStubsInspection */
4934 19
            return (string) \mb_ereg_replace($pattern, '', $str);
4935
        }
4936
4937
        if ($chars) {
4938
            $chars = \preg_quote($chars, '/');
4939
            $pattern = "[${chars}]+$";
4940
        } else {
4941
            $pattern = '[\\s]+$';
4942
        }
4943
4944
        return self::regex_replace($str, $pattern, '', '', '/');
4945
    }
4946
4947
    /**
4948
     * WARNING: Print native UTF-8 support (libs), e.g. for debugging.
4949
     *
4950
     * @return void
4951
     */
4952 2
    public static function showSupport()
4953
    {
4954 2
        echo '<pre>';
4955 2
        foreach (self::$SUPPORT as $key => &$value) {
4956 2
            echo $key . ' - ' . \print_r($value, true) . "\n<br>";
4957
        }
4958 2
        unset($value);
4959 2
        echo '</pre>';
4960 2
    }
4961
4962
    /**
4963
     * Converts a UTF-8 character to HTML Numbered Entity like "&#123;".
4964
     *
4965
     * @param string $char             <p>The Unicode character to be encoded as numbered entity.</p>
4966
     * @param bool   $keep_ascii_chars <p>Set to <strong>true</strong> to keep ASCII chars.</>
4967
     * @param string $encoding         [optional] <p>Set the charset for e.g. "mb_" function</p>
4968
     *
4969
     * @return string
4970
     *                <p>The HTML numbered entity for the given character.</p>
4971
     */
4972 2
    public static function single_chr_html_encode(
4973
        string $char,
4974
        bool $keep_ascii_chars = false,
4975
        string $encoding = 'UTF-8'
4976
    ): string {
4977 2
        if ($char === '') {
4978 2
            return '';
4979
        }
4980
4981
        if (
4982 2
            $keep_ascii_chars === true
4983
            &&
4984 2
            ASCII::is_ascii($char) === true
4985
        ) {
4986 2
            return $char;
4987
        }
4988
4989 2
        return '&#' . self::ord($char, $encoding) . ';';
4990
    }
4991
4992
    /**
4993
     * @param string $str
4994
     * @param int    $tab_length
4995
     *
4996
     * @return string
4997
     */
4998 5
    public static function spaces_to_tabs(string $str, int $tab_length = 4): string
4999
    {
5000 5
        if ($tab_length === 4) {
5001 3
            $tab = '    ';
5002 2
        } elseif ($tab_length === 2) {
5003 1
            $tab = '  ';
5004
        } else {
5005 1
            $tab = \str_repeat(' ', $tab_length);
5006
        }
5007
5008 5
        return \str_replace($tab, "\t", $str);
5009
    }
5010
5011
    /**
5012
     * alias for "UTF8::str_split()"
5013
     *
5014
     * @param string|string[] $str
5015
     * @param int             $length
5016
     * @param bool            $clean_utf8
5017
     *
5018
     * @return string[]
5019
     *
5020
     * @see UTF8::str_split()
5021
     * @deprecated <p>please use "UTF8::str_split()"</p>
5022
     */
5023 9
    public static function split(
5024
        $str,
5025
        int $length = 1,
5026
        bool $clean_utf8 = false
5027
    ): array {
5028 9
        return self::str_split($str, $length, $clean_utf8);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::str_split($str, $length, $clean_utf8) returns an array which contains values of type array which are incompatible with the documented value type string.
Loading history...
5029
    }
5030
5031
    /**
5032
     * alias for "UTF8::str_starts_with()"
5033
     *
5034
     * @param string $haystack
5035
     * @param string $needle
5036
     *
5037
     * @return bool
5038
     *
5039
     * @see UTF8::str_starts_with()
5040
     * @deprecated <p>please use "UTF8::str_starts_with()"</p>
5041
     */
5042
    public static function str_begins(string $haystack, string $needle): bool
5043
    {
5044
        return self::str_starts_with($haystack, $needle);
5045
    }
5046
5047
    /**
5048
     * Returns a camelCase version of the string. Trims surrounding spaces,
5049
     * capitalizes letters following digits, spaces, dashes and underscores,
5050
     * and removes spaces, dashes, as well as underscores.
5051
     *
5052
     * @param string      $str                           <p>The input string.</p>
5053
     * @param string      $encoding                      [optional] <p>Default: 'UTF-8'</p>
5054
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
5055
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
5056
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
5057
     *
5058
     * @return string
5059
     */
5060 32
    public static function str_camelize(
5061
        string $str,
5062
        string $encoding = 'UTF-8',
5063
        bool $clean_utf8 = false,
5064
        string $lang = null,
5065
        bool $try_to_keep_the_string_length = false
5066
    ): string {
5067 32
        if ($clean_utf8 === true) {
5068
            $str = self::clean($str);
5069
        }
5070
5071 32
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
5072 26
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
5073
        }
5074
5075 32
        $str = self::lcfirst(
5076 32
            \trim($str),
5077 32
            $encoding,
5078 32
            false,
5079 32
            $lang,
5080 32
            $try_to_keep_the_string_length
5081
        );
5082 32
        $str = (string) \preg_replace('/^[-_]+/', '', $str);
5083
5084 32
        $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
5085
5086 32
        $str = (string) \preg_replace_callback(
5087 32
            '/[-_\\s]+(.)?/u',
5088
            /**
5089
             * @param array $match
5090
             *
5091
             * @return string
5092
             */
5093
            static function (array $match) use ($use_mb_functions, $encoding, $lang, $try_to_keep_the_string_length): string {
5094 27
                if (isset($match[1])) {
5095 27
                    if ($use_mb_functions === true) {
5096 27
                        if ($encoding === 'UTF-8') {
5097 27
                            return \mb_strtoupper($match[1]);
5098
                        }
5099
5100
                        return \mb_strtoupper($match[1], $encoding);
5101
                    }
5102
5103
                    return self::strtoupper($match[1], $encoding, false, $lang, $try_to_keep_the_string_length);
5104
                }
5105
5106 1
                return '';
5107 32
            },
5108 32
            $str
5109
        );
5110
5111 32
        return (string) \preg_replace_callback(
5112 32
            '/[\\p{N}]+(.)?/u',
5113
            /**
5114
             * @param array $match
5115
             *
5116
             * @return string
5117
             */
5118
            static function (array $match) use ($use_mb_functions, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length): string {
5119 6
                if ($use_mb_functions === true) {
5120 6
                    if ($encoding === 'UTF-8') {
5121 6
                        return \mb_strtoupper($match[0]);
5122
                    }
5123
5124
                    return \mb_strtoupper($match[0], $encoding);
5125
                }
5126
5127
                return self::strtoupper($match[0], $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
5128 32
            },
5129 32
            $str
5130
        );
5131
    }
5132
5133
    /**
5134
     * Returns the string with the first letter of each word capitalized,
5135
     * except for when the word is a name which shouldn't be capitalized.
5136
     *
5137
     * @param string $str
5138
     *
5139
     * @return string
5140
     *                <p>A string with $str capitalized.</p>
5141
     */
5142 1
    public static function str_capitalize_name(string $str): string
5143
    {
5144 1
        return self::str_capitalize_name_helper(
5145 1
            self::str_capitalize_name_helper(
5146 1
                self::collapse_whitespace($str),
5147 1
                ' '
5148
            ),
5149 1
            '-'
5150
        );
5151
    }
5152
5153
    /**
5154
     * Returns true if the string contains $needle, false otherwise. By default
5155
     * the comparison is case-sensitive, but can be made insensitive by setting
5156
     * $case_sensitive to false.
5157
     *
5158
     * @param string $haystack       <p>The input string.</p>
5159
     * @param string $needle         <p>Substring to look for.</p>
5160
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5161
     *
5162
     * @return bool whether or not $haystack contains $needle
5163
     */
5164 21
    public static function str_contains(
5165
        string $haystack,
5166
        string $needle,
5167
        bool $case_sensitive = true
5168
    ): bool {
5169 21
        if ($case_sensitive) {
5170 11
            return \strpos($haystack, $needle) !== false;
5171
        }
5172
5173 10
        return \mb_stripos($haystack, $needle) !== false;
5174
    }
5175
5176
    /**
5177
     * Returns true if the string contains all $needles, false otherwise. By
5178
     * default the comparison is case-sensitive, but can be made insensitive by
5179
     * setting $case_sensitive to false.
5180
     *
5181
     * @param string $haystack       <p>The input string.</p>
5182
     * @param array  $needles        <p>SubStrings to look for.</p>
5183
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5184
     *
5185
     * @return bool whether or not $haystack contains $needle
5186
     */
5187 45
    public static function str_contains_all(
5188
        string $haystack,
5189
        array $needles,
5190
        bool $case_sensitive = true
5191
    ): bool {
5192 45
        if ($haystack === '' || $needles === []) {
5193 1
            return false;
5194
        }
5195
5196
        /** @noinspection LoopWhichDoesNotLoopInspection */
5197 44
        foreach ($needles as &$needle) {
5198 44
            if ($case_sensitive) {
5199
                /** @noinspection NestedPositiveIfStatementsInspection */
5200 24
                if (\strpos($haystack, $needle) === false) {
5201 12
                    return  false;
5202
                }
5203
            }
5204
5205 33
            if (\mb_stripos($haystack, $needle) === false) {
5206 33
                return  false;
5207
            }
5208
        }
5209
5210 24
        return true;
5211
    }
5212
5213
    /**
5214
     * Returns true if the string contains any $needles, false otherwise. By
5215
     * default the comparison is case-sensitive, but can be made insensitive by
5216
     * setting $case_sensitive to false.
5217
     *
5218
     * @param string $haystack       <p>The input string.</p>
5219
     * @param array  $needles        <p>SubStrings to look for.</p>
5220
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
5221
     *
5222
     * @return bool
5223
     *              Whether or not $str contains $needle
5224
     */
5225 46
    public static function str_contains_any(
5226
        string $haystack,
5227
        array $needles,
5228
        bool $case_sensitive = true
5229
    ): bool {
5230 46
        if ($haystack === '' || $needles === []) {
5231 1
            return false;
5232
        }
5233
5234
        /** @noinspection LoopWhichDoesNotLoopInspection */
5235 45
        foreach ($needles as &$needle) {
5236 45
            if (!$needle) {
5237
                continue;
5238
            }
5239
5240 45
            if ($case_sensitive) {
5241 25
                if (\strpos($haystack, $needle) !== false) {
5242 14
                    return true;
5243
                }
5244
5245 13
                continue;
5246
            }
5247
5248 20
            if (\mb_stripos($haystack, $needle) !== false) {
5249 20
                return true;
5250
            }
5251
        }
5252
5253 19
        return false;
5254
    }
5255
5256
    /**
5257
     * Returns a lowercase and trimmed string separated by dashes. Dashes are
5258
     * inserted before uppercase characters (with the exception of the first
5259
     * character of the string), and in place of spaces as well as underscores.
5260
     *
5261
     * @param string $str      <p>The input string.</p>
5262
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5263
     *
5264
     * @return string
5265
     */
5266 19
    public static function str_dasherize(string $str, string $encoding = 'UTF-8'): string
5267
    {
5268 19
        return self::str_delimit($str, '-', $encoding);
5269
    }
5270
5271
    /**
5272
     * Returns a lowercase and trimmed string separated by the given delimiter.
5273
     * Delimiters are inserted before uppercase characters (with the exception
5274
     * of the first character of the string), and in place of spaces, dashes,
5275
     * and underscores. Alpha delimiters are not converted to lowercase.
5276
     *
5277
     * @param string      $str                           <p>The input string.</p>
5278
     * @param string      $delimiter                     <p>Sequence used to separate parts of the string.</p>
5279
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
5280
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
5281
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt,
5282
     *                                                   tr</p>
5283
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ ->
5284
     *                                                   ß</p>
5285
     *
5286
     * @return string
5287
     */
5288 49
    public static function str_delimit(
5289
        string $str,
5290
        string $delimiter,
5291
        string $encoding = 'UTF-8',
5292
        bool $clean_utf8 = false,
5293
        string $lang = null,
5294
        bool $try_to_keep_the_string_length = false
5295
    ): string {
5296 49
        if (self::$SUPPORT['mbstring'] === true) {
5297
            /** @noinspection PhpComposerExtensionStubsInspection */
5298 49
            $str = (string) \mb_ereg_replace('\\B(\\p{Lu})', '-\1', \trim($str));
5299
5300 49
            $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
5301 49
            if ($use_mb_functions === true && $encoding === 'UTF-8') {
5302 22
                $str = \mb_strtolower($str);
5303
            } else {
5304 27
                $str = self::strtolower($str, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
5305
            }
5306
5307
            /** @noinspection PhpComposerExtensionStubsInspection */
5308 49
            return (string) \mb_ereg_replace('[\\-_\\s]+', $delimiter, $str);
5309
        }
5310
5311
        $str = (string) \preg_replace('/\\B(\\p{Lu})/u', '-\1', \trim($str));
5312
5313
        $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
5314
        if ($use_mb_functions === true && $encoding === 'UTF-8') {
5315
            $str = \mb_strtolower($str);
5316
        } else {
5317
            $str = self::strtolower($str, $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
5318
        }
5319
5320
        return (string) \preg_replace('/[\\-_\\s]+/u', $delimiter, $str);
5321
    }
5322
5323
    /**
5324
     * Optimized "mb_detect_encoding()"-function -> with support for UTF-16 and UTF-32.
5325
     *
5326
     * @param string $str <p>The input string.</p>
5327
     *
5328
     * @return false|string
5329
     *                      The detected string-encoding e.g. UTF-8 or UTF-16BE,<br>
5330
     *                      otherwise it will return false e.g. for BINARY or not detected encoding.
5331
     */
5332 30
    public static function str_detect_encoding($str)
5333
    {
5334
        // init
5335 30
        $str = (string) $str;
5336
5337
        //
5338
        // 1.) check binary strings (010001001...) like UTF-16 / UTF-32 / PDF / Images / ...
5339
        //
5340
5341 30
        if (self::is_binary($str, true) === true) {
5342 11
            $is_utf32 = self::is_utf32($str, false);
5343 11
            if ($is_utf32 === 1) {
5344
                return 'UTF-32LE';
5345
            }
5346 11
            if ($is_utf32 === 2) {
5347 1
                return 'UTF-32BE';
5348
            }
5349
5350 11
            $is_utf16 = self::is_utf16($str, false);
5351 11
            if ($is_utf16 === 1) {
5352 3
                return 'UTF-16LE';
5353
            }
5354 11
            if ($is_utf16 === 2) {
5355 2
                return 'UTF-16BE';
5356
            }
5357
5358
            // is binary but not "UTF-16" or "UTF-32"
5359 9
            return false;
5360
        }
5361
5362
        //
5363
        // 2.) simple check for ASCII chars
5364
        //
5365
5366 26
        if (ASCII::is_ascii($str) === true) {
5367 10
            return 'ASCII';
5368
        }
5369
5370
        //
5371
        // 3.) simple check for UTF-8 chars
5372
        //
5373
5374 26
        if (self::is_utf8_string($str) === true) {
5375 19
            return 'UTF-8';
5376
        }
5377
5378
        //
5379
        // 4.) check via "mb_detect_encoding()"
5380
        //
5381
        // INFO: UTF-16, UTF-32, UCS2 and UCS4, encoding detection will fail always with "mb_detect_encoding()"
5382
5383
        $encoding_detecting_order = [
5384 15
            'ISO-8859-1',
5385
            'ISO-8859-2',
5386
            'ISO-8859-3',
5387
            'ISO-8859-4',
5388
            'ISO-8859-5',
5389
            'ISO-8859-6',
5390
            'ISO-8859-7',
5391
            'ISO-8859-8',
5392
            'ISO-8859-9',
5393
            'ISO-8859-10',
5394
            'ISO-8859-13',
5395
            'ISO-8859-14',
5396
            'ISO-8859-15',
5397
            'ISO-8859-16',
5398
            'WINDOWS-1251',
5399
            'WINDOWS-1252',
5400
            'WINDOWS-1254',
5401
            'CP932',
5402
            'CP936',
5403
            'CP950',
5404
            'CP866',
5405
            'CP850',
5406
            'CP51932',
5407
            'CP50220',
5408
            'CP50221',
5409
            'CP50222',
5410
            'ISO-2022-JP',
5411
            'ISO-2022-KR',
5412
            'JIS',
5413
            'JIS-ms',
5414
            'EUC-CN',
5415
            'EUC-JP',
5416
        ];
5417
5418 15
        if (self::$SUPPORT['mbstring'] === true) {
5419
            // info: do not use the symfony polyfill here
5420 15
            $encoding = \mb_detect_encoding($str, $encoding_detecting_order, true);
5421 15
            if ($encoding) {
5422 15
                return $encoding;
5423
            }
5424
        }
5425
5426
        //
5427
        // 5.) check via "iconv()"
5428
        //
5429
5430
        if (self::$ENCODINGS === null) {
5431
            self::$ENCODINGS = self::getData('encodings');
5432
        }
5433
5434
        foreach (self::$ENCODINGS as $encoding_tmp) {
5435
            // INFO: //IGNORE but still throw notice
5436
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
5437
            if ((string) @\iconv($encoding_tmp, $encoding_tmp . '//IGNORE', $str) === $str) {
5438
                return $encoding_tmp;
5439
            }
5440
        }
5441
5442
        return false;
5443
    }
5444
5445
    /**
5446
     * alias for "UTF8::str_ends_with()"
5447
     *
5448
     * @param string $haystack
5449
     * @param string $needle
5450
     *
5451
     * @return bool
5452
     *
5453
     * @see UTF8::str_ends_with()
5454
     * @deprecated <p>please use "UTF8::str_ends_with()"</p>
5455
     */
5456
    public static function str_ends(string $haystack, string $needle): bool
5457
    {
5458
        return self::str_ends_with($haystack, $needle);
5459
    }
5460
5461
    /**
5462
     * Check if the string ends with the given substring.
5463
     *
5464
     * @param string $haystack <p>The string to search in.</p>
5465
     * @param string $needle   <p>The substring to search for.</p>
5466
     *
5467
     * @return bool
5468
     */
5469 9
    public static function str_ends_with(string $haystack, string $needle): bool
5470
    {
5471 9
        if ($needle === '') {
5472 2
            return true;
5473
        }
5474
5475 9
        if ($haystack === '') {
5476
            return false;
5477
        }
5478
5479 9
        return \substr($haystack, -\strlen($needle)) === $needle;
5480
    }
5481
5482
    /**
5483
     * Returns true if the string ends with any of $substrings, false otherwise.
5484
     *
5485
     * - case-sensitive
5486
     *
5487
     * @param string   $str        <p>The input string.</p>
5488
     * @param string[] $substrings <p>Substrings to look for.</p>
5489
     *
5490
     * @return bool whether or not $str ends with $substring
5491
     */
5492 7
    public static function str_ends_with_any(string $str, array $substrings): bool
5493
    {
5494 7
        if ($substrings === []) {
5495
            return false;
5496
        }
5497
5498 7
        foreach ($substrings as &$substring) {
5499 7
            if (\substr($str, -\strlen($substring)) === $substring) {
5500 7
                return true;
5501
            }
5502
        }
5503
5504 6
        return false;
5505
    }
5506
5507
    /**
5508
     * Ensures that the string begins with $substring. If it doesn't, it's
5509
     * prepended.
5510
     *
5511
     * @param string $str       <p>The input string.</p>
5512
     * @param string $substring <p>The substring to add if not present.</p>
5513
     *
5514
     * @return string
5515
     */
5516 10
    public static function str_ensure_left(string $str, string $substring): string
5517
    {
5518
        if (
5519 10
            $substring !== ''
5520
            &&
5521 10
            \strpos($str, $substring) === 0
5522
        ) {
5523 6
            return $str;
5524
        }
5525
5526 4
        return $substring . $str;
5527
    }
5528
5529
    /**
5530
     * Ensures that the string ends with $substring. If it doesn't, it's appended.
5531
     *
5532
     * @param string $str       <p>The input string.</p>
5533
     * @param string $substring <p>The substring to add if not present.</p>
5534
     *
5535
     * @return string
5536
     */
5537 10
    public static function str_ensure_right(string $str, string $substring): string
5538
    {
5539
        if (
5540 10
            $str === ''
5541
            ||
5542 10
            $substring === ''
5543
            ||
5544 10
            \substr($str, -\strlen($substring)) !== $substring
5545
        ) {
5546 4
            $str .= $substring;
5547
        }
5548
5549 10
        return $str;
5550
    }
5551
5552
    /**
5553
     * Capitalizes the first word of the string, replaces underscores with
5554
     * spaces, and strips '_id'.
5555
     *
5556
     * @param string $str
5557
     *
5558
     * @return string
5559
     */
5560 3
    public static function str_humanize($str): string
5561
    {
5562 3
        $str = \str_replace(
5563
            [
5564 3
                '_id',
5565
                '_',
5566
            ],
5567
            [
5568 3
                '',
5569
                ' ',
5570
            ],
5571 3
            $str
5572
        );
5573
5574 3
        return self::ucfirst(\trim($str));
5575
    }
5576
5577
    /**
5578
     * alias for "UTF8::str_istarts_with()"
5579
     *
5580
     * @param string $haystack
5581
     * @param string $needle
5582
     *
5583
     * @return bool
5584
     *
5585
     * @see UTF8::str_istarts_with()
5586
     * @deprecated <p>please use "UTF8::str_istarts_with()"</p>
5587
     */
5588
    public static function str_ibegins(string $haystack, string $needle): bool
5589
    {
5590
        return self::str_istarts_with($haystack, $needle);
5591
    }
5592
5593
    /**
5594
     * alias for "UTF8::str_iends_with()"
5595
     *
5596
     * @param string $haystack
5597
     * @param string $needle
5598
     *
5599
     * @return bool
5600
     *
5601
     * @see UTF8::str_iends_with()
5602
     * @deprecated <p>please use "UTF8::str_iends_with()"</p>
5603
     */
5604
    public static function str_iends(string $haystack, string $needle): bool
5605
    {
5606
        return self::str_iends_with($haystack, $needle);
5607
    }
5608
5609
    /**
5610
     * Check if the string ends with the given substring, case-insensitive.
5611
     *
5612
     * @param string $haystack <p>The string to search in.</p>
5613
     * @param string $needle   <p>The substring to search for.</p>
5614
     *
5615
     * @return bool
5616
     */
5617 12
    public static function str_iends_with(string $haystack, string $needle): bool
5618
    {
5619 12
        if ($needle === '') {
5620 2
            return true;
5621
        }
5622
5623 12
        if ($haystack === '') {
5624
            return false;
5625
        }
5626
5627 12
        return self::strcasecmp(\substr($haystack, -\strlen($needle)), $needle) === 0;
5628
    }
5629
5630
    /**
5631
     * Returns true if the string ends with any of $substrings, false otherwise.
5632
     *
5633
     * - case-insensitive
5634
     *
5635
     * @param string   $str        <p>The input string.</p>
5636
     * @param string[] $substrings <p>Substrings to look for.</p>
5637
     *
5638
     * @return bool
5639
     *              <p>Whether or not $str ends with $substring.</p>
5640
     */
5641 4
    public static function str_iends_with_any(string $str, array $substrings): bool
5642
    {
5643 4
        if ($substrings === []) {
5644
            return false;
5645
        }
5646
5647 4
        foreach ($substrings as &$substring) {
5648 4
            if (self::str_iends_with($str, $substring)) {
5649 4
                return true;
5650
            }
5651
        }
5652
5653
        return false;
5654
    }
5655
5656
    /**
5657
     * Returns the index of the first occurrence of $needle in the string,
5658
     * and false if not found. Accepts an optional offset from which to begin
5659
     * the search.
5660
     *
5661
     * @param string $str      <p>The input string.</p>
5662
     * @param string $needle   <p>Substring to look for.</p>
5663
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
5664
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5665
     *
5666
     * @return false|int
5667
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
5668
     *
5669
     * @see UTF8::stripos()
5670
     * @deprecated <p>please use "UTF8::stripos()"</p>
5671
     */
5672
    public static function str_iindex_first(
5673
        string $str,
5674
        string $needle,
5675
        int $offset = 0,
5676
        string $encoding = 'UTF-8'
5677
    ) {
5678
        return self::stripos(
5679
            $str,
5680
            $needle,
5681
            $offset,
5682
            $encoding
5683
        );
5684
    }
5685
5686
    /**
5687
     * Returns the index of the last occurrence of $needle in the string,
5688
     * and false if not found. Accepts an optional offset from which to begin
5689
     * the search. Offsets may be negative to count from the last character
5690
     * in the string.
5691
     *
5692
     * @param string $str      <p>The input string.</p>
5693
     * @param string $needle   <p>Substring to look for.</p>
5694
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
5695
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5696
     *
5697
     * @return false|int
5698
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
5699
     *
5700
     * @see UTF8::strripos()
5701
     * @deprecated <p>please use "UTF8::strripos()"</p>
5702
     */
5703
    public static function str_iindex_last(
5704
        string $str,
5705
        string $needle,
5706
        int $offset = 0,
5707
        string $encoding = 'UTF-8'
5708
    ) {
5709
        return self::strripos(
5710
            $str,
5711
            $needle,
5712
            $offset,
5713
            $encoding
5714
        );
5715
    }
5716
5717
    /**
5718
     * Returns the index of the first occurrence of $needle in the string,
5719
     * and false if not found. Accepts an optional offset from which to begin
5720
     * the search.
5721
     *
5722
     * @param string $str      <p>The input string.</p>
5723
     * @param string $needle   <p>Substring to look for.</p>
5724
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
5725
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5726
     *
5727
     * @return false|int
5728
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
5729
     *
5730
     * @see UTF8::strpos()
5731
     * @deprecated <p>please use "UTF8::strpos()"</p>
5732
     */
5733 10
    public static function str_index_first(
5734
        string $str,
5735
        string $needle,
5736
        int $offset = 0,
5737
        string $encoding = 'UTF-8'
5738
    ) {
5739 10
        return self::strpos(
5740 10
            $str,
5741 10
            $needle,
5742 10
            $offset,
5743 10
            $encoding
5744
        );
5745
    }
5746
5747
    /**
5748
     * Returns the index of the last occurrence of $needle in the string,
5749
     * and false if not found. Accepts an optional offset from which to begin
5750
     * the search. Offsets may be negative to count from the last character
5751
     * in the string.
5752
     *
5753
     * @param string $str      <p>The input string.</p>
5754
     * @param string $needle   <p>Substring to look for.</p>
5755
     * @param int    $offset   [optional] <p>Offset from which to search. Default: 0</p>
5756
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
5757
     *
5758
     * @return false|int
5759
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
5760
     *
5761
     * @see UTF8::strrpos()
5762
     * @deprecated <p>please use "UTF8::strrpos()"</p>
5763
     */
5764 10
    public static function str_index_last(
5765
        string $str,
5766
        string $needle,
5767
        int $offset = 0,
5768
        string $encoding = 'UTF-8'
5769
    ) {
5770 10
        return self::strrpos(
5771 10
            $str,
5772 10
            $needle,
5773 10
            $offset,
5774 10
            $encoding
5775
        );
5776
    }
5777
5778
    /**
5779
     * Inserts $substring into the string at the $index provided.
5780
     *
5781
     * @param string $str       <p>The input string.</p>
5782
     * @param string $substring <p>String to be inserted.</p>
5783
     * @param int    $index     <p>The index at which to insert the substring.</p>
5784
     * @param string $encoding  [optional] <p>Set the charset for e.g. "mb_" function</p>
5785
     *
5786
     * @return string
5787
     */
5788 8
    public static function str_insert(
5789
        string $str,
5790
        string $substring,
5791
        int $index,
5792
        string $encoding = 'UTF-8'
5793
    ): string {
5794 8
        if ($encoding === 'UTF-8') {
5795 4
            $len = (int) \mb_strlen($str);
5796 4
            if ($index > $len) {
5797
                return $str;
5798
            }
5799
5800
            /** @noinspection UnnecessaryCastingInspection */
5801 4
            return (string) \mb_substr($str, 0, $index) .
5802 4
                   $substring .
5803 4
                   (string) \mb_substr($str, $index, $len);
5804
        }
5805
5806 4
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
5807
5808 4
        $len = (int) self::strlen($str, $encoding);
5809 4
        if ($index > $len) {
5810 1
            return $str;
5811
        }
5812
5813 3
        return ((string) self::substr($str, 0, $index, $encoding)) .
5814 3
               $substring .
5815 3
               ((string) self::substr($str, $index, $len, $encoding));
5816
    }
5817
5818
    /**
5819
     * Case-insensitive and UTF-8 safe version of <function>str_replace</function>.
5820
     *
5821
     * @see http://php.net/manual/en/function.str-ireplace.php
5822
     *
5823
     * @param mixed $search  <p>
5824
     *                       Every replacement with search array is
5825
     *                       performed on the result of previous replacement.
5826
     *                       </p>
5827
     * @param mixed $replace <p>
5828
     *                       </p>
5829
     * @param mixed $subject <p>
5830
     *                       If subject is an array, then the search and
5831
     *                       replace is performed with every entry of
5832
     *                       subject, and the return value is an array as
5833
     *                       well.
5834
     *                       </p>
5835
     * @param int   $count   [optional] <p>
5836
     *                       The number of matched and replaced needles will
5837
     *                       be returned in count which is passed by
5838
     *                       reference.
5839
     *                       </p>
5840
     *
5841
     * @return mixed a string or an array of replacements
5842
     */
5843 29
    public static function str_ireplace($search, $replace, $subject, &$count = null)
5844
    {
5845 29
        $search = (array) $search;
5846
5847
        /** @noinspection AlterInForeachInspection */
5848 29
        foreach ($search as &$s) {
5849 29
            $s = (string) $s;
5850 29
            if ($s === '') {
5851 6
                $s = '/^(?<=.)$/';
5852
            } else {
5853 29
                $s = '/' . \preg_quote($s, '/') . '/ui';
5854
            }
5855
        }
5856
5857 29
        $subject = \preg_replace($search, $replace, $subject, -1, $replace);
5858 29
        $count = $replace; // used as reference parameter
5859
5860 29
        return $subject;
5861
    }
5862
5863
    /**
5864
     * Replaces $search from the beginning of string with $replacement.
5865
     *
5866
     * @param string $str         <p>The input string.</p>
5867
     * @param string $search      <p>The string to search for.</p>
5868
     * @param string $replacement <p>The replacement.</p>
5869
     *
5870
     * @return string string after the replacements
5871
     */
5872 17
    public static function str_ireplace_beginning(string $str, string $search, string $replacement): string
5873
    {
5874 17
        if ($str === '') {
5875 4
            if ($replacement === '') {
5876 2
                return '';
5877
            }
5878
5879 2
            if ($search === '') {
5880 2
                return $replacement;
5881
            }
5882
        }
5883
5884 13
        if ($search === '') {
5885 2
            return $str . $replacement;
5886
        }
5887
5888 11
        if (\stripos($str, $search) === 0) {
5889 10
            return $replacement . \substr($str, \strlen($search));
5890
        }
5891
5892 1
        return $str;
5893
    }
5894
5895
    /**
5896
     * Replaces $search from the ending of string with $replacement.
5897
     *
5898
     * @param string $str         <p>The input string.</p>
5899
     * @param string $search      <p>The string to search for.</p>
5900
     * @param string $replacement <p>The replacement.</p>
5901
     *
5902
     * @return string
5903
     *                <p>string after the replacements.</p>
5904
     */
5905 17
    public static function str_ireplace_ending(string $str, string $search, string $replacement): string
5906
    {
5907 17
        if ($str === '') {
5908 4
            if ($replacement === '') {
5909 2
                return '';
5910
            }
5911
5912 2
            if ($search === '') {
5913 2
                return $replacement;
5914
            }
5915
        }
5916
5917 13
        if ($search === '') {
5918 2
            return $str . $replacement;
5919
        }
5920
5921 11
        if (\stripos($str, $search, \strlen($str) - \strlen($search)) !== false) {
5922 9
            $str = \substr($str, 0, -\strlen($search)) . $replacement;
5923
        }
5924
5925 11
        return $str;
5926
    }
5927
5928
    /**
5929
     * Check if the string starts with the given substring, case-insensitive.
5930
     *
5931
     * @param string $haystack <p>The string to search in.</p>
5932
     * @param string $needle   <p>The substring to search for.</p>
5933
     *
5934
     * @return bool
5935
     */
5936 12
    public static function str_istarts_with(string $haystack, string $needle): bool
5937
    {
5938 12
        if ($needle === '') {
5939 2
            return true;
5940
        }
5941
5942 12
        if ($haystack === '') {
5943
            return false;
5944
        }
5945
5946 12
        return self::stripos($haystack, $needle) === 0;
5947
    }
5948
5949
    /**
5950
     * Returns true if the string begins with any of $substrings, false otherwise.
5951
     *
5952
     * - case-insensitive
5953
     *
5954
     * @param string $str        <p>The input string.</p>
5955
     * @param array  $substrings <p>Substrings to look for.</p>
5956
     *
5957
     * @return bool whether or not $str starts with $substring
5958
     */
5959 4
    public static function str_istarts_with_any(string $str, array $substrings): bool
5960
    {
5961 4
        if ($str === '') {
5962
            return false;
5963
        }
5964
5965 4
        if ($substrings === []) {
5966
            return false;
5967
        }
5968
5969 4
        foreach ($substrings as &$substring) {
5970 4
            if (self::str_istarts_with($str, $substring)) {
5971 4
                return true;
5972
            }
5973
        }
5974
5975
        return false;
5976
    }
5977
5978
    /**
5979
     * Gets the substring after the first occurrence of a separator.
5980
     *
5981
     * @param string $str       <p>The input string.</p>
5982
     * @param string $separator <p>The string separator.</p>
5983
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
5984
     *
5985
     * @return string
5986
     */
5987 1
    public static function str_isubstr_after_first_separator(
5988
        string $str,
5989
        string $separator,
5990
        string $encoding = 'UTF-8'
5991
    ): string {
5992 1
        if ($separator === '' || $str === '') {
5993 1
            return '';
5994
        }
5995
5996 1
        $offset = self::stripos($str, $separator);
5997 1
        if ($offset === false) {
5998 1
            return '';
5999
        }
6000
6001 1
        if ($encoding === 'UTF-8') {
6002 1
            return (string) \mb_substr(
6003 1
                $str,
6004 1
                $offset + (int) \mb_strlen($separator)
6005
            );
6006
        }
6007
6008
        return (string) self::substr(
6009
            $str,
6010
            $offset + (int) self::strlen($separator, $encoding),
6011
            null,
6012
            $encoding
6013
        );
6014
    }
6015
6016
    /**
6017
     * Gets the substring after the last occurrence of a separator.
6018
     *
6019
     * @param string $str       <p>The input string.</p>
6020
     * @param string $separator <p>The string separator.</p>
6021
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
6022
     *
6023
     * @return string
6024
     */
6025 1
    public static function str_isubstr_after_last_separator(
6026
        string $str,
6027
        string $separator,
6028
        string $encoding = 'UTF-8'
6029
    ): string {
6030 1
        if ($separator === '' || $str === '') {
6031 1
            return '';
6032
        }
6033
6034 1
        $offset = self::strripos($str, $separator);
6035 1
        if ($offset === false) {
6036 1
            return '';
6037
        }
6038
6039 1
        if ($encoding === 'UTF-8') {
6040 1
            return (string) \mb_substr(
6041 1
                $str,
6042 1
                $offset + (int) self::strlen($separator)
6043
            );
6044
        }
6045
6046
        return (string) self::substr(
6047
            $str,
6048
            $offset + (int) self::strlen($separator, $encoding),
6049
            null,
6050
            $encoding
6051
        );
6052
    }
6053
6054
    /**
6055
     * Gets the substring before the first occurrence of a separator.
6056
     *
6057
     * @param string $str       <p>The input string.</p>
6058
     * @param string $separator <p>The string separator.</p>
6059
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
6060
     *
6061
     * @return string
6062
     */
6063 1
    public static function str_isubstr_before_first_separator(
6064
        string $str,
6065
        string $separator,
6066
        string $encoding = 'UTF-8'
6067
    ): string {
6068 1
        if ($separator === '' || $str === '') {
6069 1
            return '';
6070
        }
6071
6072 1
        $offset = self::stripos($str, $separator);
6073 1
        if ($offset === false) {
6074 1
            return '';
6075
        }
6076
6077 1
        if ($encoding === 'UTF-8') {
6078 1
            return (string) \mb_substr($str, 0, $offset);
6079
        }
6080
6081
        return (string) self::substr($str, 0, $offset, $encoding);
6082
    }
6083
6084
    /**
6085
     * Gets the substring before the last occurrence of a separator.
6086
     *
6087
     * @param string $str       <p>The input string.</p>
6088
     * @param string $separator <p>The string separator.</p>
6089
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
6090
     *
6091
     * @return string
6092
     */
6093 1
    public static function str_isubstr_before_last_separator(
6094
        string $str,
6095
        string $separator,
6096
        string $encoding = 'UTF-8'
6097
    ): string {
6098 1
        if ($separator === '' || $str === '') {
6099 1
            return '';
6100
        }
6101
6102 1
        if ($encoding === 'UTF-8') {
6103 1
            $offset = \mb_strripos($str, $separator);
6104 1
            if ($offset === false) {
6105 1
                return '';
6106
            }
6107
6108 1
            return (string) \mb_substr($str, 0, $offset);
6109
        }
6110
6111
        $offset = self::strripos($str, $separator, 0, $encoding);
6112
        if ($offset === false) {
6113
            return '';
6114
        }
6115
6116
        return (string) self::substr($str, 0, $offset, $encoding);
6117
    }
6118
6119
    /**
6120
     * Gets the substring after (or before via "$before_needle") the first occurrence of the "$needle".
6121
     *
6122
     * @param string $str           <p>The input string.</p>
6123
     * @param string $needle        <p>The string to look for.</p>
6124
     * @param bool   $before_needle [optional] <p>Default: false</p>
6125
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
6126
     *
6127
     * @return string
6128
     */
6129 2
    public static function str_isubstr_first(
6130
        string $str,
6131
        string $needle,
6132
        bool $before_needle = false,
6133
        string $encoding = 'UTF-8'
6134
    ): string {
6135
        if (
6136 2
            $needle === ''
6137
            ||
6138 2
            $str === ''
6139
        ) {
6140 2
            return '';
6141
        }
6142
6143 2
        $part = self::stristr(
6144 2
            $str,
6145 2
            $needle,
6146 2
            $before_needle,
6147 2
            $encoding
6148
        );
6149 2
        if ($part === false) {
6150 2
            return '';
6151
        }
6152
6153 2
        return $part;
6154
    }
6155
6156
    /**
6157
     * Gets the substring after (or before via "$before_needle") the last occurrence of the "$needle".
6158
     *
6159
     * @param string $str           <p>The input string.</p>
6160
     * @param string $needle        <p>The string to look for.</p>
6161
     * @param bool   $before_needle [optional] <p>Default: false</p>
6162
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
6163
     *
6164
     * @return string
6165
     */
6166 1
    public static function str_isubstr_last(
6167
        string $str,
6168
        string $needle,
6169
        bool $before_needle = false,
6170
        string $encoding = 'UTF-8'
6171
    ): string {
6172
        if (
6173 1
            $needle === ''
6174
            ||
6175 1
            $str === ''
6176
        ) {
6177 1
            return '';
6178
        }
6179
6180 1
        $part = self::strrichr(
6181 1
            $str,
6182 1
            $needle,
6183 1
            $before_needle,
6184 1
            $encoding
6185
        );
6186 1
        if ($part === false) {
6187 1
            return '';
6188
        }
6189
6190 1
        return $part;
6191
    }
6192
6193
    /**
6194
     * Returns the last $n characters of the string.
6195
     *
6196
     * @param string $str      <p>The input string.</p>
6197
     * @param int    $n        <p>Number of characters to retrieve from the end.</p>
6198
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6199
     *
6200
     * @return string
6201
     */
6202 12
    public static function str_last_char(
6203
        string $str,
6204
        int $n = 1,
6205
        string $encoding = 'UTF-8'
6206
    ): string {
6207 12
        if ($str === '' || $n <= 0) {
6208 4
            return '';
6209
        }
6210
6211 8
        if ($encoding === 'UTF-8') {
6212 4
            return (string) \mb_substr($str, -$n);
6213
        }
6214
6215 4
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
6216
6217 4
        return (string) self::substr($str, -$n, null, $encoding);
6218
    }
6219
6220
    /**
6221
     * Limit the number of characters in a string.
6222
     *
6223
     * @param string $str        <p>The input string.</p>
6224
     * @param int    $length     [optional] <p>Default: 100</p>
6225
     * @param string $str_add_on [optional] <p>Default: …</p>
6226
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
6227
     *
6228
     * @return string
6229
     */
6230 2
    public static function str_limit(
6231
        string $str,
6232
        int $length = 100,
6233
        string $str_add_on = '…',
6234
        string $encoding = 'UTF-8'
6235
    ): string {
6236 2
        if ($str === '' || $length <= 0) {
6237 2
            return '';
6238
        }
6239
6240 2
        if ($encoding === 'UTF-8') {
6241 2
            if ((int) \mb_strlen($str) <= $length) {
6242 2
                return $str;
6243
            }
6244
6245
            /** @noinspection UnnecessaryCastingInspection */
6246 2
            return (string) \mb_substr($str, 0, $length - (int) self::strlen($str_add_on)) . $str_add_on;
6247
        }
6248
6249
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
6250
6251
        if ((int) self::strlen($str, $encoding) <= $length) {
6252
            return $str;
6253
        }
6254
6255
        return ((string) self::substr($str, 0, $length - (int) self::strlen($str_add_on), $encoding)) . $str_add_on;
6256
    }
6257
6258
    /**
6259
     * Limit the number of characters in a string, but also after the next word.
6260
     *
6261
     * @param string $str        <p>The input string.</p>
6262
     * @param int    $length     [optional] <p>Default: 100</p>
6263
     * @param string $str_add_on [optional] <p>Default: …</p>
6264
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
6265
     *
6266
     * @return string
6267
     */
6268 6
    public static function str_limit_after_word(
6269
        string $str,
6270
        int $length = 100,
6271
        string $str_add_on = '…',
6272
        string $encoding = 'UTF-8'
6273
    ): string {
6274 6
        if ($str === '' || $length <= 0) {
6275 2
            return '';
6276
        }
6277
6278 6
        if ($encoding === 'UTF-8') {
6279
            /** @noinspection UnnecessaryCastingInspection */
6280 2
            if ((int) \mb_strlen($str) <= $length) {
6281 2
                return $str;
6282
            }
6283
6284 2
            if (\mb_substr($str, $length - 1, 1) === ' ') {
6285 2
                return ((string) \mb_substr($str, 0, $length - 1)) . $str_add_on;
6286
            }
6287
6288 2
            $str = \mb_substr($str, 0, $length);
6289
6290 2
            $array = \explode(' ', $str);
6291 2
            \array_pop($array);
6292 2
            $new_str = \implode(' ', $array);
6293
6294 2
            if ($new_str === '') {
6295 2
                return ((string) \mb_substr($str, 0, $length - 1)) . $str_add_on;
6296
            }
6297
        } else {
6298 4
            if ((int) self::strlen($str, $encoding) <= $length) {
6299
                return $str;
6300
            }
6301
6302 4
            if (self::substr($str, $length - 1, 1, $encoding) === ' ') {
6303 3
                return ((string) self::substr($str, 0, $length - 1, $encoding)) . $str_add_on;
6304
            }
6305
6306
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
6307 1
            $str = self::substr($str, 0, $length, $encoding);
6308
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
6309 1
            if ($str === false) {
6310
                return '' . $str_add_on;
6311
            }
6312
6313 1
            $array = \explode(' ', $str);
6314 1
            \array_pop($array);
6315 1
            $new_str = \implode(' ', $array);
6316
6317 1
            if ($new_str === '') {
6318
                return ((string) self::substr($str, 0, $length - 1, $encoding)) . $str_add_on;
6319
            }
6320
        }
6321
6322 3
        return $new_str . $str_add_on;
6323
    }
6324
6325
    /**
6326
     * Returns the longest common prefix between the $str1 and $str2.
6327
     *
6328
     * @param string $str1     <p>The input sting.</p>
6329
     * @param string $str2     <p>Second string for comparison.</p>
6330
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6331
     *
6332
     * @return string
6333
     */
6334 10
    public static function str_longest_common_prefix(
6335
        string $str1,
6336
        string $str2,
6337
        string $encoding = 'UTF-8'
6338
    ): string {
6339
        // init
6340 10
        $longest_common_prefix = '';
6341
6342 10
        if ($encoding === 'UTF-8') {
6343 5
            $max_length = (int) \min(
6344 5
                \mb_strlen($str1),
6345 5
                \mb_strlen($str2)
6346
            );
6347
6348 5
            for ($i = 0; $i < $max_length; ++$i) {
6349 4
                $char = \mb_substr($str1, $i, 1);
6350
6351
                if (
6352 4
                    $char !== false
6353
                    &&
6354 4
                    $char === \mb_substr($str2, $i, 1)
6355
                ) {
6356 3
                    $longest_common_prefix .= $char;
6357
                } else {
6358 3
                    break;
6359
                }
6360
            }
6361
        } else {
6362 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
6363
6364 5
            $max_length = (int) \min(
6365 5
                self::strlen($str1, $encoding),
6366 5
                self::strlen($str2, $encoding)
6367
            );
6368
6369 5
            for ($i = 0; $i < $max_length; ++$i) {
6370 4
                $char = self::substr($str1, $i, 1, $encoding);
6371
6372
                if (
6373 4
                    $char !== false
6374
                    &&
6375 4
                    $char === self::substr($str2, $i, 1, $encoding)
6376
                ) {
6377 3
                    $longest_common_prefix .= $char;
6378
                } else {
6379 3
                    break;
6380
                }
6381
            }
6382
        }
6383
6384 10
        return $longest_common_prefix;
6385
    }
6386
6387
    /**
6388
     * Returns the longest common substring between the $str1 and $str2.
6389
     * In the case of ties, it returns that which occurs first.
6390
     *
6391
     * @param string $str1
6392
     * @param string $str2     <p>Second string for comparison.</p>
6393
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6394
     *
6395
     * @return string
6396
     *                <p>A string with its $str being the longest common substring.</p>
6397
     */
6398 11
    public static function str_longest_common_substring(
6399
        string $str1,
6400
        string $str2,
6401
        string $encoding = 'UTF-8'
6402
    ): string {
6403 11
        if ($str1 === '' || $str2 === '') {
6404 2
            return '';
6405
        }
6406
6407
        // Uses dynamic programming to solve
6408
        // http://en.wikipedia.org/wiki/Longest_common_substring_problem
6409
6410 9
        if ($encoding === 'UTF-8') {
6411 4
            $str_length = (int) \mb_strlen($str1);
6412 4
            $other_length = (int) \mb_strlen($str2);
6413
        } else {
6414 5
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
6415
6416 5
            $str_length = (int) self::strlen($str1, $encoding);
6417 5
            $other_length = (int) self::strlen($str2, $encoding);
6418
        }
6419
6420
        // Return if either string is empty
6421 9
        if ($str_length === 0 || $other_length === 0) {
6422
            return '';
6423
        }
6424
6425 9
        $len = 0;
6426 9
        $end = 0;
6427 9
        $table = \array_fill(
6428 9
            0,
6429 9
            $str_length + 1,
6430 9
            \array_fill(0, $other_length + 1, 0)
6431
        );
6432
6433 9
        if ($encoding === 'UTF-8') {
6434 9
            for ($i = 1; $i <= $str_length; ++$i) {
6435 9
                for ($j = 1; $j <= $other_length; ++$j) {
6436 9
                    $str_char = \mb_substr($str1, $i - 1, 1);
6437 9
                    $other_char = \mb_substr($str2, $j - 1, 1);
6438
6439 9
                    if ($str_char === $other_char) {
6440 8
                        $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
6441 8
                        if ($table[$i][$j] > $len) {
6442 8
                            $len = $table[$i][$j];
6443 8
                            $end = $i;
6444
                        }
6445
                    } else {
6446 9
                        $table[$i][$j] = 0;
6447
                    }
6448
                }
6449
            }
6450
        } else {
6451
            for ($i = 1; $i <= $str_length; ++$i) {
6452
                for ($j = 1; $j <= $other_length; ++$j) {
6453
                    $str_char = self::substr($str1, $i - 1, 1, $encoding);
6454
                    $other_char = self::substr($str2, $j - 1, 1, $encoding);
6455
6456
                    if ($str_char === $other_char) {
6457
                        $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
6458
                        if ($table[$i][$j] > $len) {
6459
                            $len = $table[$i][$j];
6460
                            $end = $i;
6461
                        }
6462
                    } else {
6463
                        $table[$i][$j] = 0;
6464
                    }
6465
                }
6466
            }
6467
        }
6468
6469 9
        if ($encoding === 'UTF-8') {
6470 9
            return (string) \mb_substr($str1, $end - $len, $len);
6471
        }
6472
6473
        return (string) self::substr($str1, $end - $len, $len, $encoding);
6474
    }
6475
6476
    /**
6477
     * Returns the longest common suffix between the $str1 and $str2.
6478
     *
6479
     * @param string $str1
6480
     * @param string $str2     <p>Second string for comparison.</p>
6481
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6482
     *
6483
     * @return string
6484
     */
6485 10
    public static function str_longest_common_suffix(
6486
        string $str1,
6487
        string $str2,
6488
        string $encoding = 'UTF-8'
6489
    ): string {
6490 10
        if ($str1 === '' || $str2 === '') {
6491 2
            return '';
6492
        }
6493
6494 8
        if ($encoding === 'UTF-8') {
6495 4
            $max_length = (int) \min(
6496 4
                \mb_strlen($str1, $encoding),
6497 4
                \mb_strlen($str2, $encoding)
6498
            );
6499
6500 4
            $longest_common_suffix = '';
6501 4
            for ($i = 1; $i <= $max_length; ++$i) {
6502 4
                $char = \mb_substr($str1, -$i, 1);
6503
6504
                if (
6505 4
                    $char !== false
6506
                    &&
6507 4
                    $char === \mb_substr($str2, -$i, 1)
6508
                ) {
6509 3
                    $longest_common_suffix = $char . $longest_common_suffix;
6510
                } else {
6511 3
                    break;
6512
                }
6513
            }
6514
        } else {
6515 4
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
6516
6517 4
            $max_length = (int) \min(
6518 4
                self::strlen($str1, $encoding),
6519 4
                self::strlen($str2, $encoding)
6520
            );
6521
6522 4
            $longest_common_suffix = '';
6523 4
            for ($i = 1; $i <= $max_length; ++$i) {
6524 4
                $char = self::substr($str1, -$i, 1, $encoding);
6525
6526
                if (
6527 4
                    $char !== false
6528
                    &&
6529 4
                    $char === self::substr($str2, -$i, 1, $encoding)
6530
                ) {
6531 3
                    $longest_common_suffix = $char . $longest_common_suffix;
6532
                } else {
6533 3
                    break;
6534
                }
6535
            }
6536
        }
6537
6538 8
        return $longest_common_suffix;
6539
    }
6540
6541
    /**
6542
     * Returns true if $str matches the supplied pattern, false otherwise.
6543
     *
6544
     * @param string $str     <p>The input string.</p>
6545
     * @param string $pattern <p>Regex pattern to match against.</p>
6546
     *
6547
     * @return bool whether or not $str matches the pattern
6548
     */
6549
    public static function str_matches_pattern(string $str, string $pattern): bool
6550
    {
6551
        return (bool) \preg_match('/' . $pattern . '/u', $str);
6552
    }
6553
6554
    /**
6555
     * Returns whether or not a character exists at an index. Offsets may be
6556
     * negative to count from the last character in the string. Implements
6557
     * part of the ArrayAccess interface.
6558
     *
6559
     * @param string $str      <p>The input string.</p>
6560
     * @param int    $offset   <p>The index to check.</p>
6561
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6562
     *
6563
     * @return bool whether or not the index exists
6564
     */
6565 6
    public static function str_offset_exists(string $str, int $offset, string $encoding = 'UTF-8'): bool
6566
    {
6567
        // init
6568 6
        $length = (int) self::strlen($str, $encoding);
6569
6570 6
        if ($offset >= 0) {
6571 3
            return $length > $offset;
6572
        }
6573
6574 3
        return $length >= \abs($offset);
6575
    }
6576
6577
    /**
6578
     * Returns the character at the given index. Offsets may be negative to
6579
     * count from the last character in the string. Implements part of the
6580
     * ArrayAccess interface, and throws an OutOfBoundsException if the index
6581
     * does not exist.
6582
     *
6583
     * @param string $str      <p>The input string.</p>
6584
     * @param int    $index    <p>The <strong>index</strong> from which to retrieve the char.</p>
6585
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6586
     *
6587
     * @throws \OutOfBoundsException if the positive or negative offset does not exist
6588
     *
6589
     * @return string
6590
     *                <p>The character at the specified index.</p>
6591
     */
6592 2
    public static function str_offset_get(string $str, int $index, string $encoding = 'UTF-8'): string
6593
    {
6594
        // init
6595 2
        $length = (int) self::strlen($str);
6596
6597
        if (
6598 2
            ($index >= 0 && $length <= $index)
6599
            ||
6600 2
            $length < \abs($index)
6601
        ) {
6602 1
            throw new \OutOfBoundsException('No character exists at the index');
6603
        }
6604
6605 1
        return self::char_at($str, $index, $encoding);
6606
    }
6607
6608
    /**
6609
     * Pad a UTF-8 string to a given length with another string.
6610
     *
6611
     * @param string     $str        <p>The input string.</p>
6612
     * @param int        $pad_length <p>The length of return string.</p>
6613
     * @param string     $pad_string [optional] <p>String to use for padding the input string.</p>
6614
     * @param int|string $pad_type   [optional] <p>
6615
     *                               Can be <strong>STR_PAD_RIGHT</strong> (default), [or string "right"]<br>
6616
     *                               <strong>STR_PAD_LEFT</strong> [or string "left"] or<br>
6617
     *                               <strong>STR_PAD_BOTH</strong> [or string "both"]
6618
     *                               </p>
6619
     * @param string     $encoding   [optional] <p>Default: 'UTF-8'</p>
6620
     *
6621
     * @return string
6622
     *                <p>Returns the padded string.</p>
6623
     */
6624 41
    public static function str_pad(
6625
        string $str,
6626
        int $pad_length,
6627
        string $pad_string = ' ',
6628
        $pad_type = \STR_PAD_RIGHT,
6629
        string $encoding = 'UTF-8'
6630
    ): string {
6631 41
        if ($pad_length === 0 || $pad_string === '') {
6632 1
            return $str;
6633
        }
6634
6635 41
        if ($pad_type !== (int) $pad_type) {
6636 13
            if ($pad_type === 'left') {
6637 3
                $pad_type = \STR_PAD_LEFT;
6638 10
            } elseif ($pad_type === 'right') {
6639 6
                $pad_type = \STR_PAD_RIGHT;
6640 4
            } elseif ($pad_type === 'both') {
6641 3
                $pad_type = \STR_PAD_BOTH;
6642
            } else {
6643 1
                throw new \InvalidArgumentException(
6644 1
                    'Pad expects $pad_type to be "STR_PAD_*" or ' . "to be one of 'left', 'right' or 'both'"
6645
                );
6646
            }
6647
        }
6648
6649 40
        if ($encoding === 'UTF-8') {
6650 25
            $str_length = (int) \mb_strlen($str);
6651
6652 25
            if ($pad_length >= $str_length) {
6653
                switch ($pad_type) {
6654 25
                    case \STR_PAD_LEFT:
6655 8
                        $ps_length = (int) \mb_strlen($pad_string);
6656
6657 8
                        $diff = ($pad_length - $str_length);
6658
6659 8
                        $pre = (string) \mb_substr(
6660 8
                            \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
6661 8
                            0,
6662 8
                            $diff
6663
                        );
6664 8
                        $post = '';
6665
6666 8
                        break;
6667
6668 20
                    case \STR_PAD_BOTH:
6669 14
                        $diff = ($pad_length - $str_length);
6670
6671 14
                        $ps_length_left = (int) \floor($diff / 2);
6672
6673 14
                        $ps_length_right = (int) \ceil($diff / 2);
6674
6675 14
                        $pre = (string) \mb_substr(
6676 14
                            \str_repeat($pad_string, $ps_length_left),
6677 14
                            0,
6678 14
                            $ps_length_left
6679
                        );
6680 14
                        $post = (string) \mb_substr(
6681 14
                            \str_repeat($pad_string, $ps_length_right),
6682 14
                            0,
6683 14
                            $ps_length_right
6684
                        );
6685
6686 14
                        break;
6687
6688 9
                    case \STR_PAD_RIGHT:
6689
                    default:
6690 9
                        $ps_length = (int) \mb_strlen($pad_string);
6691
6692 9
                        $diff = ($pad_length - $str_length);
6693
6694 9
                        $post = (string) \mb_substr(
6695 9
                            \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
6696 9
                            0,
6697 9
                            $diff
6698
                        );
6699 9
                        $pre = '';
6700
                }
6701
6702 25
                return $pre . $str . $post;
6703
            }
6704
6705 3
            return $str;
6706
        }
6707
6708 15
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
6709
6710 15
        $str_length = (int) self::strlen($str, $encoding);
6711
6712 15
        if ($pad_length >= $str_length) {
6713
            switch ($pad_type) {
6714 14
                case \STR_PAD_LEFT:
6715 5
                    $ps_length = (int) self::strlen($pad_string, $encoding);
6716
6717 5
                    $diff = ($pad_length - $str_length);
6718
6719 5
                    $pre = (string) self::substr(
6720 5
                        \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
6721 5
                        0,
6722 5
                        $diff,
6723 5
                        $encoding
6724
                    );
6725 5
                    $post = '';
6726
6727 5
                    break;
6728
6729 9
                case \STR_PAD_BOTH:
6730 3
                    $diff = ($pad_length - $str_length);
6731
6732 3
                    $ps_length_left = (int) \floor($diff / 2);
6733
6734 3
                    $ps_length_right = (int) \ceil($diff / 2);
6735
6736 3
                    $pre = (string) self::substr(
6737 3
                        \str_repeat($pad_string, $ps_length_left),
6738 3
                        0,
6739 3
                        $ps_length_left,
6740 3
                        $encoding
6741
                    );
6742 3
                    $post = (string) self::substr(
6743 3
                        \str_repeat($pad_string, $ps_length_right),
6744 3
                        0,
6745 3
                        $ps_length_right,
6746 3
                        $encoding
6747
                    );
6748
6749 3
                    break;
6750
6751 6
                case \STR_PAD_RIGHT:
6752
                default:
6753 6
                    $ps_length = (int) self::strlen($pad_string, $encoding);
6754
6755 6
                    $diff = ($pad_length - $str_length);
6756
6757 6
                    $post = (string) self::substr(
6758 6
                        \str_repeat($pad_string, (int) \ceil($diff / $ps_length)),
6759 6
                        0,
6760 6
                        $diff,
6761 6
                        $encoding
6762
                    );
6763 6
                    $pre = '';
6764
            }
6765
6766 14
            return $pre . $str . $post;
6767
        }
6768
6769 1
        return $str;
6770
    }
6771
6772
    /**
6773
     * Returns a new string of a given length such that both sides of the
6774
     * string are padded. Alias for "UTF8::str_pad()" with a $pad_type of 'both'.
6775
     *
6776
     * @param string $str
6777
     * @param int    $length   <p>Desired string length after padding.</p>
6778
     * @param string $pad_str  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
6779
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6780
     *
6781
     * @return string
6782
     *                <p>The string with padding applied.</p>
6783
     */
6784 11
    public static function str_pad_both(
6785
        string $str,
6786
        int $length,
6787
        string $pad_str = ' ',
6788
        string $encoding = 'UTF-8'
6789
    ): string {
6790 11
        return self::str_pad(
6791 11
            $str,
6792 11
            $length,
6793 11
            $pad_str,
6794 11
            \STR_PAD_BOTH,
6795 11
            $encoding
6796
        );
6797
    }
6798
6799
    /**
6800
     * Returns a new string of a given length such that the beginning of the
6801
     * string is padded. Alias for "UTF8::str_pad()" with a $pad_type of 'left'.
6802
     *
6803
     * @param string $str
6804
     * @param int    $length   <p>Desired string length after padding.</p>
6805
     * @param string $pad_str  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
6806
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6807
     *
6808
     * @return string
6809
     *                <p>The string with left padding.</p>
6810
     */
6811 7
    public static function str_pad_left(
6812
        string $str,
6813
        int $length,
6814
        string $pad_str = ' ',
6815
        string $encoding = 'UTF-8'
6816
    ): string {
6817 7
        return self::str_pad(
6818 7
            $str,
6819 7
            $length,
6820 7
            $pad_str,
6821 7
            \STR_PAD_LEFT,
6822 7
            $encoding
6823
        );
6824
    }
6825
6826
    /**
6827
     * Returns a new string of a given length such that the end of the string
6828
     * is padded. Alias for "UTF8::str_pad()" with a $pad_type of 'right'.
6829
     *
6830
     * @param string $str
6831
     * @param int    $length   <p>Desired string length after padding.</p>
6832
     * @param string $pad_str  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
6833
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
6834
     *
6835
     * @return string
6836
     *                <p>The string with right padding.</p>
6837
     */
6838 7
    public static function str_pad_right(
6839
        string $str,
6840
        int $length,
6841
        string $pad_str = ' ',
6842
        string $encoding = 'UTF-8'
6843
    ): string {
6844 7
        return self::str_pad(
6845 7
            $str,
6846 7
            $length,
6847 7
            $pad_str,
6848 7
            \STR_PAD_RIGHT,
6849 7
            $encoding
6850
        );
6851
    }
6852
6853
    /**
6854
     * Repeat a string.
6855
     *
6856
     * @param string $str        <p>
6857
     *                           The string to be repeated.
6858
     *                           </p>
6859
     * @param int    $multiplier <p>
6860
     *                           Number of time the input string should be
6861
     *                           repeated.
6862
     *                           </p>
6863
     *                           <p>
6864
     *                           multiplier has to be greater than or equal to 0.
6865
     *                           If the multiplier is set to 0, the function
6866
     *                           will return an empty string.
6867
     *                           </p>
6868
     *
6869
     * @return string
6870
     *                <p>The repeated string.</P>
6871
     */
6872 9
    public static function str_repeat(string $str, int $multiplier): string
6873
    {
6874 9
        $str = self::filter($str);
6875
6876 9
        return \str_repeat($str, $multiplier);
6877
    }
6878
6879
    /**
6880
     * INFO: This is only a wrapper for "str_replace()"  -> the original functions is already UTF-8 safe.
6881
     *
6882
     * Replace all occurrences of the search string with the replacement string
6883
     *
6884
     * @see http://php.net/manual/en/function.str-replace.php
6885
     *
6886
     * @param mixed $search  <p>
6887
     *                       The value being searched for, otherwise known as the needle.
6888
     *                       An array may be used to designate multiple needles.
6889
     *                       </p>
6890
     * @param mixed $replace <p>
6891
     *                       The replacement value that replaces found search
6892
     *                       values. An array may be used to designate multiple replacements.
6893
     *                       </p>
6894
     * @param mixed $subject <p>
6895
     *                       The string or array being searched and replaced on,
6896
     *                       otherwise known as the haystack.
6897
     *                       </p>
6898
     *                       <p>
6899
     *                       If subject is an array, then the search and
6900
     *                       replace is performed with every entry of
6901
     *                       subject, and the return value is an array as
6902
     *                       well.
6903
     *                       </p>
6904
     * @param int   $count   [optional] If passed, this will hold the number of matched and replaced needles
6905
     *
6906
     * @return mixed this function returns a string or an array with the replaced values
6907
     */
6908 12
    public static function str_replace(
6909
        $search,
6910
        $replace,
6911
        $subject,
6912
        int &$count = null
6913
    ) {
6914
        /**
6915
         * @psalm-suppress PossiblyNullArgument
6916
         */
6917 12
        return \str_replace(
6918 12
            $search,
6919 12
            $replace,
6920 12
            $subject,
6921 12
            $count
6922
        );
6923
    }
6924
6925
    /**
6926
     * Replaces $search from the beginning of string with $replacement.
6927
     *
6928
     * @param string $str         <p>The input string.</p>
6929
     * @param string $search      <p>The string to search for.</p>
6930
     * @param string $replacement <p>The replacement.</p>
6931
     *
6932
     * @return string
6933
     *                <p>A string after the replacements.</p>
6934
     */
6935 17
    public static function str_replace_beginning(
6936
        string $str,
6937
        string $search,
6938
        string $replacement
6939
    ): string {
6940 17
        if ($str === '') {
6941 4
            if ($replacement === '') {
6942 2
                return '';
6943
            }
6944
6945 2
            if ($search === '') {
6946 2
                return $replacement;
6947
            }
6948
        }
6949
6950 13
        if ($search === '') {
6951 2
            return $str . $replacement;
6952
        }
6953
6954 11
        if (\strpos($str, $search) === 0) {
6955 9
            return $replacement . \substr($str, \strlen($search));
6956
        }
6957
6958 2
        return $str;
6959
    }
6960
6961
    /**
6962
     * Replaces $search from the ending of string with $replacement.
6963
     *
6964
     * @param string $str         <p>The input string.</p>
6965
     * @param string $search      <p>The string to search for.</p>
6966
     * @param string $replacement <p>The replacement.</p>
6967
     *
6968
     * @return string
6969
     *                <p>A string after the replacements.</p>
6970
     */
6971 17
    public static function str_replace_ending(
6972
        string $str,
6973
        string $search,
6974
        string $replacement
6975
    ): string {
6976 17
        if ($str === '') {
6977 4
            if ($replacement === '') {
6978 2
                return '';
6979
            }
6980
6981 2
            if ($search === '') {
6982 2
                return $replacement;
6983
            }
6984
        }
6985
6986 13
        if ($search === '') {
6987 2
            return $str . $replacement;
6988
        }
6989
6990 11
        if (\strpos($str, $search, \strlen($str) - \strlen($search)) !== false) {
6991 8
            $str = \substr($str, 0, -\strlen($search)) . $replacement;
6992
        }
6993
6994 11
        return $str;
6995
    }
6996
6997
    /**
6998
     * Replace the first "$search"-term with the "$replace"-term.
6999
     *
7000
     * @param string $search
7001
     * @param string $replace
7002
     * @param string $subject
7003
     *
7004
     * @return string
7005
     *
7006
     * @psalm-suppress InvalidReturnType
7007
     */
7008 2
    public static function str_replace_first(
7009
        string $search,
7010
        string $replace,
7011
        string $subject
7012
    ): string {
7013 2
        $pos = self::strpos($subject, $search);
7014
7015 2
        if ($pos !== false) {
7016
            /**
7017
             * @psalm-suppress InvalidReturnStatement
7018
             */
7019 2
            return self::substr_replace(
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::substr_repl...)self::strlen($search)) could return the type string[] which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
7020 2
                $subject,
7021 2
                $replace,
7022 2
                $pos,
7023 2
                (int) self::strlen($search)
7024
            );
7025
        }
7026
7027 2
        return $subject;
7028
    }
7029
7030
    /**
7031
     * Replace the last "$search"-term with the "$replace"-term.
7032
     *
7033
     * @param string $search
7034
     * @param string $replace
7035
     * @param string $subject
7036
     *
7037
     * @return string
7038
     *
7039
     * @psalm-suppress InvalidReturnType
7040
     */
7041 2
    public static function str_replace_last(
7042
        string $search,
7043
        string $replace,
7044
        string $subject
7045
    ): string {
7046 2
        $pos = self::strrpos($subject, $search);
7047 2
        if ($pos !== false) {
7048
            /**
7049
             * @psalm-suppress InvalidReturnStatement
7050
             */
7051 2
            return self::substr_replace(
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::substr_repl...)self::strlen($search)) could return the type string[] which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
7052 2
                $subject,
7053 2
                $replace,
7054 2
                $pos,
7055 2
                (int) self::strlen($search)
7056
            );
7057
        }
7058
7059 2
        return $subject;
7060
    }
7061
7062
    /**
7063
     * Shuffles all the characters in the string.
7064
     *
7065
     * PS: uses random algorithm which is weak for cryptography purposes
7066
     *
7067
     * @param string $str      <p>The input string</p>
7068
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7069
     *
7070
     * @return string
7071
     *                <p>The shuffled string.</p>
7072
     */
7073 5
    public static function str_shuffle(string $str, string $encoding = 'UTF-8'): string
7074
    {
7075 5
        if ($encoding === 'UTF-8') {
7076 5
            $indexes = \range(0, (int) \mb_strlen($str) - 1);
7077
            /** @noinspection NonSecureShuffleUsageInspection */
7078 5
            \shuffle($indexes);
7079
7080
            // init
7081 5
            $shuffled_str = '';
7082
7083 5
            foreach ($indexes as &$i) {
7084 5
                $tmp_sub_str = \mb_substr($str, $i, 1);
7085 5
                if ($tmp_sub_str !== false) {
7086 5
                    $shuffled_str .= $tmp_sub_str;
7087
                }
7088
            }
7089
        } else {
7090
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
7091
7092
            $indexes = \range(0, (int) self::strlen($str, $encoding) - 1);
7093
            /** @noinspection NonSecureShuffleUsageInspection */
7094
            \shuffle($indexes);
7095
7096
            // init
7097
            $shuffled_str = '';
7098
7099
            foreach ($indexes as &$i) {
7100
                $tmp_sub_str = self::substr($str, $i, 1, $encoding);
7101
                if ($tmp_sub_str !== false) {
7102
                    $shuffled_str .= $tmp_sub_str;
7103
                }
7104
            }
7105
        }
7106
7107 5
        return $shuffled_str;
7108
    }
7109
7110
    /**
7111
     * Returns the substring beginning at $start, and up to, but not including
7112
     * the index specified by $end. If $end is omitted, the function extracts
7113
     * the remaining string. If $end is negative, it is computed from the end
7114
     * of the string.
7115
     *
7116
     * @param string $str
7117
     * @param int    $start    <p>Initial index from which to begin extraction.</p>
7118
     * @param int    $end      [optional] <p>Index at which to end extraction. Default: null</p>
7119
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7120
     *
7121
     * @return false|string
7122
     *                      <p>The extracted substring.</p><p>If <i>str</i> is shorter than <i>start</i>
7123
     *                      characters long, <b>FALSE</b> will be returned.
7124
     */
7125 18
    public static function str_slice(
7126
        string $str,
7127
        int $start,
7128
        int $end = null,
7129
        string $encoding = 'UTF-8'
7130
    ) {
7131 18
        if ($encoding === 'UTF-8') {
7132 7
            if ($end === null) {
7133 1
                $length = (int) \mb_strlen($str);
7134 6
            } elseif ($end >= 0 && $end <= $start) {
7135 2
                return '';
7136 4
            } elseif ($end < 0) {
7137 1
                $length = (int) \mb_strlen($str) + $end - $start;
7138
            } else {
7139 3
                $length = $end - $start;
7140
            }
7141
7142 5
            return \mb_substr($str, $start, $length);
7143
        }
7144
7145 11
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
7146
7147 11
        if ($end === null) {
7148 5
            $length = (int) self::strlen($str, $encoding);
7149 6
        } elseif ($end >= 0 && $end <= $start) {
7150 2
            return '';
7151 4
        } elseif ($end < 0) {
7152 1
            $length = (int) self::strlen($str, $encoding) + $end - $start;
7153
        } else {
7154 3
            $length = $end - $start;
7155
        }
7156
7157 9
        return self::substr($str, $start, $length, $encoding);
7158
    }
7159
7160
    /**
7161
     * Convert a string to e.g.: "snake_case"
7162
     *
7163
     * @param string $str
7164
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7165
     *
7166
     * @return string
7167
     *                <p>A string in snake_case.</p>
7168
     */
7169 22
    public static function str_snakeize(string $str, string $encoding = 'UTF-8'): string
7170
    {
7171 22
        if ($str === '') {
7172
            return '';
7173
        }
7174
7175 22
        $str = \str_replace(
7176 22
            '-',
7177 22
            '_',
7178 22
            self::normalize_whitespace($str)
7179
        );
7180
7181 22
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
7182 19
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
7183
        }
7184
7185 22
        $str = (string) \preg_replace_callback(
7186 22
            '/([\\p{N}|\\p{Lu}])/u',
7187
            /**
7188
             * @param string[] $matches
7189
             *
7190
             * @return string
7191
             */
7192
            static function (array $matches) use ($encoding): string {
7193 9
                $match = $matches[1];
7194 9
                $match_int = (int) $match;
7195
7196 9
                if ((string) $match_int === $match) {
7197 4
                    return '_' . $match . '_';
7198
                }
7199
7200 5
                if ($encoding === 'UTF-8') {
7201 5
                    return '_' . \mb_strtolower($match);
7202
                }
7203
7204
                return '_' . self::strtolower($match, $encoding);
7205 22
            },
7206 22
            $str
7207
        );
7208
7209 22
        $str = (string) \preg_replace(
7210
            [
7211 22
                '/\\s+/u',           // convert spaces to "_"
7212
                '/^\\s+|\\s+$/u', // trim leading & trailing spaces
7213
                '/_+/',                 // remove double "_"
7214
            ],
7215
            [
7216 22
                '_',
7217
                '',
7218
                '_',
7219
            ],
7220 22
            $str
7221
        );
7222
7223 22
        return \trim(\trim($str, '_')); // trim leading & trailing "_" + whitespace
7224
    }
7225
7226
    /**
7227
     * Sort all characters according to code points.
7228
     *
7229
     * @param string $str    <p>A UTF-8 string.</p>
7230
     * @param bool   $unique <p>Sort unique. If <strong>true</strong>, repeated characters are ignored.</p>
7231
     * @param bool   $desc   <p>If <strong>true</strong>, will sort characters in reverse code point order.</p>
7232
     *
7233
     * @return string
7234
     *                <p>A string of sorted characters.</p>
7235
     */
7236 2
    public static function str_sort(string $str, bool $unique = false, bool $desc = false): string
7237
    {
7238 2
        $array = self::codepoints($str);
7239
7240 2
        if ($unique) {
7241 2
            $array = \array_flip(\array_flip($array));
7242
        }
7243
7244 2
        if ($desc) {
7245 2
            \arsort($array);
0 ignored issues
show
Bug introduced by
It seems like $array can also be of type null; however, parameter $array of arsort() does only seem to accept array, 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

7245
            \arsort(/** @scrutinizer ignore-type */ $array);
Loading history...
7246
        } else {
7247 2
            \asort($array);
0 ignored issues
show
Bug introduced by
It seems like $array can also be of type null; however, parameter $array of asort() does only seem to accept array, 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

7247
            \asort(/** @scrutinizer ignore-type */ $array);
Loading history...
7248
        }
7249
7250 2
        return self::string($array);
7251
    }
7252
7253
    /**
7254
     * Convert a string to an array of Unicode characters.
7255
     *
7256
     * @param int|int[]|string|string[] $str                     <p>The string to split into array.</p>
7257
     * @param int                       $length                  [optional] <p>Max character length of each array
7258
     *                                                           element.</p>
7259
     * @param bool                      $clean_utf8              [optional] <p>Remove non UTF-8 chars from the string.</p>
7260
     * @param bool                      $try_to_use_mb_functions [optional] <p>Set to false, if you don't want to use
7261
     *                                                           "mb_substr"</p>
7262
     *
7263
     * @return array
7264
     *               <p>An array containing chunks of the input.</p>
7265
     */
7266 89
    public static function str_split(
7267
        $str,
7268
        int $length = 1,
7269
        bool $clean_utf8 = false,
7270
        bool $try_to_use_mb_functions = true
7271
    ): array {
7272 89
        if ($length <= 0) {
7273 3
            return [];
7274
        }
7275
7276 88
        if (\is_array($str) === true) {
7277 2
            foreach ($str as $k => &$v) {
7278 2
                $v = self::str_split(
7279 2
                    $v,
7280 2
                    $length,
7281 2
                    $clean_utf8,
7282 2
                    $try_to_use_mb_functions
7283
                );
7284
            }
7285
7286 2
            return $str;
7287
        }
7288
7289
        // init
7290 88
        $str = (string) $str;
7291
7292 88
        if ($str === '') {
7293 13
            return [];
7294
        }
7295
7296 85
        if ($clean_utf8 === true) {
7297 19
            $str = self::clean($str);
7298
        }
7299
7300
        if (
7301 85
            $try_to_use_mb_functions === true
7302
            &&
7303 85
            self::$SUPPORT['mbstring'] === true
7304
        ) {
7305 81
            if (Bootup::is_php('7.4')) {
7306
                $return = \mb_str_split($str, $length);
7307
                if ($return !== false) {
7308
                    return $return;
7309
                }
7310
            }
7311
7312 81
            $i_max = \mb_strlen($str);
7313 81
            if ($i_max <= 127) {
7314 75
                $ret = [];
7315 75
                for ($i = 0; $i < $i_max; ++$i) {
7316 75
                    $ret[] = \mb_substr($str, $i, 1);
7317
                }
7318
            } else {
7319 16
                $return_array = [];
7320 16
                \preg_match_all('/./us', $str, $return_array);
7321 81
                $ret = $return_array[0] ?? [];
7322
            }
7323 23
        } elseif (self::$SUPPORT['pcre_utf8'] === true) {
7324 17
            $return_array = [];
7325 17
            \preg_match_all('/./us', $str, $return_array);
7326 17
            $ret = $return_array[0] ?? [];
7327
        } else {
7328
7329
            // fallback
7330
7331 8
            $ret = [];
7332 8
            $len = \strlen($str);
7333
7334
            /** @noinspection ForeachInvariantsInspection */
7335 8
            for ($i = 0; $i < $len; ++$i) {
7336 8
                if (($str[$i] & "\x80") === "\x00") {
7337 8
                    $ret[] = $str[$i];
7338
                } elseif (
7339 8
                    isset($str[$i + 1])
7340
                    &&
7341 8
                    ($str[$i] & "\xE0") === "\xC0"
7342
                ) {
7343 4
                    if (($str[$i + 1] & "\xC0") === "\x80") {
7344 4
                        $ret[] = $str[$i] . $str[$i + 1];
7345
7346 4
                        ++$i;
7347
                    }
7348
                } elseif (
7349 6
                    isset($str[$i + 2])
7350
                    &&
7351 6
                    ($str[$i] & "\xF0") === "\xE0"
7352
                ) {
7353
                    if (
7354 6
                        ($str[$i + 1] & "\xC0") === "\x80"
7355
                        &&
7356 6
                        ($str[$i + 2] & "\xC0") === "\x80"
7357
                    ) {
7358 6
                        $ret[] = $str[$i] . $str[$i + 1] . $str[$i + 2];
7359
7360 6
                        $i += 2;
7361
                    }
7362
                } elseif (
7363
                    isset($str[$i + 3])
7364
                    &&
7365
                    ($str[$i] & "\xF8") === "\xF0"
7366
                ) {
7367
                    if (
7368
                        ($str[$i + 1] & "\xC0") === "\x80"
7369
                        &&
7370
                        ($str[$i + 2] & "\xC0") === "\x80"
7371
                        &&
7372
                        ($str[$i + 3] & "\xC0") === "\x80"
7373
                    ) {
7374
                        $ret[] = $str[$i] . $str[$i + 1] . $str[$i + 2] . $str[$i + 3];
7375
7376
                        $i += 3;
7377
                    }
7378
                }
7379
            }
7380
        }
7381
7382 85
        if ($length > 1) {
7383 11
            $ret = \array_chunk($ret, $length);
7384
7385 11
            return \array_map(
7386
                static function (array &$item): string {
7387 11
                    return \implode('', $item);
7388 11
                },
7389 11
                $ret
7390
            );
7391
        }
7392
7393 78
        if (isset($ret[0]) && $ret[0] === '') {
7394
            return [];
7395
        }
7396
7397 78
        return $ret;
7398
    }
7399
7400
    /**
7401
     * Splits the string with the provided regular expression, returning an
7402
     * array of strings. An optional integer $limit will truncate the
7403
     * results.
7404
     *
7405
     * @param string $str
7406
     * @param string $pattern <p>The regex with which to split the string.</p>
7407
     * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no limit</p>
7408
     *
7409
     * @return string[]
7410
     *                  <p>An array of strings.</p>
7411
     */
7412 16
    public static function str_split_pattern(string $str, string $pattern, int $limit = -1): array
7413
    {
7414 16
        if ($limit === 0) {
7415 2
            return [];
7416
        }
7417
7418 14
        if ($pattern === '') {
7419 1
            return [$str];
7420
        }
7421
7422 13
        if (self::$SUPPORT['mbstring'] === true) {
7423 13
            if ($limit >= 0) {
7424
                /** @noinspection PhpComposerExtensionStubsInspection */
7425 8
                $result_tmp = \mb_split($pattern, $str);
7426
7427 8
                $result = [];
7428 8
                foreach ($result_tmp as $item_tmp) {
7429 8
                    if ($limit === 0) {
7430 4
                        break;
7431
                    }
7432 8
                    --$limit;
7433
7434 8
                    $result[] = $item_tmp;
7435
                }
7436
7437 8
                return $result;
7438
            }
7439
7440
            /** @noinspection PhpComposerExtensionStubsInspection */
7441 5
            return \mb_split($pattern, $str);
7442
        }
7443
7444
        if ($limit > 0) {
7445
            ++$limit;
7446
        } else {
7447
            $limit = -1;
7448
        }
7449
7450
        $array = \preg_split('/' . \preg_quote($pattern, '/') . '/u', $str, $limit);
7451
7452
        if ($array === false) {
7453
            return [];
7454
        }
7455
7456
        if ($limit > 0 && \count($array) === $limit) {
7457
            \array_pop($array);
7458
        }
7459
7460
        return $array;
7461
    }
7462
7463
    /**
7464
     * Check if the string starts with the given substring.
7465
     *
7466
     * @param string $haystack <p>The string to search in.</p>
7467
     * @param string $needle   <p>The substring to search for.</p>
7468
     *
7469
     * @return bool
7470
     */
7471 19
    public static function str_starts_with(string $haystack, string $needle): bool
7472
    {
7473 19
        if ($needle === '') {
7474 2
            return true;
7475
        }
7476
7477 19
        if ($haystack === '') {
7478
            return false;
7479
        }
7480
7481 19
        return \strpos($haystack, $needle) === 0;
7482
    }
7483
7484
    /**
7485
     * Returns true if the string begins with any of $substrings, false otherwise.
7486
     *
7487
     * - case-sensitive
7488
     *
7489
     * @param string $str        <p>The input string.</p>
7490
     * @param array  $substrings <p>Substrings to look for.</p>
7491
     *
7492
     * @return bool whether or not $str starts with $substring
7493
     */
7494 8
    public static function str_starts_with_any(string $str, array $substrings): bool
7495
    {
7496 8
        if ($str === '') {
7497
            return false;
7498
        }
7499
7500 8
        if ($substrings === []) {
7501
            return false;
7502
        }
7503
7504 8
        foreach ($substrings as &$substring) {
7505 8
            if (self::str_starts_with($str, $substring)) {
7506 8
                return true;
7507
            }
7508
        }
7509
7510 6
        return false;
7511
    }
7512
7513
    /**
7514
     * Gets the substring after the first occurrence of a separator.
7515
     *
7516
     * @param string $str       <p>The input string.</p>
7517
     * @param string $separator <p>The string separator.</p>
7518
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
7519
     *
7520
     * @return string
7521
     */
7522 1
    public static function str_substr_after_first_separator(string $str, string $separator, string $encoding = 'UTF-8'): string
7523
    {
7524 1
        if ($separator === '' || $str === '') {
7525 1
            return '';
7526
        }
7527
7528 1
        if ($encoding === 'UTF-8') {
7529 1
            $offset = \mb_strpos($str, $separator);
7530 1
            if ($offset === false) {
7531 1
                return '';
7532
            }
7533
7534 1
            return (string) \mb_substr(
7535 1
                $str,
7536 1
                $offset + (int) \mb_strlen($separator)
7537
            );
7538
        }
7539
7540
        $offset = self::strpos($str, $separator, 0, $encoding);
7541
        if ($offset === false) {
7542
            return '';
7543
        }
7544
7545
        return (string) \mb_substr(
7546
            $str,
7547
            $offset + (int) self::strlen($separator, $encoding),
7548
            null,
7549
            $encoding
7550
        );
7551
    }
7552
7553
    /**
7554
     * Gets the substring after the last occurrence of a separator.
7555
     *
7556
     * @param string $str       <p>The input string.</p>
7557
     * @param string $separator <p>The string separator.</p>
7558
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
7559
     *
7560
     * @return string
7561
     */
7562 1
    public static function str_substr_after_last_separator(string $str, string $separator, string $encoding = 'UTF-8'): string
7563
    {
7564 1
        if ($separator === '' || $str === '') {
7565 1
            return '';
7566
        }
7567
7568 1
        if ($encoding === 'UTF-8') {
7569 1
            $offset = \mb_strrpos($str, $separator);
7570 1
            if ($offset === false) {
7571 1
                return '';
7572
            }
7573
7574 1
            return (string) \mb_substr(
7575 1
                $str,
7576 1
                $offset + (int) \mb_strlen($separator)
7577
            );
7578
        }
7579
7580
        $offset = self::strrpos($str, $separator, 0, $encoding);
7581
        if ($offset === false) {
7582
            return '';
7583
        }
7584
7585
        return (string) self::substr(
7586
            $str,
7587
            $offset + (int) self::strlen($separator, $encoding),
7588
            null,
7589
            $encoding
7590
        );
7591
    }
7592
7593
    /**
7594
     * Gets the substring before the first occurrence of a separator.
7595
     *
7596
     * @param string $str       <p>The input string.</p>
7597
     * @param string $separator <p>The string separator.</p>
7598
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
7599
     *
7600
     * @return string
7601
     */
7602 1
    public static function str_substr_before_first_separator(
7603
        string $str,
7604
        string $separator,
7605
        string $encoding = 'UTF-8'
7606
    ): string {
7607 1
        if ($separator === '' || $str === '') {
7608 1
            return '';
7609
        }
7610
7611 1
        if ($encoding === 'UTF-8') {
7612 1
            $offset = \mb_strpos($str, $separator);
7613 1
            if ($offset === false) {
7614 1
                return '';
7615
            }
7616
7617 1
            return (string) \mb_substr(
7618 1
                $str,
7619 1
                0,
7620 1
                $offset
7621
            );
7622
        }
7623
7624
        $offset = self::strpos($str, $separator, 0, $encoding);
7625
        if ($offset === false) {
7626
            return '';
7627
        }
7628
7629
        return (string) self::substr(
7630
            $str,
7631
            0,
7632
            $offset,
7633
            $encoding
7634
        );
7635
    }
7636
7637
    /**
7638
     * Gets the substring before the last occurrence of a separator.
7639
     *
7640
     * @param string $str       <p>The input string.</p>
7641
     * @param string $separator <p>The string separator.</p>
7642
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
7643
     *
7644
     * @return string
7645
     */
7646 1
    public static function str_substr_before_last_separator(string $str, string $separator, string $encoding = 'UTF-8'): string
7647
    {
7648 1
        if ($separator === '' || $str === '') {
7649 1
            return '';
7650
        }
7651
7652 1
        if ($encoding === 'UTF-8') {
7653 1
            $offset = \mb_strrpos($str, $separator);
7654 1
            if ($offset === false) {
7655 1
                return '';
7656
            }
7657
7658 1
            return (string) \mb_substr(
7659 1
                $str,
7660 1
                0,
7661 1
                $offset
7662
            );
7663
        }
7664
7665
        $offset = self::strrpos($str, $separator, 0, $encoding);
7666
        if ($offset === false) {
7667
            return '';
7668
        }
7669
7670
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
7671
7672
        return (string) self::substr(
7673
            $str,
7674
            0,
7675
            $offset,
7676
            $encoding
7677
        );
7678
    }
7679
7680
    /**
7681
     * Gets the substring after (or before via "$before_needle") the first occurrence of the "$needle".
7682
     *
7683
     * @param string $str           <p>The input string.</p>
7684
     * @param string $needle        <p>The string to look for.</p>
7685
     * @param bool   $before_needle [optional] <p>Default: false</p>
7686
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
7687
     *
7688
     * @return string
7689
     */
7690 2
    public static function str_substr_first(
7691
        string $str,
7692
        string $needle,
7693
        bool $before_needle = false,
7694
        string $encoding = 'UTF-8'
7695
    ): string {
7696 2
        if ($str === '' || $needle === '') {
7697 2
            return '';
7698
        }
7699
7700 2
        if ($encoding === 'UTF-8') {
7701 2
            if ($before_needle === true) {
7702 1
                $part = \mb_strstr(
7703 1
                    $str,
7704 1
                    $needle,
7705 1
                    $before_needle
7706
                );
7707
            } else {
7708 1
                $part = \mb_strstr(
7709 1
                    $str,
7710 2
                    $needle
7711
                );
7712
            }
7713
        } else {
7714
            $part = self::strstr(
7715
                $str,
7716
                $needle,
7717
                $before_needle,
7718
                $encoding
7719
            );
7720
        }
7721
7722 2
        return $part === false ? '' : $part;
7723
    }
7724
7725
    /**
7726
     * Gets the substring after (or before via "$before_needle") the last occurrence of the "$needle".
7727
     *
7728
     * @param string $str           <p>The input string.</p>
7729
     * @param string $needle        <p>The string to look for.</p>
7730
     * @param bool   $before_needle [optional] <p>Default: false</p>
7731
     * @param string $encoding      [optional] <p>Default: 'UTF-8'</p>
7732
     *
7733
     * @return string
7734
     */
7735 2
    public static function str_substr_last(
7736
        string $str,
7737
        string $needle,
7738
        bool $before_needle = false,
7739
        string $encoding = 'UTF-8'
7740
    ): string {
7741 2
        if ($str === '' || $needle === '') {
7742 2
            return '';
7743
        }
7744
7745 2
        if ($encoding === 'UTF-8') {
7746 2
            if ($before_needle === true) {
7747 1
                $part = \mb_strrchr(
7748 1
                    $str,
7749 1
                    $needle,
7750 1
                    $before_needle
7751
                );
7752
            } else {
7753 1
                $part = \mb_strrchr(
7754 1
                    $str,
7755 2
                    $needle
7756
                );
7757
            }
7758
        } else {
7759
            $part = self::strrchr(
7760
                $str,
7761
                $needle,
7762
                $before_needle,
7763
                $encoding
7764
            );
7765
        }
7766
7767 2
        return $part === false ? '' : $part;
7768
    }
7769
7770
    /**
7771
     * Surrounds $str with the given substring.
7772
     *
7773
     * @param string $str
7774
     * @param string $substring <p>The substring to add to both sides.</P>
7775
     *
7776
     * @return string
7777
     *                <p>A string with the substring both prepended and appended.</p>
7778
     */
7779 5
    public static function str_surround(string $str, string $substring): string
7780
    {
7781 5
        return $substring . $str . $substring;
7782
    }
7783
7784
    /**
7785
     * Returns a trimmed string with the first letter of each word capitalized.
7786
     * Also accepts an array, $ignore, allowing you to list words not to be
7787
     * capitalized.
7788
     *
7789
     * @param string              $str
7790
     * @param array|string[]|null $ignore                        [optional] <p>An array of words not to capitalize or null.
7791
     *                                                           Default: null</p>
7792
     * @param string              $encoding                      [optional] <p>Default: 'UTF-8'</p>
7793
     * @param bool                $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
7794
     * @param string|null         $lang                          [optional] <p>Set the language for special cases: az, el, lt,
7795
     *                                                           tr</p>
7796
     * @param bool                $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ ->
7797
     *                                                           ß</p>
7798
     * @param bool                $use_trim_first                [optional] <p>true === trim the input string, first</p>
7799
     * @param string|null         $word_define_chars             [optional] <p>An string of chars that will be used as whitespace separator === words.</p>
7800
     *
7801
     * @return string
7802
     *                <p>The titleized string.</p>
7803
     */
7804 10
    public static function str_titleize(
7805
        string $str,
7806
        array $ignore = null,
7807
        string $encoding = 'UTF-8',
7808
        bool $clean_utf8 = false,
7809
        string $lang = null,
7810
        bool $try_to_keep_the_string_length = false,
7811
        bool $use_trim_first = true,
7812
        string $word_define_chars = null
7813
    ): string {
7814 10
        if ($str === '') {
7815
            return '';
7816
        }
7817
7818 10
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
7819 9
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
7820
        }
7821
7822 10
        if ($use_trim_first === true) {
7823 10
            $str = \trim($str);
7824
        }
7825
7826 10
        if ($clean_utf8 === true) {
7827
            $str = self::clean($str);
7828
        }
7829
7830 10
        $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
7831
7832 10
        if ($word_define_chars) {
7833 4
            $word_define_chars = \preg_quote($word_define_chars, '/');
7834
        } else {
7835 6
            $word_define_chars = '';
7836
        }
7837
7838 10
        $str = (string) \preg_replace_callback(
7839 10
            '/([^\\s' . $word_define_chars . ']+)/u',
7840
            static function (array $match) use ($try_to_keep_the_string_length, $lang, $ignore, $use_mb_functions, $encoding): string {
7841 10
                if ($ignore !== null && \in_array($match[0], $ignore, true)) {
7842 4
                    return $match[0];
7843
                }
7844
7845 10
                if ($use_mb_functions === true) {
7846 10
                    if ($encoding === 'UTF-8') {
7847 10
                        return \mb_strtoupper(\mb_substr($match[0], 0, 1))
7848 10
                               . \mb_strtolower(\mb_substr($match[0], 1));
7849
                    }
7850
7851
                    return \mb_strtoupper(\mb_substr($match[0], 0, 1, $encoding), $encoding)
7852
                           . \mb_strtolower(\mb_substr($match[0], 1, null, $encoding), $encoding);
7853
                }
7854
7855
                return self::ucfirst(
7856
                    self::strtolower(
7857
                        $match[0],
7858
                        $encoding,
7859
                        false,
7860
                        $lang,
7861
                        $try_to_keep_the_string_length
7862
                    ),
7863
                    $encoding,
7864
                    false,
7865
                    $lang,
7866
                    $try_to_keep_the_string_length
7867
                );
7868 10
            },
7869 10
            $str
7870
        );
7871
7872 10
        return $str;
7873
    }
7874
7875
    /**
7876
     * Returns a trimmed string in proper title case.
7877
     *
7878
     * Also accepts an array, $ignore, allowing you to list words not to be
7879
     * capitalized.
7880
     *
7881
     * Adapted from John Gruber's script.
7882
     *
7883
     * @see https://gist.github.com/gruber/9f9e8650d68b13ce4d78
7884
     *
7885
     * @param string $str
7886
     * @param array  $ignore   <p>An array of words not to capitalize.</p>
7887
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
7888
     *
7889
     * @return string
7890
     *                <p>The titleized string.</p>
7891
     */
7892 35
    public static function str_titleize_for_humans(
7893
        string $str,
7894
        array $ignore = [],
7895
        string $encoding = 'UTF-8'
7896
    ): string {
7897 35
        if ($str === '') {
7898
            return '';
7899
        }
7900
7901
        $small_words = [
7902 35
            '(?<!q&)a',
7903
            'an',
7904
            'and',
7905
            'as',
7906
            'at(?!&t)',
7907
            'but',
7908
            'by',
7909
            'en',
7910
            'for',
7911
            'if',
7912
            'in',
7913
            'of',
7914
            'on',
7915
            'or',
7916
            'the',
7917
            'to',
7918
            'v[.]?',
7919
            'via',
7920
            'vs[.]?',
7921
        ];
7922
7923 35
        if ($ignore !== []) {
7924 1
            $small_words = \array_merge($small_words, $ignore);
7925
        }
7926
7927 35
        $small_words_rx = \implode('|', $small_words);
7928 35
        $apostrophe_rx = '(?x: [\'’] [[:lower:]]* )?';
7929
7930 35
        $str = \trim($str);
7931
7932 35
        if (self::has_lowercase($str) === false) {
7933 2
            $str = self::strtolower($str, $encoding);
7934
        }
7935
7936
        // the main substitutions
7937 35
        $str = (string) \preg_replace_callback(
7938
            '~\\b (_*) (?:                                                         # 1. Leading underscore and
7939
                        ( (?<=[ ][/\\\\]) [[:alpha:]]+ [-_[:alpha:]/\\\\]+ |              # 2. file path or 
7940 35
                          [-_[:alpha:]]+ [@.:] [-_[:alpha:]@.:/]+ ' . $apostrophe_rx . ' ) #    URL, domain, or email
7941
                        |
7942 35
                        ( (?i: ' . $small_words_rx . ' ) ' . $apostrophe_rx . ' )            # 3. or small word (case-insensitive)
7943
                        |
7944 35
                        ( [[:alpha:]] [[:lower:]\'’()\[\]{}]* ' . $apostrophe_rx . ' )     # 4. or word w/o internal caps
7945
                        |
7946 35
                        ( [[:alpha:]] [[:alpha:]\'’()\[\]{}]* ' . $apostrophe_rx . ' )     # 5. or some other word
7947
                      ) (_*) \\b                                                          # 6. With trailing underscore
7948
                    ~ux',
7949
            /**
7950
             * @param string[] $matches
7951
             *
7952
             * @return string
7953
             */
7954
            static function (array $matches) use ($encoding): string {
7955
                // preserve leading underscore
7956 35
                $str = $matches[1];
7957 35
                if ($matches[2]) {
7958
                    // preserve URLs, domains, emails and file paths
7959 5
                    $str .= $matches[2];
7960 35
                } elseif ($matches[3]) {
7961
                    // lower-case small words
7962 25
                    $str .= self::strtolower($matches[3], $encoding);
7963 35
                } elseif ($matches[4]) {
7964
                    // capitalize word w/o internal caps
7965 34
                    $str .= static::ucfirst($matches[4], $encoding);
7966
                } else {
7967
                    // preserve other kinds of word (iPhone)
7968 7
                    $str .= $matches[5];
7969
                }
7970
                // preserve trailing underscore
7971 35
                $str .= $matches[6];
7972
7973 35
                return $str;
7974 35
            },
7975 35
            $str
7976
        );
7977
7978
        // Exceptions for small words: capitalize at start of title...
7979 35
        $str = (string) \preg_replace_callback(
7980
            '~(  \\A [[:punct:]]*            # start of title...
7981
                      |  [:.;?!][ ]+                # or of subsentence...
7982
                      |  [ ][\'"“‘(\[][ ]* )        # or of inserted subphrase...
7983 35
                      ( ' . $small_words_rx . ' ) \\b # ...followed by small word
7984
                     ~uxi',
7985
            /**
7986
             * @param string[] $matches
7987
             *
7988
             * @return string
7989
             */
7990
            static function (array $matches) use ($encoding): string {
7991 11
                return $matches[1] . static::ucfirst($matches[2], $encoding);
7992 35
            },
7993 35
            $str
7994
        );
7995
7996
        // ...and end of title
7997 35
        $str = (string) \preg_replace_callback(
7998 35
            '~\\b ( ' . $small_words_rx . ' ) # small word...
7999
                      (?= [[:punct:]]* \Z          # ...at the end of the title...
8000
                      |   [\'"’”)\]] [ ] )         # ...or of an inserted subphrase?
8001
                     ~uxi',
8002
            /**
8003
             * @param string[] $matches
8004
             *
8005
             * @return string
8006
             */
8007
            static function (array $matches) use ($encoding): string {
8008 3
                return static::ucfirst($matches[1], $encoding);
8009 35
            },
8010 35
            $str
8011
        );
8012
8013
        // Exceptions for small words in hyphenated compound words.
8014
        // e.g. "in-flight" -> In-Flight
8015 35
        $str = (string) \preg_replace_callback(
8016
            '~\\b
8017
                        (?<! -)                   # Negative lookbehind for a hyphen; we do not want to match man-in-the-middle but do want (in-flight)
8018 35
                        ( ' . $small_words_rx . ' )
8019
                        (?= -[[:alpha:]]+)        # lookahead for "-someword"
8020
                       ~uxi',
8021
            /**
8022
             * @param string[] $matches
8023
             *
8024
             * @return string
8025
             */
8026
            static function (array $matches) use ($encoding): string {
8027
                return static::ucfirst($matches[1], $encoding);
8028 35
            },
8029 35
            $str
8030
        );
8031
8032
        // e.g. "Stand-in" -> "Stand-In" (Stand is already capped at this point)
8033 35
        $str = (string) \preg_replace_callback(
8034
            '~\\b
8035
                      (?<!…)                    # Negative lookbehind for a hyphen; we do not want to match man-in-the-middle but do want (stand-in)
8036
                      ( [[:alpha:]]+- )         # $1 = first word and hyphen, should already be properly capped
8037 35
                      ( ' . $small_words_rx . ' ) # ...followed by small word
8038
                      (?!	- )                 # Negative lookahead for another -
8039
                     ~uxi',
8040
            /**
8041
             * @param string[] $matches
8042
             *
8043
             * @return string
8044
             */
8045
            static function (array $matches) use ($encoding): string {
8046
                return $matches[1] . static::ucfirst($matches[2], $encoding);
8047 35
            },
8048 35
            $str
8049
        );
8050
8051 35
        return $str;
8052
    }
8053
8054
    /**
8055
     * Get a binary representation of a specific string.
8056
     *
8057
     * @param string $str <p>The input string.</p>
8058
     *
8059
     * @return false|string
8060
     *                      <p>false on error</p>
8061
     */
8062 2
    public static function str_to_binary(string $str)
8063
    {
8064
        /** @var array|false $value - needed for PhpStan (stubs error) */
8065 2
        $value = \unpack('H*', $str);
8066 2
        if ($value === false) {
8067
            return false;
8068
        }
8069
8070
        /** @noinspection OffsetOperationsInspection */
8071 2
        return \base_convert($value[1], 16, 2);
8072
    }
8073
8074
    /**
8075
     * @param string   $str
8076
     * @param bool     $remove_empty_values <p>Remove empty values.</p>
8077
     * @param int|null $remove_short_values <p>The min. string length or null to disable</p>
8078
     *
8079
     * @return string[]
8080
     */
8081 17
    public static function str_to_lines(string $str, bool $remove_empty_values = false, int $remove_short_values = null): array
8082
    {
8083 17
        if ($str === '') {
8084 1
            return $remove_empty_values === true ? [] : [''];
8085
        }
8086
8087 16
        if (self::$SUPPORT['mbstring'] === true) {
8088
            /** @noinspection PhpComposerExtensionStubsInspection */
8089 16
            $return = \mb_split("[\r\n]{1,2}", $str);
8090
        } else {
8091
            $return = \preg_split("/[\r\n]{1,2}/u", $str);
8092
        }
8093
8094 16
        if ($return === false) {
8095
            return $remove_empty_values === true ? [] : [''];
8096
        }
8097
8098
        if (
8099 16
            $remove_short_values === null
8100
            &&
8101 16
            $remove_empty_values === false
8102
        ) {
8103 16
            return $return;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $return returns an array which contains values of type array which are incompatible with the documented value type string.
Loading history...
8104
        }
8105
8106
        return self::reduce_string_array(
8107
            $return,
8108
            $remove_empty_values,
8109
            $remove_short_values
8110
        );
8111
    }
8112
8113
    /**
8114
     * Convert a string into an array of words.
8115
     *
8116
     * @param string   $str
8117
     * @param string   $char_list           <p>Additional chars for the definition of "words".</p>
8118
     * @param bool     $remove_empty_values <p>Remove empty values.</p>
8119
     * @param int|null $remove_short_values <p>The min. string length or null to disable</p>
8120
     *
8121
     * @return string[]
8122
     */
8123 13
    public static function str_to_words(
8124
        string $str,
8125
        string $char_list = '',
8126
        bool $remove_empty_values = false,
8127
        int $remove_short_values = null
8128
    ): array {
8129 13
        if ($str === '') {
8130 4
            return $remove_empty_values === true ? [] : [''];
8131
        }
8132
8133 13
        $char_list = self::rxClass($char_list, '\pL');
8134
8135 13
        $return = \preg_split("/({$char_list}+(?:[\p{Pd}’']{$char_list}+)*)/u", $str, -1, \PREG_SPLIT_DELIM_CAPTURE);
8136 13
        if ($return === false) {
8137
            return $remove_empty_values === true ? [] : [''];
8138
        }
8139
8140
        if (
8141 13
            $remove_short_values === null
8142
            &&
8143 13
            $remove_empty_values === false
8144
        ) {
8145 13
            return $return;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $return returns an array which contains values of type array which are incompatible with the documented value type string.
Loading history...
8146
        }
8147
8148 2
        $tmp_return = self::reduce_string_array(
8149 2
            $return,
8150 2
            $remove_empty_values,
8151 2
            $remove_short_values
8152
        );
8153
8154 2
        foreach ($tmp_return as &$item) {
8155 2
            $item = (string) $item;
8156
        }
8157
8158 2
        return $tmp_return;
8159
    }
8160
8161
    /**
8162
     * alias for "UTF8::to_ascii()"
8163
     *
8164
     * @param string $str
8165
     * @param string $unknown
8166
     * @param bool   $strict
8167
     *
8168
     * @return string
8169
     *
8170
     * @see UTF8::to_ascii()
8171
     * @deprecated <p>please use "UTF8::to_ascii()"</p>
8172
     */
8173 7
    public static function str_transliterate(
8174
        string $str,
8175
        string $unknown = '?',
8176
        bool $strict = false
8177
    ): string {
8178 7
        return self::to_ascii($str, $unknown, $strict);
8179
    }
8180
8181
    /**
8182
     * Truncates the string to a given length. If $substring is provided, and
8183
     * truncating occurs, the string is further truncated so that the substring
8184
     * may be appended without exceeding the desired length.
8185
     *
8186
     * @param string $str
8187
     * @param int    $length    <p>Desired length of the truncated string.</p>
8188
     * @param string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
8189
     * @param string $encoding  [optional] <p>Default: 'UTF-8'</p>
8190
     *
8191
     * @return string
8192
     *                <p>A string after truncating.</p>
8193
     */
8194 22
    public static function str_truncate(
8195
        string $str,
8196
        int $length,
8197
        string $substring = '',
8198
        string $encoding = 'UTF-8'
8199
    ): string {
8200 22
        if ($str === '') {
8201
            return '';
8202
        }
8203
8204 22
        if ($encoding === 'UTF-8') {
8205 10
            if ($length >= (int) \mb_strlen($str)) {
8206 2
                return $str;
8207
            }
8208
8209 8
            if ($substring !== '') {
8210 4
                $length -= (int) \mb_strlen($substring);
8211
8212
                /** @noinspection UnnecessaryCastingInspection */
8213 4
                return (string) \mb_substr($str, 0, $length) . $substring;
8214
            }
8215
8216
            /** @noinspection UnnecessaryCastingInspection */
8217 4
            return (string) \mb_substr($str, 0, $length);
8218
        }
8219
8220 12
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
8221
8222 12
        if ($length >= (int) self::strlen($str, $encoding)) {
8223 2
            return $str;
8224
        }
8225
8226 10
        if ($substring !== '') {
8227 6
            $length -= (int) self::strlen($substring, $encoding);
8228
        }
8229
8230
        return (
8231 10
               (string) self::substr(
8232 10
                   $str,
8233 10
                   0,
8234 10
                   $length,
8235 10
                   $encoding
8236
               )
8237 10
               ) . $substring;
8238
    }
8239
8240
    /**
8241
     * Truncates the string to a given length, while ensuring that it does not
8242
     * split words. If $substring is provided, and truncating occurs, the
8243
     * string is further truncated so that the substring may be appended without
8244
     * exceeding the desired length.
8245
     *
8246
     * @param string $str
8247
     * @param int    $length                                 <p>Desired length of the truncated string.</p>
8248
     * @param string $substring                              [optional] <p>The substring to append if it can fit. Default:
8249
     *                                                       ''</p>
8250
     * @param string $encoding                               [optional] <p>Default: 'UTF-8'</p>
8251
     * @param bool   $ignore_do_not_split_words_for_one_word [optional] <p>Default: false</p>
8252
     *
8253
     * @return string
8254
     *                <p>A string after truncating.</p>
8255
     */
8256 47
    public static function str_truncate_safe(
8257
        string $str,
8258
        int $length,
8259
        string $substring = '',
8260
        string $encoding = 'UTF-8',
8261
        bool $ignore_do_not_split_words_for_one_word = false
8262
    ): string {
8263 47
        if ($str === '' || $length <= 0) {
8264 1
            return $substring;
8265
        }
8266
8267 47
        if ($encoding === 'UTF-8') {
8268 21
            if ($length >= (int) \mb_strlen($str)) {
8269 5
                return $str;
8270
            }
8271
8272
            // need to further trim the string so we can append the substring
8273 17
            $length -= (int) \mb_strlen($substring);
8274 17
            if ($length <= 0) {
8275 1
                return $substring;
8276
            }
8277
8278
            /** @var false|string $truncated - needed for PhpStan (stubs error) */
8279 17
            $truncated = \mb_substr($str, 0, $length);
8280 17
            if ($truncated === false) {
8281
                return '';
8282
            }
8283
8284
            // if the last word was truncated
8285 17
            $space_position = \mb_strpos($str, ' ', $length - 1);
8286 17
            if ($space_position !== $length) {
8287
                // find pos of the last occurrence of a space, get up to that
8288 13
                $last_position = \mb_strrpos($truncated, ' ', 0);
8289
8290
                if (
8291 13
                    $last_position !== false
8292
                    ||
8293 13
                    ($space_position !== false && $ignore_do_not_split_words_for_one_word === false)
8294
                ) {
8295 17
                    $truncated = (string) \mb_substr($truncated, 0, (int) $last_position);
8296
                }
8297
            }
8298
        } else {
8299 26
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8300
8301 26
            if ($length >= (int) self::strlen($str, $encoding)) {
8302 4
                return $str;
8303
            }
8304
8305
            // need to further trim the string so we can append the substring
8306 22
            $length -= (int) self::strlen($substring, $encoding);
8307 22
            if ($length <= 0) {
8308
                return $substring;
8309
            }
8310
8311 22
            $truncated = self::substr($str, 0, $length, $encoding);
8312
8313 22
            if ($truncated === false) {
8314
                return '';
8315
            }
8316
8317
            // if the last word was truncated
8318 22
            $space_position = self::strpos($str, ' ', $length - 1, $encoding);
8319 22
            if ($space_position !== $length) {
8320
                // find pos of the last occurrence of a space, get up to that
8321 12
                $last_position = self::strrpos($truncated, ' ', 0, $encoding);
8322
8323
                if (
8324 12
                    $last_position !== false
8325
                    ||
8326 12
                    ($space_position !== false && $ignore_do_not_split_words_for_one_word === false)
8327
                ) {
8328 9
                    $truncated = (string) self::substr($truncated, 0, (int) $last_position, $encoding);
8329
                }
8330
            }
8331
        }
8332
8333 39
        return $truncated . $substring;
8334
    }
8335
8336
    /**
8337
     * Returns a lowercase and trimmed string separated by underscores.
8338
     * Underscores are inserted before uppercase characters (with the exception
8339
     * of the first character of the string), and in place of spaces as well as
8340
     * dashes.
8341
     *
8342
     * @param string $str
8343
     *
8344
     * @return string
8345
     *                <p>The underscored string.</p>
8346
     */
8347 16
    public static function str_underscored(string $str): string
8348
    {
8349 16
        return self::str_delimit($str, '_');
8350
    }
8351
8352
    /**
8353
     * Returns an UpperCamelCase version of the supplied string. It trims
8354
     * surrounding spaces, capitalizes letters following digits, spaces, dashes
8355
     * and underscores, and removes spaces, dashes, underscores.
8356
     *
8357
     * @param string      $str                           <p>The input string.</p>
8358
     * @param string      $encoding                      [optional] <p>Default: 'UTF-8'</p>
8359
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
8360
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
8361
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
8362
     *
8363
     * @return string
8364
     *                <p>A string in UpperCamelCase.</p>
8365
     */
8366 13
    public static function str_upper_camelize(
8367
        string $str,
8368
        string $encoding = 'UTF-8',
8369
        bool $clean_utf8 = false,
8370
        string $lang = null,
8371
        bool $try_to_keep_the_string_length = false
8372
    ): string {
8373 13
        return self::ucfirst(self::str_camelize($str, $encoding), $encoding, $clean_utf8, $lang, $try_to_keep_the_string_length);
8374
    }
8375
8376
    /**
8377
     * alias for "UTF8::ucfirst()"
8378
     *
8379
     * @param string      $str
8380
     * @param string      $encoding
8381
     * @param bool        $clean_utf8
8382
     * @param string|null $lang
8383
     * @param bool        $try_to_keep_the_string_length
8384
     *
8385
     * @return string
8386
     *
8387
     * @see UTF8::ucfirst()
8388
     * @deprecated <p>please use "UTF8::ucfirst()"</p>
8389
     */
8390 5
    public static function str_upper_first(
8391
        string $str,
8392
        string $encoding = 'UTF-8',
8393
        bool $clean_utf8 = false,
8394
        string $lang = null,
8395
        bool $try_to_keep_the_string_length = false
8396
    ): string {
8397 5
        return self::ucfirst(
8398 5
            $str,
8399 5
            $encoding,
8400 5
            $clean_utf8,
8401 5
            $lang,
8402 5
            $try_to_keep_the_string_length
8403
        );
8404
    }
8405
8406
    /**
8407
     * Get the number of words in a specific string.
8408
     *
8409
     * @param string $str       <p>The input string.</p>
8410
     * @param int    $format    [optional] <p>
8411
     *                          <strong>0</strong> => return a number of words (default)<br>
8412
     *                          <strong>1</strong> => return an array of words<br>
8413
     *                          <strong>2</strong> => return an array of words with word-offset as key
8414
     *                          </p>
8415
     * @param string $char_list [optional] <p>Additional chars that contains to words and do not start a new word.</p>
8416
     *
8417
     * @return int|string[] The number of words in the string
8418
     */
8419 2
    public static function str_word_count(string $str, int $format = 0, string $char_list = '')
8420
    {
8421 2
        $str_parts = self::str_to_words($str, $char_list);
8422
8423 2
        $len = \count($str_parts);
8424
8425 2
        if ($format === 1) {
8426 2
            $number_of_words = [];
8427 2
            for ($i = 1; $i < $len; $i += 2) {
8428 2
                $number_of_words[] = $str_parts[$i];
8429
            }
8430 2
        } elseif ($format === 2) {
8431 2
            $number_of_words = [];
8432 2
            $offset = (int) self::strlen($str_parts[0]);
8433 2
            for ($i = 1; $i < $len; $i += 2) {
8434 2
                $number_of_words[$offset] = $str_parts[$i];
8435 2
                $offset += (int) self::strlen($str_parts[$i]) + (int) self::strlen($str_parts[$i + 1]);
8436
            }
8437
        } else {
8438 2
            $number_of_words = (int) (($len - 1) / 2);
8439
        }
8440
8441 2
        return $number_of_words;
8442
    }
8443
8444
    /**
8445
     * Case-insensitive string comparison.
8446
     *
8447
     * INFO: Case-insensitive version of UTF8::strcmp()
8448
     *
8449
     * @param string $str1     <p>The first string.</p>
8450
     * @param string $str2     <p>The second string.</p>
8451
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
8452
     *
8453
     * @return int
8454
     *             <strong>&lt; 0</strong> if str1 is less than str2;<br>
8455
     *             <strong>&gt; 0</strong> if str1 is greater than str2,<br>
8456
     *             <strong>0</strong> if they are equal
8457
     */
8458 23
    public static function strcasecmp(
8459
        string $str1,
8460
        string $str2,
8461
        string $encoding = 'UTF-8'
8462
    ): int {
8463 23
        return self::strcmp(
8464 23
            self::strtocasefold(
8465 23
                $str1,
8466 23
                true,
8467 23
                false,
8468 23
                $encoding,
8469 23
                null,
8470 23
                false
8471
            ),
8472 23
            self::strtocasefold(
8473 23
                $str2,
8474 23
                true,
8475 23
                false,
8476 23
                $encoding,
8477 23
                null,
8478 23
                false
8479
            )
8480
        );
8481
    }
8482
8483
    /**
8484
     * alias for "UTF8::strstr()"
8485
     *
8486
     * @param string $haystack
8487
     * @param string $needle
8488
     * @param bool   $before_needle
8489
     * @param string $encoding
8490
     * @param bool   $clean_utf8
8491
     *
8492
     * @return false|string
8493
     *
8494
     * @see UTF8::strstr()
8495
     * @deprecated <p>please use "UTF8::strstr()"</p>
8496
     */
8497 2
    public static function strchr(
8498
        string $haystack,
8499
        string $needle,
8500
        bool $before_needle = false,
8501
        string $encoding = 'UTF-8',
8502
        bool $clean_utf8 = false
8503
    ) {
8504 2
        return self::strstr(
8505 2
            $haystack,
8506 2
            $needle,
8507 2
            $before_needle,
8508 2
            $encoding,
8509 2
            $clean_utf8
8510
        );
8511
    }
8512
8513
    /**
8514
     * Case-sensitive string comparison.
8515
     *
8516
     * @param string $str1 <p>The first string.</p>
8517
     * @param string $str2 <p>The second string.</p>
8518
     *
8519
     * @return int
8520
     *             <strong>&lt; 0</strong> if str1 is less than str2<br>
8521
     *             <strong>&gt; 0</strong> if str1 is greater than str2<br>
8522
     *             <strong>0</strong> if they are equal
8523
     */
8524 29
    public static function strcmp(string $str1, string $str2): int
8525
    {
8526 29
        if ($str1 === $str2) {
8527 21
            return 0;
8528
        }
8529
8530 24
        return \strcmp(
8531 24
            \Normalizer::normalize($str1, \Normalizer::NFD),
8532 24
            \Normalizer::normalize($str2, \Normalizer::NFD)
8533
        );
8534
    }
8535
8536
    /**
8537
     * Find length of initial segment not matching mask.
8538
     *
8539
     * @param string $str
8540
     * @param string $char_list
8541
     * @param int    $offset
8542
     * @param int    $length
8543
     * @param string $encoding  [optional] <p>Set the charset for e.g. "mb_" function</p>
8544
     *
8545
     * @return int
8546
     */
8547 12
    public static function strcspn(
8548
        string $str,
8549
        string $char_list,
8550
        int $offset = null,
8551
        int $length = null,
8552
        string $encoding = 'UTF-8'
8553
    ): int {
8554 12
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
8555
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8556
        }
8557
8558 12
        if ($char_list === '') {
8559 2
            return (int) self::strlen($str, $encoding);
8560
        }
8561
8562 11
        if ($offset !== null || $length !== null) {
8563 3
            if ($encoding === 'UTF-8') {
8564 3
                if ($length === null) {
8565
                    /** @noinspection UnnecessaryCastingInspection */
8566 2
                    $str_tmp = \mb_substr($str, (int) $offset);
8567
                } else {
8568
                    /** @noinspection UnnecessaryCastingInspection */
8569 3
                    $str_tmp = \mb_substr($str, (int) $offset, $length);
8570
                }
8571
            } else {
8572
                /** @noinspection UnnecessaryCastingInspection */
8573
                $str_tmp = self::substr($str, (int) $offset, $length, $encoding);
8574
            }
8575
8576 3
            if ($str_tmp === false) {
8577
                return 0;
8578
            }
8579
8580
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
8581 3
            $str = $str_tmp;
8582
        }
8583
8584 11
        if ($str === '') {
8585 2
            return 0;
8586
        }
8587
8588 10
        $matches = [];
8589 10
        if (\preg_match('/^(.*?)' . self::rxClass($char_list) . '/us', $str, $matches)) {
8590 9
            $return = self::strlen($matches[1], $encoding);
8591 9
            if ($return === false) {
8592
                return 0;
8593
            }
8594
8595 9
            return $return;
8596
        }
8597
8598 2
        return (int) self::strlen($str, $encoding);
8599
    }
8600
8601
    /**
8602
     * alias for "UTF8::stristr()"
8603
     *
8604
     * @param string $haystack
8605
     * @param string $needle
8606
     * @param bool   $before_needle
8607
     * @param string $encoding
8608
     * @param bool   $clean_utf8
8609
     *
8610
     * @return false|string
8611
     *
8612
     * @see UTF8::stristr()
8613
     * @deprecated <p>please use "UTF8::stristr()"</p>
8614
     */
8615 1
    public static function strichr(
8616
        string $haystack,
8617
        string $needle,
8618
        bool $before_needle = false,
8619
        string $encoding = 'UTF-8',
8620
        bool $clean_utf8 = false
8621
    ) {
8622 1
        return self::stristr(
8623 1
            $haystack,
8624 1
            $needle,
8625 1
            $before_needle,
8626 1
            $encoding,
8627 1
            $clean_utf8
8628
        );
8629
    }
8630
8631
    /**
8632
     * Create a UTF-8 string from code points.
8633
     *
8634
     * INFO: opposite to UTF8::codepoints()
8635
     *
8636
     * @param array $array <p>Integer or Hexadecimal codepoints.</p>
8637
     *
8638
     * @return string
8639
     *                <p>A UTF-8 encoded string.</p>
8640
     */
8641 4
    public static function string(array $array): string
8642
    {
8643 4
        if ($array === []) {
8644 4
            return  '';
8645
        }
8646
8647 4
        $str = '';
8648 4
        foreach ($array as $strPart) {
8649 4
            $str .= '&#' . (int) $strPart . ';';
8650
        }
8651
8652 4
        return self::html_entity_decode($str, \ENT_QUOTES | \ENT_HTML5);
8653
    }
8654
8655
    /**
8656
     * Checks if string starts with "BOM" (Byte Order Mark Character) character.
8657
     *
8658
     * @param string $str <p>The input string.</p>
8659
     *
8660
     * @return bool
8661
     *              <strong>true</strong> if the string has BOM at the start,<br>
8662
     *              <strong>false</strong> otherwise
8663
     */
8664 6
    public static function string_has_bom(string $str): bool
8665
    {
8666
        /** @noinspection PhpUnusedLocalVariableInspection */
8667 6
        foreach (self::$BOM as $bom_string => &$bom_byte_length) {
8668 6
            if (\strpos($str, $bom_string) === 0) {
8669 6
                return true;
8670
            }
8671
        }
8672
8673 6
        return false;
8674
    }
8675
8676
    /**
8677
     * Strip HTML and PHP tags from a string + clean invalid UTF-8.
8678
     *
8679
     * @see http://php.net/manual/en/function.strip-tags.php
8680
     *
8681
     * @param string $str            <p>
8682
     *                               The input string.
8683
     *                               </p>
8684
     * @param string $allowable_tags [optional] <p>
8685
     *                               You can use the optional second parameter to specify tags which should
8686
     *                               not be stripped.
8687
     *                               </p>
8688
     *                               <p>
8689
     *                               HTML comments and PHP tags are also stripped. This is hardcoded and
8690
     *                               can not be changed with allowable_tags.
8691
     *                               </p>
8692
     * @param bool   $clean_utf8     [optional] <p>Remove non UTF-8 chars from the string.</p>
8693
     *
8694
     * @return string
8695
     *                <p>The stripped string.</p>
8696
     */
8697 4
    public static function strip_tags(
8698
        string $str,
8699
        string $allowable_tags = null,
8700
        bool $clean_utf8 = false
8701
    ): string {
8702 4
        if ($str === '') {
8703 1
            return '';
8704
        }
8705
8706 4
        if ($clean_utf8 === true) {
8707 2
            $str = self::clean($str);
8708
        }
8709
8710 4
        if ($allowable_tags === null) {
8711 4
            return \strip_tags($str);
8712
        }
8713
8714 2
        return \strip_tags($str, $allowable_tags);
8715
    }
8716
8717
    /**
8718
     * Strip all whitespace characters. This includes tabs and newline
8719
     * characters, as well as multibyte whitespace such as the thin space
8720
     * and ideographic space.
8721
     *
8722
     * @param string $str
8723
     *
8724
     * @return string
8725
     */
8726 36
    public static function strip_whitespace(string $str): string
8727
    {
8728 36
        if ($str === '') {
8729 3
            return '';
8730
        }
8731
8732 33
        return (string) \preg_replace('/[[:space:]]+/u', '', $str);
8733
    }
8734
8735
    /**
8736
     * Find the position of the first occurrence of a substring in a string, case-insensitive.
8737
     *
8738
     * @see http://php.net/manual/en/function.mb-stripos.php
8739
     *
8740
     * @param string $haystack   <p>The string from which to get the position of the first occurrence of needle.</p>
8741
     * @param string $needle     <p>The string to find in haystack.</p>
8742
     * @param int    $offset     [optional] <p>The position in haystack to start searching.</p>
8743
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
8744
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
8745
     *
8746
     * @return false|int
8747
     *                   Return the <strong>(int)</strong> numeric position of the first occurrence of needle in the
8748
     *                   haystack string,<br> or <strong>false</strong> if needle is not found
8749
     */
8750 24
    public static function stripos(
8751
        string $haystack,
8752
        string $needle,
8753
        int $offset = 0,
8754
        $encoding = 'UTF-8',
8755
        bool $clean_utf8 = false
8756
    ) {
8757 24
        if ($haystack === '' || $needle === '') {
8758 5
            return false;
8759
        }
8760
8761 23
        if ($clean_utf8 === true) {
8762
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
8763
            // if invalid characters are found in $haystack before $needle
8764 1
            $haystack = self::clean($haystack);
8765 1
            $needle = self::clean($needle);
8766
        }
8767
8768 23
        if (self::$SUPPORT['mbstring'] === true) {
8769 23
            if ($encoding === 'UTF-8') {
8770 23
                return \mb_stripos($haystack, $needle, $offset);
8771
            }
8772
8773 3
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8774
8775 3
            return \mb_stripos($haystack, $needle, $offset, $encoding);
8776
        }
8777
8778 2
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
8779
8780
        if (
8781 2
            $encoding === 'UTF-8' // INFO: "grapheme_stripos()" can't handle other encodings
8782
            &&
8783 2
            $offset >= 0 // grapheme_stripos() can't handle negative offset
8784
            &&
8785 2
            self::$SUPPORT['intl'] === true
8786
        ) {
8787
            $return_tmp = \grapheme_stripos($haystack, $needle, $offset);
8788
            if ($return_tmp !== false) {
8789
                return $return_tmp;
8790
            }
8791
        }
8792
8793
        //
8794
        // fallback for ascii only
8795
        //
8796
8797 2
        if (ASCII::is_ascii($haystack . $needle)) {
8798
            return \stripos($haystack, $needle, $offset);
8799
        }
8800
8801
        //
8802
        // fallback via vanilla php
8803
        //
8804
8805 2
        $haystack = self::strtocasefold($haystack, true, false, $encoding, null, false);
8806 2
        $needle = self::strtocasefold($needle, true, false, $encoding, null, false);
8807
8808 2
        return self::strpos($haystack, $needle, $offset, $encoding);
8809
    }
8810
8811
    /**
8812
     * Returns all of haystack starting from and including the first occurrence of needle to the end.
8813
     *
8814
     * @param string $haystack      <p>The input string. Must be valid UTF-8.</p>
8815
     * @param string $needle        <p>The string to look for. Must be valid UTF-8.</p>
8816
     * @param bool   $before_needle [optional] <p>
8817
     *                              If <b>TRUE</b>, it returns the part of the
8818
     *                              haystack before the first occurrence of the needle (excluding the needle).
8819
     *                              </p>
8820
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
8821
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
8822
     *
8823
     * @return false|string
8824
     *                      <p>A sub-string,<br>or <strong>false</strong> if needle is not found.</p>
8825
     */
8826 12
    public static function stristr(
8827
        string $haystack,
8828
        string $needle,
8829
        bool $before_needle = false,
8830
        string $encoding = 'UTF-8',
8831
        bool $clean_utf8 = false
8832
    ) {
8833 12
        if ($haystack === '' || $needle === '') {
8834 3
            return false;
8835
        }
8836
8837 9
        if ($clean_utf8 === true) {
8838
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
8839
            // if invalid characters are found in $haystack before $needle
8840 1
            $needle = self::clean($needle);
8841 1
            $haystack = self::clean($haystack);
8842
        }
8843
8844 9
        if (!$needle) {
8845
            return $haystack;
8846
        }
8847
8848 9
        if (self::$SUPPORT['mbstring'] === true) {
8849 9
            if ($encoding === 'UTF-8') {
8850 9
                return \mb_stristr($haystack, $needle, $before_needle);
8851
            }
8852
8853 1
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8854
8855 1
            return \mb_stristr($haystack, $needle, $before_needle, $encoding);
8856
        }
8857
8858
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
8859
8860
        if (
8861
            $encoding !== 'UTF-8'
8862
            &&
8863
            self::$SUPPORT['mbstring'] === false
8864
        ) {
8865
            \trigger_error('UTF8::stristr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
8866
        }
8867
8868
        if (
8869
            $encoding === 'UTF-8' // INFO: "grapheme_stristr()" can't handle other encodings
8870
            &&
8871
            self::$SUPPORT['intl'] === true
8872
        ) {
8873
            $return_tmp = \grapheme_stristr($haystack, $needle, $before_needle);
8874
            if ($return_tmp !== false) {
8875
                return $return_tmp;
8876
            }
8877
        }
8878
8879
        if (ASCII::is_ascii($needle . $haystack)) {
8880
            return \stristr($haystack, $needle, $before_needle);
8881
        }
8882
8883
        \preg_match('/^(.*?)' . \preg_quote($needle, '/') . '/usi', $haystack, $match);
8884
8885
        if (!isset($match[1])) {
8886
            return false;
8887
        }
8888
8889
        if ($before_needle) {
8890
            return $match[1];
8891
        }
8892
8893
        return self::substr($haystack, (int) self::strlen($match[1], $encoding), null, $encoding);
8894
    }
8895
8896
    /**
8897
     * Get the string length, not the byte-length!
8898
     *
8899
     * @see http://php.net/manual/en/function.mb-strlen.php
8900
     *
8901
     * @param string $str        <p>The string being checked for length.</p>
8902
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
8903
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
8904
     *
8905
     * @return false|int
8906
     *                   <p>
8907
     *                   The number <strong>(int)</strong> of characters in the string $str having character encoding
8908
     *                   $encoding.
8909
     *                   (One multi-byte character counted as +1).
8910
     *                   <br>
8911
     *                   Can return <strong>false</strong>, if e.g. mbstring is not installed and we process invalid
8912
     *                   chars.
8913
     *                   </p>
8914
     */
8915 173
    public static function strlen(
8916
        string $str,
8917
        string $encoding = 'UTF-8',
8918
        bool $clean_utf8 = false
8919
    ) {
8920 173
        if ($str === '') {
8921 21
            return 0;
8922
        }
8923
8924 171
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
8925 12
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
8926
        }
8927
8928 171
        if ($clean_utf8 === true) {
8929
            // "mb_strlen" and "\iconv_strlen" returns wrong length,
8930
            // if invalid characters are found in $str
8931 4
            $str = self::clean($str);
8932
        }
8933
8934
        //
8935
        // fallback via mbstring
8936
        //
8937
8938 171
        if (self::$SUPPORT['mbstring'] === true) {
8939 165
            if ($encoding === 'UTF-8') {
8940 165
                return \mb_strlen($str);
8941
            }
8942
8943 4
            return \mb_strlen($str, $encoding);
8944
        }
8945
8946
        //
8947
        // fallback for binary || ascii only
8948
        //
8949
8950
        if (
8951 8
            $encoding === 'CP850'
8952
            ||
8953 8
            $encoding === 'ASCII'
8954
        ) {
8955
            return \strlen($str);
8956
        }
8957
8958
        if (
8959 8
            $encoding !== 'UTF-8'
8960
            &&
8961 8
            self::$SUPPORT['mbstring'] === false
8962
            &&
8963 8
            self::$SUPPORT['iconv'] === false
8964
        ) {
8965 2
            \trigger_error('UTF8::strlen() without mbstring / iconv cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
8966
        }
8967
8968
        //
8969
        // fallback via iconv
8970
        //
8971
8972 8
        if (self::$SUPPORT['iconv'] === true) {
8973
            $return_tmp = \iconv_strlen($str, $encoding);
8974
            if ($return_tmp !== false) {
8975
                return $return_tmp;
8976
            }
8977
        }
8978
8979
        //
8980
        // fallback via intl
8981
        //
8982
8983
        if (
8984 8
            $encoding === 'UTF-8' // INFO: "grapheme_strlen()" can't handle other encodings
8985
            &&
8986 8
            self::$SUPPORT['intl'] === true
8987
        ) {
8988
            $return_tmp = \grapheme_strlen($str);
8989
            if ($return_tmp !== null) {
8990
                return $return_tmp;
8991
            }
8992
        }
8993
8994
        //
8995
        // fallback for ascii only
8996
        //
8997
8998 8
        if (ASCII::is_ascii($str)) {
8999 4
            return \strlen($str);
9000
        }
9001
9002
        //
9003
        // fallback via vanilla php
9004
        //
9005
9006 8
        \preg_match_all('/./us', $str, $parts);
9007
9008 8
        $return_tmp = \count($parts[0]);
9009 8
        if ($return_tmp === 0) {
9010
            return false;
9011
        }
9012
9013 8
        return $return_tmp;
9014
    }
9015
9016
    /**
9017
     * Get string length in byte.
9018
     *
9019
     * @param string $str
9020
     *
9021
     * @return int
9022
     */
9023
    public static function strlen_in_byte(string $str): int
9024
    {
9025
        if ($str === '') {
9026
            return 0;
9027
        }
9028
9029
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
9030
            // "mb_" is available if overload is used, so use it ...
9031
            return \mb_strlen($str, 'CP850'); // 8-BIT
9032
        }
9033
9034
        return \strlen($str);
9035
    }
9036
9037
    /**
9038
     * Case-insensitive string comparisons using a "natural order" algorithm.
9039
     *
9040
     * INFO: natural order version of UTF8::strcasecmp()
9041
     *
9042
     * @param string $str1     <p>The first string.</p>
9043
     * @param string $str2     <p>The second string.</p>
9044
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9045
     *
9046
     * @return int
9047
     *             <strong>&lt; 0</strong> if str1 is less than str2<br>
9048
     *             <strong>&gt; 0</strong> if str1 is greater than str2<br>
9049
     *             <strong>0</strong> if they are equal
9050
     */
9051 2
    public static function strnatcasecmp(string $str1, string $str2, string $encoding = 'UTF-8'): int
9052
    {
9053 2
        return self::strnatcmp(
9054 2
            self::strtocasefold($str1, true, false, $encoding, null, false),
9055 2
            self::strtocasefold($str2, true, false, $encoding, null, false)
9056
        );
9057
    }
9058
9059
    /**
9060
     * String comparisons using a "natural order" algorithm
9061
     *
9062
     * INFO: natural order version of UTF8::strcmp()
9063
     *
9064
     * @see http://php.net/manual/en/function.strnatcmp.php
9065
     *
9066
     * @param string $str1 <p>The first string.</p>
9067
     * @param string $str2 <p>The second string.</p>
9068
     *
9069
     * @return int
9070
     *             <strong>&lt; 0</strong> if str1 is less than str2;<br>
9071
     *             <strong>&gt; 0</strong> if str1 is greater than str2;<br>
9072
     *             <strong>0</strong> if they are equal
9073
     */
9074 4
    public static function strnatcmp(string $str1, string $str2): int
9075
    {
9076 4
        if ($str1 === $str2) {
9077 4
            return 0;
9078
        }
9079
9080 4
        return \strnatcmp(
9081 4
            (string) self::strtonatfold($str1),
9082 4
            (string) self::strtonatfold($str2)
9083
        );
9084
    }
9085
9086
    /**
9087
     * Case-insensitive string comparison of the first n characters.
9088
     *
9089
     * @see http://php.net/manual/en/function.strncasecmp.php
9090
     *
9091
     * @param string $str1     <p>The first string.</p>
9092
     * @param string $str2     <p>The second string.</p>
9093
     * @param int    $len      <p>The length of strings to be used in the comparison.</p>
9094
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9095
     *
9096
     * @return int
9097
     *             <strong>&lt; 0</strong> if <i>str1</i> is less than <i>str2</i>;<br>
9098
     *             <strong>&gt; 0</strong> if <i>str1</i> is greater than <i>str2</i>;<br>
9099
     *             <strong>0</strong> if they are equal
9100
     */
9101 2
    public static function strncasecmp(
9102
        string $str1,
9103
        string $str2,
9104
        int $len,
9105
        string $encoding = 'UTF-8'
9106
    ): int {
9107 2
        return self::strncmp(
9108 2
            self::strtocasefold($str1, true, false, $encoding, null, false),
9109 2
            self::strtocasefold($str2, true, false, $encoding, null, false),
9110 2
            $len
9111
        );
9112
    }
9113
9114
    /**
9115
     * String comparison of the first n characters.
9116
     *
9117
     * @see http://php.net/manual/en/function.strncmp.php
9118
     *
9119
     * @param string $str1     <p>The first string.</p>
9120
     * @param string $str2     <p>The second string.</p>
9121
     * @param int    $len      <p>Number of characters to use in the comparison.</p>
9122
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9123
     *
9124
     * @return int
9125
     *             <strong>&lt; 0</strong> if <i>str1</i> is less than <i>str2</i>;<br>
9126
     *             <strong>&gt; 0</strong> if <i>str1</i> is greater than <i>str2</i>;<br>
9127
     *             <strong>0</strong> if they are equal
9128
     */
9129 4
    public static function strncmp(
9130
        string $str1,
9131
        string $str2,
9132
        int $len,
9133
        string $encoding = 'UTF-8'
9134
    ): int {
9135 4
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9136
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9137
        }
9138
9139 4
        if ($encoding === 'UTF-8') {
9140 4
            $str1 = (string) \mb_substr($str1, 0, $len);
9141 4
            $str2 = (string) \mb_substr($str2, 0, $len);
9142
        } else {
9143
            $str1 = (string) self::substr($str1, 0, $len, $encoding);
9144
            $str2 = (string) self::substr($str2, 0, $len, $encoding);
9145
        }
9146
9147 4
        return self::strcmp($str1, $str2);
9148
    }
9149
9150
    /**
9151
     * Search a string for any of a set of characters.
9152
     *
9153
     * @see http://php.net/manual/en/function.strpbrk.php
9154
     *
9155
     * @param string $haystack  <p>The string where char_list is looked for.</p>
9156
     * @param string $char_list <p>This parameter is case-sensitive.</p>
9157
     *
9158
     * @return false|string string starting from the character found, or false if it is not found
9159
     */
9160 2
    public static function strpbrk(string $haystack, string $char_list)
9161
    {
9162 2
        if ($haystack === '' || $char_list === '') {
9163 2
            return false;
9164
        }
9165
9166 2
        if (\preg_match('/' . self::rxClass($char_list) . '/us', $haystack, $m)) {
9167 2
            return \substr($haystack, (int) \strpos($haystack, $m[0]));
9168
        }
9169
9170 2
        return false;
9171
    }
9172
9173
    /**
9174
     * Find the position of the first occurrence of a substring in a string.
9175
     *
9176
     * @see http://php.net/manual/en/function.mb-strpos.php
9177
     *
9178
     * @param string     $haystack   <p>The string from which to get the position of the first occurrence of needle.</p>
9179
     * @param int|string $needle     <p>The string to find in haystack.<br>Or a code point as int.</p>
9180
     * @param int        $offset     [optional] <p>The search offset. If it is not specified, 0 is used.</p>
9181
     * @param string     $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
9182
     * @param bool       $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
9183
     *
9184
     * @return false|int
9185
     *                   The <strong>(int)</strong> numeric position of the first occurrence of needle in the haystack
9186
     *                   string.<br> If needle is not found it returns false.
9187
     */
9188 53
    public static function strpos(
9189
        string $haystack,
9190
        $needle,
9191
        int $offset = 0,
9192
        $encoding = 'UTF-8',
9193
        bool $clean_utf8 = false
9194
    ) {
9195 53
        if ($haystack === '') {
9196 4
            return false;
9197
        }
9198
9199
        // iconv and mbstring do not support integer $needle
9200 52
        if ((int) $needle === $needle) {
9201
            $needle = (string) self::chr($needle);
9202
        }
9203 52
        $needle = (string) $needle;
9204
9205 52
        if ($needle === '') {
9206 2
            return false;
9207
        }
9208
9209 52
        if ($clean_utf8 === true) {
9210
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9211
            // if invalid characters are found in $haystack before $needle
9212 3
            $needle = self::clean($needle);
9213 3
            $haystack = self::clean($haystack);
9214
        }
9215
9216 52
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9217 11
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9218
        }
9219
9220
        //
9221
        // fallback via mbstring
9222
        //
9223
9224 52
        if (self::$SUPPORT['mbstring'] === true) {
9225 50
            if ($encoding === 'UTF-8') {
9226 50
                return \mb_strpos($haystack, $needle, $offset);
9227
            }
9228
9229 2
            return \mb_strpos($haystack, $needle, $offset, $encoding);
9230
        }
9231
9232
        //
9233
        // fallback for binary || ascii only
9234
        //
9235
        if (
9236 4
            $encoding === 'CP850'
9237
            ||
9238 4
            $encoding === 'ASCII'
9239
        ) {
9240 2
            return \strpos($haystack, $needle, $offset);
9241
        }
9242
9243
        if (
9244 4
            $encoding !== 'UTF-8'
9245
            &&
9246 4
            self::$SUPPORT['iconv'] === false
9247
            &&
9248 4
            self::$SUPPORT['mbstring'] === false
9249
        ) {
9250 2
            \trigger_error('UTF8::strpos() without mbstring / iconv cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9251
        }
9252
9253
        //
9254
        // fallback via intl
9255
        //
9256
9257
        if (
9258 4
            $encoding === 'UTF-8' // INFO: "grapheme_strpos()" can't handle other encodings
9259
            &&
9260 4
            $offset >= 0 // grapheme_strpos() can't handle negative offset
9261
            &&
9262 4
            self::$SUPPORT['intl'] === true
9263
        ) {
9264
            $return_tmp = \grapheme_strpos($haystack, $needle, $offset);
9265
            if ($return_tmp !== false) {
9266
                return $return_tmp;
9267
            }
9268
        }
9269
9270
        //
9271
        // fallback via iconv
9272
        //
9273
9274
        if (
9275 4
            $offset >= 0 // iconv_strpos() can't handle negative offset
9276
            &&
9277 4
            self::$SUPPORT['iconv'] === true
9278
        ) {
9279
            // ignore invalid negative offset to keep compatibility
9280
            // with php < 5.5.35, < 5.6.21, < 7.0.6
9281
            $return_tmp = \iconv_strpos($haystack, $needle, $offset > 0 ? $offset : 0, $encoding);
9282
            if ($return_tmp !== false) {
9283
                return $return_tmp;
9284
            }
9285
        }
9286
9287
        //
9288
        // fallback for ascii only
9289
        //
9290
9291 4
        if (ASCII::is_ascii($haystack . $needle)) {
9292 2
            return \strpos($haystack, $needle, $offset);
9293
        }
9294
9295
        //
9296
        // fallback via vanilla php
9297
        //
9298
9299 4
        $haystack_tmp = self::substr($haystack, $offset, null, $encoding);
9300 4
        if ($haystack_tmp === false) {
9301
            $haystack_tmp = '';
9302
        }
9303 4
        $haystack = (string) $haystack_tmp;
9304
9305 4
        if ($offset < 0) {
9306
            $offset = 0;
9307
        }
9308
9309 4
        $pos = \strpos($haystack, $needle);
9310 4
        if ($pos === false) {
9311 2
            return false;
9312
        }
9313
9314 4
        if ($pos) {
9315 4
            return $offset + (int) self::strlen(\substr($haystack, 0, $pos), $encoding);
9316
        }
9317
9318 2
        return $offset + 0;
9319
    }
9320
9321
    /**
9322
     * Find the position of the first occurrence of a substring in a string.
9323
     *
9324
     * @param string $haystack <p>
9325
     *                         The string being checked.
9326
     *                         </p>
9327
     * @param string $needle   <p>
9328
     *                         The position counted from the beginning of haystack.
9329
     *                         </p>
9330
     * @param int    $offset   [optional] <p>
9331
     *                         The search offset. If it is not specified, 0 is used.
9332
     *                         </p>
9333
     *
9334
     * @return false|int The numeric position of the first occurrence of needle in the
9335
     *                   haystack string. If needle is not found, it returns false.
9336
     */
9337
    public static function strpos_in_byte(string $haystack, string $needle, int $offset = 0)
9338
    {
9339
        if ($haystack === '' || $needle === '') {
9340
            return false;
9341
        }
9342
9343
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
9344
            // "mb_" is available if overload is used, so use it ...
9345
            return \mb_strpos($haystack, $needle, $offset, 'CP850'); // 8-BIT
9346
        }
9347
9348
        return \strpos($haystack, $needle, $offset);
9349
    }
9350
9351
    /**
9352
     * Find the last occurrence of a character in a string within another.
9353
     *
9354
     * @see http://php.net/manual/en/function.mb-strrchr.php
9355
     *
9356
     * @param string $haystack      <p>The string from which to get the last occurrence of needle.</p>
9357
     * @param string $needle        <p>The string to find in haystack</p>
9358
     * @param bool   $before_needle [optional] <p>
9359
     *                              Determines which portion of haystack
9360
     *                              this function returns.
9361
     *                              If set to true, it returns all of haystack
9362
     *                              from the beginning to the last occurrence of needle.
9363
     *                              If set to false, it returns all of haystack
9364
     *                              from the last occurrence of needle to the end,
9365
     *                              </p>
9366
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
9367
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
9368
     *
9369
     * @return false|string the portion of haystack or false if needle is not found
9370
     */
9371 2
    public static function strrchr(
9372
        string $haystack,
9373
        string $needle,
9374
        bool $before_needle = false,
9375
        string $encoding = 'UTF-8',
9376
        bool $clean_utf8 = false
9377
    ) {
9378 2
        if ($haystack === '' || $needle === '') {
9379 2
            return false;
9380
        }
9381
9382 2
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9383 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9384
        }
9385
9386 2
        if ($clean_utf8 === true) {
9387
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9388
            // if invalid characters are found in $haystack before $needle
9389 2
            $needle = self::clean($needle);
9390 2
            $haystack = self::clean($haystack);
9391
        }
9392
9393
        //
9394
        // fallback via mbstring
9395
        //
9396
9397 2
        if (self::$SUPPORT['mbstring'] === true) {
9398 2
            if ($encoding === 'UTF-8') {
9399 2
                return \mb_strrchr($haystack, $needle, $before_needle);
9400
            }
9401
9402 2
            return \mb_strrchr($haystack, $needle, $before_needle, $encoding);
9403
        }
9404
9405
        //
9406
        // fallback for binary || ascii only
9407
        //
9408
9409
        if (
9410
            $before_needle === false
9411
            &&
9412
            (
9413
                $encoding === 'CP850'
9414
                ||
9415
                $encoding === 'ASCII'
9416
            )
9417
        ) {
9418
            return \strrchr($haystack, $needle);
9419
        }
9420
9421
        if (
9422
            $encoding !== 'UTF-8'
9423
            &&
9424
            self::$SUPPORT['mbstring'] === false
9425
        ) {
9426
            \trigger_error('UTF8::strrchr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9427
        }
9428
9429
        //
9430
        // fallback via iconv
9431
        //
9432
9433
        if (self::$SUPPORT['iconv'] === true) {
9434
            $needle_tmp = self::substr($needle, 0, 1, $encoding);
9435
            if ($needle_tmp === false) {
9436
                return false;
9437
            }
9438
            $needle = (string) $needle_tmp;
9439
9440
            $pos = \iconv_strrpos($haystack, $needle, $encoding);
9441
            if ($pos === false) {
9442
                return false;
9443
            }
9444
9445
            if ($before_needle) {
9446
                return self::substr($haystack, 0, $pos, $encoding);
9447
            }
9448
9449
            return self::substr($haystack, $pos, null, $encoding);
9450
        }
9451
9452
        //
9453
        // fallback via vanilla php
9454
        //
9455
9456
        $needle_tmp = self::substr($needle, 0, 1, $encoding);
9457
        if ($needle_tmp === false) {
9458
            return false;
9459
        }
9460
        $needle = (string) $needle_tmp;
9461
9462
        $pos = self::strrpos($haystack, $needle, 0, $encoding);
9463
        if ($pos === false) {
9464
            return false;
9465
        }
9466
9467
        if ($before_needle) {
9468
            return self::substr($haystack, 0, $pos, $encoding);
9469
        }
9470
9471
        return self::substr($haystack, $pos, null, $encoding);
9472
    }
9473
9474
    /**
9475
     * Reverses characters order in the string.
9476
     *
9477
     * @param string $str      <p>The input string.</p>
9478
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
9479
     *
9480
     * @return string the string with characters in the reverse sequence
9481
     */
9482 10
    public static function strrev(string $str, string $encoding = 'UTF-8'): string
9483
    {
9484 10
        if ($str === '') {
9485 4
            return '';
9486
        }
9487
9488
        // init
9489 8
        $reversed = '';
9490
9491 8
        $str = self::emoji_encode($str, true);
9492
9493 8
        if ($encoding === 'UTF-8') {
9494 8
            if (self::$SUPPORT['intl'] === true) {
9495
                // try "grapheme" first: https://stackoverflow.com/questions/17496493/strrev-dosent-support-utf-8
9496 8
                $i = (int) \grapheme_strlen($str);
9497 8
                while ($i--) {
9498 8
                    $reversed_tmp = \grapheme_substr($str, $i, 1);
9499 8
                    if ($reversed_tmp !== false) {
9500 8
                        $reversed .= $reversed_tmp;
9501
                    }
9502
                }
9503
            } else {
9504
                $i = (int) \mb_strlen($str);
9505 8
                while ($i--) {
9506
                    $reversed_tmp = \mb_substr($str, $i, 1);
9507
                    if ($reversed_tmp !== false) {
9508
                        $reversed .= $reversed_tmp;
9509
                    }
9510
                }
9511
            }
9512
        } else {
9513
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9514
9515
            $i = (int) self::strlen($str, $encoding);
9516
            while ($i--) {
9517
                $reversed_tmp = self::substr($str, $i, 1, $encoding);
9518
                if ($reversed_tmp !== false) {
9519
                    $reversed .= $reversed_tmp;
9520
                }
9521
            }
9522
        }
9523
9524 8
        return self::emoji_decode($reversed, true);
9525
    }
9526
9527
    /**
9528
     * Find the last occurrence of a character in a string within another, case-insensitive.
9529
     *
9530
     * @see http://php.net/manual/en/function.mb-strrichr.php
9531
     *
9532
     * @param string $haystack      <p>The string from which to get the last occurrence of needle.</p>
9533
     * @param string $needle        <p>The string to find in haystack.</p>
9534
     * @param bool   $before_needle [optional] <p>
9535
     *                              Determines which portion of haystack
9536
     *                              this function returns.
9537
     *                              If set to true, it returns all of haystack
9538
     *                              from the beginning to the last occurrence of needle.
9539
     *                              If set to false, it returns all of haystack
9540
     *                              from the last occurrence of needle to the end,
9541
     *                              </p>
9542
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
9543
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
9544
     *
9545
     * @return false|string the portion of haystack or<br>false if needle is not found
9546
     */
9547 3
    public static function strrichr(
9548
        string $haystack,
9549
        string $needle,
9550
        bool $before_needle = false,
9551
        string $encoding = 'UTF-8',
9552
        bool $clean_utf8 = false
9553
    ) {
9554 3
        if ($haystack === '' || $needle === '') {
9555 2
            return false;
9556
        }
9557
9558 3
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9559 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9560
        }
9561
9562 3
        if ($clean_utf8 === true) {
9563
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9564
            // if invalid characters are found in $haystack before $needle
9565 2
            $needle = self::clean($needle);
9566 2
            $haystack = self::clean($haystack);
9567
        }
9568
9569
        //
9570
        // fallback via mbstring
9571
        //
9572
9573 3
        if (self::$SUPPORT['mbstring'] === true) {
9574 3
            if ($encoding === 'UTF-8') {
9575 3
                return \mb_strrichr($haystack, $needle, $before_needle);
9576
            }
9577
9578 2
            return \mb_strrichr($haystack, $needle, $before_needle, $encoding);
9579
        }
9580
9581
        //
9582
        // fallback via vanilla php
9583
        //
9584
9585
        $needle_tmp = self::substr($needle, 0, 1, $encoding);
9586
        if ($needle_tmp === false) {
9587
            return false;
9588
        }
9589
        $needle = (string) $needle_tmp;
9590
9591
        $pos = self::strripos($haystack, $needle, 0, $encoding);
9592
        if ($pos === false) {
9593
            return false;
9594
        }
9595
9596
        if ($before_needle) {
9597
            return self::substr($haystack, 0, $pos, $encoding);
9598
        }
9599
9600
        return self::substr($haystack, $pos, null, $encoding);
9601
    }
9602
9603
    /**
9604
     * Find the position of the last occurrence of a substring in a string, case-insensitive.
9605
     *
9606
     * @param string     $haystack   <p>The string to look in.</p>
9607
     * @param int|string $needle     <p>The string to look for.</p>
9608
     * @param int        $offset     [optional] <p>Number of characters to ignore in the beginning or end.</p>
9609
     * @param string     $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
9610
     * @param bool       $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
9611
     *
9612
     * @return false|int
9613
     *                   <p>The <strong>(int)</strong> numeric position of the last occurrence of needle in the haystack
9614
     *                   string.<br>If needle is not found, it returns false.</p>
9615
     */
9616 3
    public static function strripos(
9617
        string $haystack,
9618
        $needle,
9619
        int $offset = 0,
9620
        string $encoding = 'UTF-8',
9621
        bool $clean_utf8 = false
9622
    ) {
9623 3
        if ($haystack === '') {
9624
            return false;
9625
        }
9626
9627
        // iconv and mbstring do not support integer $needle
9628 3
        if ((int) $needle === $needle && $needle >= 0) {
9629
            $needle = (string) self::chr($needle);
9630
        }
9631 3
        $needle = (string) $needle;
9632
9633 3
        if ($needle === '') {
9634
            return false;
9635
        }
9636
9637 3
        if ($clean_utf8 === true) {
9638
            // mb_strripos() && iconv_strripos() is not tolerant to invalid characters
9639 2
            $needle = self::clean($needle);
9640 2
            $haystack = self::clean($haystack);
9641
        }
9642
9643 3
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9644 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9645
        }
9646
9647
        //
9648
        // fallback via mbstrig
9649
        //
9650
9651 3
        if (self::$SUPPORT['mbstring'] === true) {
9652 3
            if ($encoding === 'UTF-8') {
9653 3
                return \mb_strripos($haystack, $needle, $offset);
9654
            }
9655
9656
            return \mb_strripos($haystack, $needle, $offset, $encoding);
9657
        }
9658
9659
        //
9660
        // fallback for binary || ascii only
9661
        //
9662
9663
        if (
9664
            $encoding === 'CP850'
9665
            ||
9666
            $encoding === 'ASCII'
9667
        ) {
9668
            return \strripos($haystack, $needle, $offset);
9669
        }
9670
9671
        if (
9672
            $encoding !== 'UTF-8'
9673
            &&
9674
            self::$SUPPORT['mbstring'] === false
9675
        ) {
9676
            \trigger_error('UTF8::strripos() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9677
        }
9678
9679
        //
9680
        // fallback via intl
9681
        //
9682
9683
        if (
9684
            $encoding === 'UTF-8' // INFO: "grapheme_strripos()" can't handle other encodings
9685
            &&
9686
            $offset >= 0 // grapheme_strripos() can't handle negative offset
9687
            &&
9688
            self::$SUPPORT['intl'] === true
9689
        ) {
9690
            $return_tmp = \grapheme_strripos($haystack, $needle, $offset);
9691
            if ($return_tmp !== false) {
9692
                return $return_tmp;
9693
            }
9694
        }
9695
9696
        //
9697
        // fallback for ascii only
9698
        //
9699
9700
        if (ASCII::is_ascii($haystack . $needle)) {
9701
            return \strripos($haystack, $needle, $offset);
9702
        }
9703
9704
        //
9705
        // fallback via vanilla php
9706
        //
9707
9708
        $haystack = self::strtocasefold($haystack, true, false, $encoding);
9709
        $needle = self::strtocasefold($needle, true, false, $encoding);
9710
9711
        return self::strrpos($haystack, $needle, $offset, $encoding, $clean_utf8);
9712
    }
9713
9714
    /**
9715
     * Finds position of last occurrence of a string within another, case-insensitive.
9716
     *
9717
     * @param string $haystack <p>
9718
     *                         The string from which to get the position of the last occurrence
9719
     *                         of needle.
9720
     *                         </p>
9721
     * @param string $needle   <p>
9722
     *                         The string to find in haystack.
9723
     *                         </p>
9724
     * @param int    $offset   [optional] <p>
9725
     *                         The position in haystack
9726
     *                         to start searching.
9727
     *                         </p>
9728
     *
9729
     * @return false|int
9730
     *                   <p>eturn the numeric position of the last occurrence of needle in the
9731
     *                   haystack string, or false if needle is not found.</p>
9732
     */
9733
    public static function strripos_in_byte(string $haystack, string $needle, int $offset = 0)
9734
    {
9735
        if ($haystack === '' || $needle === '') {
9736
            return false;
9737
        }
9738
9739
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
9740
            // "mb_" is available if overload is used, so use it ...
9741
            return \mb_strripos($haystack, $needle, $offset, 'CP850'); // 8-BIT
9742
        }
9743
9744
        return \strripos($haystack, $needle, $offset);
9745
    }
9746
9747
    /**
9748
     * Find the position of the last occurrence of a substring in a string.
9749
     *
9750
     * @see http://php.net/manual/en/function.mb-strrpos.php
9751
     *
9752
     * @param string     $haystack   <p>The string being checked, for the last occurrence of needle</p>
9753
     * @param int|string $needle     <p>The string to find in haystack.<br>Or a code point as int.</p>
9754
     * @param int        $offset     [optional] <p>May be specified to begin searching an arbitrary number of characters
9755
     *                               into the string. Negative values will stop searching at an arbitrary point prior to
9756
     *                               the end of the string.
9757
     *                               </p>
9758
     * @param string     $encoding   [optional] <p>Set the charset.</p>
9759
     * @param bool       $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
9760
     *
9761
     * @return false|int
9762
     *                   <p>The <strong>(int)</strong> numeric position of the last occurrence of needle in the haystack
9763
     *                   string.<br>If needle is not found, it returns false.</p>
9764
     */
9765 35
    public static function strrpos(
9766
        string $haystack,
9767
        $needle,
9768
        int $offset = 0,
9769
        string $encoding = 'UTF-8',
9770
        bool $clean_utf8 = false
9771
    ) {
9772 35
        if ($haystack === '') {
9773 3
            return false;
9774
        }
9775
9776
        // iconv and mbstring do not support integer $needle
9777 34
        if ((int) $needle === $needle && $needle >= 0) {
9778 2
            $needle = (string) self::chr($needle);
9779
        }
9780 34
        $needle = (string) $needle;
9781
9782 34
        if ($needle === '') {
9783 2
            return false;
9784
        }
9785
9786 34
        if ($clean_utf8 === true) {
9787
            // mb_strrpos && iconv_strrpos is not tolerant to invalid characters
9788 4
            $needle = self::clean($needle);
9789 4
            $haystack = self::clean($haystack);
9790
        }
9791
9792 34
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9793 8
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9794
        }
9795
9796
        //
9797
        // fallback via mbstring
9798
        //
9799
9800 34
        if (self::$SUPPORT['mbstring'] === true) {
9801 34
            if ($encoding === 'UTF-8') {
9802 34
                return \mb_strrpos($haystack, $needle, $offset);
9803
            }
9804
9805 2
            return \mb_strrpos($haystack, $needle, $offset, $encoding);
9806
        }
9807
9808
        //
9809
        // fallback for binary || ascii only
9810
        //
9811
9812
        if (
9813
            $encoding === 'CP850'
9814
            ||
9815
            $encoding === 'ASCII'
9816
        ) {
9817
            return \strrpos($haystack, $needle, $offset);
9818
        }
9819
9820
        if (
9821
            $encoding !== 'UTF-8'
9822
            &&
9823
            self::$SUPPORT['mbstring'] === false
9824
        ) {
9825
            \trigger_error('UTF8::strrpos() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
9826
        }
9827
9828
        //
9829
        // fallback via intl
9830
        //
9831
9832
        if (
9833
            $offset >= 0 // grapheme_strrpos() can't handle negative offset
9834
            &&
9835
            $encoding === 'UTF-8' // INFO: "grapheme_strrpos()" can't handle other encodings
9836
            &&
9837
            self::$SUPPORT['intl'] === true
9838
        ) {
9839
            $return_tmp = \grapheme_strrpos($haystack, $needle, $offset);
9840
            if ($return_tmp !== false) {
9841
                return $return_tmp;
9842
            }
9843
        }
9844
9845
        //
9846
        // fallback for ascii only
9847
        //
9848
9849
        if (ASCII::is_ascii($haystack . $needle)) {
9850
            return \strrpos($haystack, $needle, $offset);
9851
        }
9852
9853
        //
9854
        // fallback via vanilla php
9855
        //
9856
9857
        $haystack_tmp = null;
9858
        if ($offset > 0) {
9859
            $haystack_tmp = self::substr($haystack, $offset);
9860
        } elseif ($offset < 0) {
9861
            $haystack_tmp = self::substr($haystack, 0, $offset);
9862
            $offset = 0;
9863
        }
9864
9865
        if ($haystack_tmp !== null) {
9866
            if ($haystack_tmp === false) {
9867
                $haystack_tmp = '';
9868
            }
9869
            $haystack = (string) $haystack_tmp;
9870
        }
9871
9872
        $pos = \strrpos($haystack, $needle);
9873
        if ($pos === false) {
9874
            return false;
9875
        }
9876
9877
        /** @var false|string $str_tmp - needed for PhpStan (stubs error) */
9878
        $str_tmp = \substr($haystack, 0, $pos);
9879
        if ($str_tmp === false) {
9880
            return false;
9881
        }
9882
9883
        return $offset + (int) self::strlen($str_tmp);
9884
    }
9885
9886
    /**
9887
     * Find the position of the last occurrence of a substring in a string.
9888
     *
9889
     * @param string $haystack <p>
9890
     *                         The string being checked, for the last occurrence
9891
     *                         of needle.
9892
     *                         </p>
9893
     * @param string $needle   <p>
9894
     *                         The string to find in haystack.
9895
     *                         </p>
9896
     * @param int    $offset   [optional] <p>May be specified to begin searching an arbitrary number of characters into
9897
     *                         the string. Negative values will stop searching at an arbitrary point
9898
     *                         prior to the end of the string.
9899
     *                         </p>
9900
     *
9901
     * @return false|int
9902
     *                   <p>The numeric position of the last occurrence of needle in the
9903
     *                   haystack string. If needle is not found, it returns false.</p>
9904
     */
9905
    public static function strrpos_in_byte(string $haystack, string $needle, int $offset = 0)
9906
    {
9907
        if ($haystack === '' || $needle === '') {
9908
            return false;
9909
        }
9910
9911
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
9912
            // "mb_" is available if overload is used, so use it ...
9913
            return \mb_strrpos($haystack, $needle, $offset, 'CP850'); // 8-BIT
9914
        }
9915
9916
        return \strrpos($haystack, $needle, $offset);
9917
    }
9918
9919
    /**
9920
     * Finds the length of the initial segment of a string consisting entirely of characters contained within a given
9921
     * mask.
9922
     *
9923
     * @param string $str      <p>The input string.</p>
9924
     * @param string $mask     <p>The mask of chars</p>
9925
     * @param int    $offset   [optional]
9926
     * @param int    $length   [optional]
9927
     * @param string $encoding [optional] <p>Set the charset.</p>
9928
     *
9929
     * @return false|int
9930
     */
9931 10
    public static function strspn(
9932
        string $str,
9933
        string $mask,
9934
        int $offset = 0,
9935
        int $length = null,
9936
        string $encoding = 'UTF-8'
9937
    ) {
9938 10
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9939
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9940
        }
9941
9942 10
        if ($offset || $length !== null) {
9943 2
            if ($encoding === 'UTF-8') {
9944 2
                if ($length === null) {
9945
                    $str = (string) \mb_substr($str, $offset);
9946
                } else {
9947 2
                    $str = (string) \mb_substr($str, $offset, $length);
9948
                }
9949
            } else {
9950
                $str = (string) self::substr($str, $offset, $length, $encoding);
9951
            }
9952
        }
9953
9954 10
        if ($str === '' || $mask === '') {
9955 2
            return 0;
9956
        }
9957
9958 8
        $matches = [];
9959
9960 8
        return \preg_match('/^' . self::rxClass($mask) . '+/u', $str, $matches) ? (int) self::strlen($matches[0], $encoding) : 0;
9961
    }
9962
9963
    /**
9964
     * Returns part of haystack string from the first occurrence of needle to the end of haystack.
9965
     *
9966
     * @param string $haystack      <p>The input string. Must be valid UTF-8.</p>
9967
     * @param string $needle        <p>The string to look for. Must be valid UTF-8.</p>
9968
     * @param bool   $before_needle [optional] <p>
9969
     *                              If <b>TRUE</b>, strstr() returns the part of the
9970
     *                              haystack before the first occurrence of the needle (excluding the needle).
9971
     *                              </p>
9972
     * @param string $encoding      [optional] <p>Set the charset for e.g. "mb_" function</p>
9973
     * @param bool   $clean_utf8    [optional] <p>Remove non UTF-8 chars from the string.</p>
9974
     *
9975
     * @return false|string
9976
     *                      A sub-string,<br>or <strong>false</strong> if needle is not found
9977
     */
9978 3
    public static function strstr(
9979
        string $haystack,
9980
        string $needle,
9981
        bool $before_needle = false,
9982
        string $encoding = 'UTF-8',
9983
        $clean_utf8 = false
9984
    ) {
9985 3
        if ($haystack === '' || $needle === '') {
9986 2
            return false;
9987
        }
9988
9989 3
        if ($clean_utf8 === true) {
9990
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
9991
            // if invalid characters are found in $haystack before $needle
9992
            $needle = self::clean($needle);
9993
            $haystack = self::clean($haystack);
9994
        }
9995
9996 3
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
9997 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
9998
        }
9999
10000
        //
10001
        // fallback via mbstring
10002
        //
10003
10004 3
        if (self::$SUPPORT['mbstring'] === true) {
10005 3
            if ($encoding === 'UTF-8') {
10006 3
                return \mb_strstr($haystack, $needle, $before_needle);
10007
            }
10008
10009 2
            return \mb_strstr($haystack, $needle, $before_needle, $encoding);
10010
        }
10011
10012
        //
10013
        // fallback for binary || ascii only
10014
        //
10015
10016
        if (
10017
            $encoding === 'CP850'
10018
            ||
10019
            $encoding === 'ASCII'
10020
        ) {
10021
            return \strstr($haystack, $needle, $before_needle);
10022
        }
10023
10024
        if (
10025
            $encoding !== 'UTF-8'
10026
            &&
10027
            self::$SUPPORT['mbstring'] === false
10028
        ) {
10029
            \trigger_error('UTF8::strstr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10030
        }
10031
10032
        //
10033
        // fallback via intl
10034
        //
10035
10036
        if (
10037
            $encoding === 'UTF-8' // INFO: "grapheme_strstr()" can't handle other encodings
10038
            &&
10039
            self::$SUPPORT['intl'] === true
10040
        ) {
10041
            $return_tmp = \grapheme_strstr($haystack, $needle, $before_needle);
10042
            if ($return_tmp !== false) {
10043
                return $return_tmp;
10044
            }
10045
        }
10046
10047
        //
10048
        // fallback for ascii only
10049
        //
10050
10051
        if (ASCII::is_ascii($haystack . $needle)) {
10052
            return \strstr($haystack, $needle, $before_needle);
10053
        }
10054
10055
        //
10056
        // fallback via vanilla php
10057
        //
10058
10059
        \preg_match('/^(.*?)' . \preg_quote($needle, '/') . '/us', $haystack, $match);
10060
10061
        if (!isset($match[1])) {
10062
            return false;
10063
        }
10064
10065
        if ($before_needle) {
10066
            return $match[1];
10067
        }
10068
10069
        return self::substr($haystack, (int) self::strlen($match[1]));
10070
    }
10071
10072
    /**
10073
     *  * Finds first occurrence of a string within another.
10074
     *
10075
     * @param string $haystack      <p>
10076
     *                              The string from which to get the first occurrence
10077
     *                              of needle.
10078
     *                              </p>
10079
     * @param string $needle        <p>
10080
     *                              The string to find in haystack.
10081
     *                              </p>
10082
     * @param bool   $before_needle [optional] <p>
10083
     *                              Determines which portion of haystack
10084
     *                              this function returns.
10085
     *                              If set to true, it returns all of haystack
10086
     *                              from the beginning to the first occurrence of needle.
10087
     *                              If set to false, it returns all of haystack
10088
     *                              from the first occurrence of needle to the end,
10089
     *                              </p>
10090
     *
10091
     * @return false|string
10092
     *                      <p>The portion of haystack,
10093
     *                      or false if needle is not found.</p>
10094
     */
10095
    public static function strstr_in_byte(
10096
        string $haystack,
10097
        string $needle,
10098
        bool $before_needle = false
10099
    ) {
10100
        if ($haystack === '' || $needle === '') {
10101
            return false;
10102
        }
10103
10104
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10105
            // "mb_" is available if overload is used, so use it ...
10106
            return \mb_strstr($haystack, $needle, $before_needle, 'CP850'); // 8-BIT
10107
        }
10108
10109
        return \strstr($haystack, $needle, $before_needle);
10110
    }
10111
10112
    /**
10113
     * Unicode transformation for case-less matching.
10114
     *
10115
     * @see http://unicode.org/reports/tr21/tr21-5.html
10116
     *
10117
     * @param string      $str        <p>The input string.</p>
10118
     * @param bool        $full       [optional] <p>
10119
     *                                <b>true</b>, replace full case folding chars (default)<br>
10120
     *                                <b>false</b>, use only limited static array [UTF8::$COMMON_CASE_FOLD]
10121
     *                                </p>
10122
     * @param bool        $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10123
     * @param string      $encoding   [optional] <p>Set the charset.</p>
10124
     * @param string|null $lang       [optional] <p>Set the language for special cases: az, el, lt, tr</p>
10125
     * @param bool        $lower      [optional] <p>Use lowercase string, otherwise use uppercase string. PS: uppercase
10126
     *                                is for some languages better ...</p>
10127
     *
10128
     * @return string
10129
     */
10130 32
    public static function strtocasefold(
10131
        string $str,
10132
        bool $full = true,
10133
        bool $clean_utf8 = false,
10134
        string $encoding = 'UTF-8',
10135
        string $lang = null,
10136
        $lower = true
10137
    ): string {
10138 32
        if ($str === '') {
10139 5
            return '';
10140
        }
10141
10142 31
        if ($clean_utf8 === true) {
10143
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10144
            // if invalid characters are found in $haystack before $needle
10145 2
            $str = self::clean($str);
10146
        }
10147
10148 31
        $str = self::fixStrCaseHelper($str, $lower, $full);
10149
10150 31
        if ($lang === null && $encoding === 'UTF-8') {
10151 31
            if ($lower === true) {
10152 2
                return \mb_strtolower($str);
10153
            }
10154
10155 29
            return \mb_strtoupper($str);
10156
        }
10157
10158 2
        if ($lower === true) {
10159
            return self::strtolower($str, $encoding, false, $lang);
10160
        }
10161
10162 2
        return self::strtoupper($str, $encoding, false, $lang);
10163
    }
10164
10165
    /**
10166
     * Make a string lowercase.
10167
     *
10168
     * @see http://php.net/manual/en/function.mb-strtolower.php
10169
     *
10170
     * @param string      $str                           <p>The string being lowercased.</p>
10171
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
10172
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
10173
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
10174
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
10175
     *
10176
     * @return string
10177
     *                <p>String with all alphabetic characters converted to lowercase.</p>
10178
     */
10179 73
    public static function strtolower(
10180
        $str,
10181
        string $encoding = 'UTF-8',
10182
        bool $clean_utf8 = false,
10183
        string $lang = null,
10184
        bool $try_to_keep_the_string_length = false
10185
    ): string {
10186
        // init
10187 73
        $str = (string) $str;
10188
10189 73
        if ($str === '') {
10190 1
            return '';
10191
        }
10192
10193 72
        if ($clean_utf8 === true) {
10194
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10195
            // if invalid characters are found in $haystack before $needle
10196 2
            $str = self::clean($str);
10197
        }
10198
10199
        // hack for old php version or for the polyfill ...
10200 72
        if ($try_to_keep_the_string_length === true) {
10201
            $str = self::fixStrCaseHelper($str, true);
10202
        }
10203
10204 72
        if ($lang === null && $encoding === 'UTF-8') {
10205 13
            return \mb_strtolower($str);
10206
        }
10207
10208 61
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
10209
10210 61
        if ($lang !== null) {
10211 2
            if (self::$SUPPORT['intl'] === true) {
10212 2
                if (self::$INTL_TRANSLITERATOR_LIST === null) {
10213
                    self::$INTL_TRANSLITERATOR_LIST = self::getData('transliterator_list');
10214
                }
10215
10216 2
                $language_code = $lang . '-Lower';
10217 2
                if (!\in_array($language_code, self::$INTL_TRANSLITERATOR_LIST, true)) {
10218
                    \trigger_error('UTF8::strtolower() cannot handle special language: ' . $lang . ' | supported: ' . \print_r(self::$INTL_TRANSLITERATOR_LIST, true), \E_USER_WARNING);
10219
10220
                    $language_code = 'Any-Lower';
10221
                }
10222
10223
                /** @noinspection PhpComposerExtensionStubsInspection */
10224
                /** @noinspection UnnecessaryCastingInspection */
10225 2
                return (string) \transliterator_transliterate($language_code, $str);
10226
            }
10227
10228
            \trigger_error('UTF8::strtolower() without intl cannot handle the "lang" parameter: ' . $lang, \E_USER_WARNING);
10229
        }
10230
10231
        // always fallback via symfony polyfill
10232 61
        return \mb_strtolower($str, $encoding);
10233
    }
10234
10235
    /**
10236
     * Make a string uppercase.
10237
     *
10238
     * @see http://php.net/manual/en/function.mb-strtoupper.php
10239
     *
10240
     * @param string      $str                           <p>The string being uppercased.</p>
10241
     * @param string      $encoding                      [optional] <p>Set the charset.</p>
10242
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
10243
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
10244
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
10245
     *
10246
     * @return string
10247
     *                <p>String with all alphabetic characters converted to uppercase.</p>
10248
     */
10249 17
    public static function strtoupper(
10250
        $str,
10251
        string $encoding = 'UTF-8',
10252
        bool $clean_utf8 = false,
10253
        string $lang = null,
10254
        bool $try_to_keep_the_string_length = false
10255
    ): string {
10256
        // init
10257 17
        $str = (string) $str;
10258
10259 17
        if ($str === '') {
10260 1
            return '';
10261
        }
10262
10263 16
        if ($clean_utf8 === true) {
10264
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10265
            // if invalid characters are found in $haystack before $needle
10266 2
            $str = self::clean($str);
10267
        }
10268
10269
        // hack for old php version or for the polyfill ...
10270 16
        if ($try_to_keep_the_string_length === true) {
10271 2
            $str = self::fixStrCaseHelper($str, false);
10272
        }
10273
10274 16
        if ($lang === null && $encoding === 'UTF-8') {
10275 8
            return \mb_strtoupper($str);
10276
        }
10277
10278 10
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
10279
10280 10
        if ($lang !== null) {
10281 2
            if (self::$SUPPORT['intl'] === true) {
10282 2
                if (self::$INTL_TRANSLITERATOR_LIST === null) {
10283
                    self::$INTL_TRANSLITERATOR_LIST = self::getData('transliterator_list');
10284
                }
10285
10286 2
                $language_code = $lang . '-Upper';
10287 2
                if (!\in_array($language_code, self::$INTL_TRANSLITERATOR_LIST, true)) {
10288
                    \trigger_error('UTF8::strtoupper() without intl for special language: ' . $lang, \E_USER_WARNING);
10289
10290
                    $language_code = 'Any-Upper';
10291
                }
10292
10293
                /** @noinspection PhpComposerExtensionStubsInspection */
10294
                /** @noinspection UnnecessaryCastingInspection */
10295 2
                return (string) \transliterator_transliterate($language_code, $str);
10296
            }
10297
10298
            \trigger_error('UTF8::strtolower() without intl cannot handle the "lang"-parameter: ' . $lang, \E_USER_WARNING);
10299
        }
10300
10301
        // always fallback via symfony polyfill
10302 10
        return \mb_strtoupper($str, $encoding);
10303
    }
10304
10305
    /**
10306
     * Translate characters or replace sub-strings.
10307
     *
10308
     * @see http://php.net/manual/en/function.strtr.php
10309
     *
10310
     * @param string          $str  <p>The string being translated.</p>
10311
     * @param string|string[] $from <p>The string replacing from.</p>
10312
     * @param string|string[] $to   [optional] <p>The string being translated to to.</p>
10313
     *
10314
     * @return string
10315
     *                <p>This function returns a copy of str, translating all occurrences of each character in "from" to the
10316
     *                corresponding character in "to".</p>
10317
     */
10318 2
    public static function strtr(string $str, $from, $to = ''): string
10319
    {
10320 2
        if ($str === '') {
10321
            return '';
10322
        }
10323
10324 2
        if ($from === $to) {
10325
            return $str;
10326
        }
10327
10328 2
        if ($to !== '') {
10329 2
            $from = self::str_split($from);
10330 2
            $to = self::str_split($to);
10331 2
            $count_from = \count($from);
10332 2
            $count_to = \count($to);
10333
10334 2
            if ($count_from > $count_to) {
10335 2
                $from = \array_slice($from, 0, $count_to);
10336 2
            } elseif ($count_from < $count_to) {
10337 2
                $to = \array_slice($to, 0, $count_from);
10338
            }
10339
10340 2
            $from = \array_combine($from, $to);
10341
            /** @noinspection CallableParameterUseCaseInTypeContextInspection - FP */
10342 2
            if ($from === false) {
10343
                throw new \InvalidArgumentException('The number of elements for each array isn\'t equal or the arrays are empty: (from: ' . \print_r($from, true) . ' | to: ' . \print_r($to, true) . ')');
10344
            }
10345
        }
10346
10347 2
        if (\is_string($from)) {
10348 2
            return \str_replace($from, '', $str);
10349
        }
10350
10351 2
        return \strtr($str, $from);
10352
    }
10353
10354
    /**
10355
     * Return the width of a string.
10356
     *
10357
     * @param string $str        <p>The input string.</p>
10358
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
10359
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10360
     *
10361
     * @return int
10362
     */
10363 2
    public static function strwidth(
10364
        string $str,
10365
        string $encoding = 'UTF-8',
10366
        bool $clean_utf8 = false
10367
    ): int {
10368 2
        if ($str === '') {
10369 2
            return 0;
10370
        }
10371
10372 2
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10373 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10374
        }
10375
10376 2
        if ($clean_utf8 === true) {
10377
            // iconv and mbstring are not tolerant to invalid encoding
10378
            // further, their behaviour is inconsistent with that of PHP's substr
10379 2
            $str = self::clean($str);
10380
        }
10381
10382
        //
10383
        // fallback via mbstring
10384
        //
10385
10386 2
        if (self::$SUPPORT['mbstring'] === true) {
10387 2
            if ($encoding === 'UTF-8') {
10388 2
                return \mb_strwidth($str);
10389
            }
10390
10391
            return \mb_strwidth($str, $encoding);
10392
        }
10393
10394
        //
10395
        // fallback via vanilla php
10396
        //
10397
10398
        if ($encoding !== 'UTF-8') {
10399
            $str = self::encode('UTF-8', $str, false, $encoding);
10400
        }
10401
10402
        $wide = 0;
10403
        $str = (string) \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', '', $str, -1, $wide);
10404
10405
        return ($wide << 1) + (int) self::strlen($str, 'UTF-8');
10406
    }
10407
10408
    /**
10409
     * Get part of a string.
10410
     *
10411
     * @see http://php.net/manual/en/function.mb-substr.php
10412
     *
10413
     * @param string $str        <p>The string being checked.</p>
10414
     * @param int    $offset     <p>The first position used in str.</p>
10415
     * @param int    $length     [optional] <p>The maximum length of the returned string.</p>
10416
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
10417
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10418
     *
10419
     * @return false|string
10420
     *                      The portion of <i>str</i> specified by the <i>offset</i> and
10421
     *                      <i>length</i> parameters.</p><p>If <i>str</i> is shorter than <i>offset</i>
10422
     *                      characters long, <b>FALSE</b> will be returned.
10423
     */
10424 172
    public static function substr(
10425
        string $str,
10426
        int $offset = 0,
10427
        int $length = null,
10428
        string $encoding = 'UTF-8',
10429
        bool $clean_utf8 = false
10430
    ) {
10431
        // empty string
10432 172
        if ($str === '' || $length === 0) {
10433 8
            return '';
10434
        }
10435
10436 168
        if ($clean_utf8 === true) {
10437
            // iconv and mbstring are not tolerant to invalid encoding
10438
            // further, their behaviour is inconsistent with that of PHP's substr
10439 2
            $str = self::clean($str);
10440
        }
10441
10442
        // whole string
10443 168
        if (!$offset && $length === null) {
10444 7
            return $str;
10445
        }
10446
10447 163
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10448 19
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10449
        }
10450
10451
        //
10452
        // fallback via mbstring
10453
        //
10454
10455 163
        if (self::$SUPPORT['mbstring'] === true) {
10456 161
            if ($encoding === 'UTF-8') {
10457 161
                if ($length === null) {
10458 64
                    return \mb_substr($str, $offset);
10459
                }
10460
10461 102
                return \mb_substr($str, $offset, $length);
10462
            }
10463
10464
            return self::substr($str, $offset, $length, $encoding);
10465
        }
10466
10467
        //
10468
        // fallback for binary || ascii only
10469
        //
10470
10471
        if (
10472 4
            $encoding === 'CP850'
10473
            ||
10474 4
            $encoding === 'ASCII'
10475
        ) {
10476
            if ($length === null) {
10477
                return \substr($str, $offset);
10478
            }
10479
10480
            return \substr($str, $offset, $length);
10481
        }
10482
10483
        // otherwise we need the string-length
10484 4
        $str_length = 0;
10485 4
        if ($offset || $length === null) {
10486 4
            $str_length = self::strlen($str, $encoding);
10487
        }
10488
10489
        // e.g.: invalid chars + mbstring not installed
10490 4
        if ($str_length === false) {
10491
            return false;
10492
        }
10493
10494
        // empty string
10495 4
        if ($offset === $str_length && !$length) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $length of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
10496
            return '';
10497
        }
10498
10499
        // impossible
10500 4
        if ($offset && $offset > $str_length) {
10501
            return '';
10502
        }
10503
10504 4
        if ($length === null) {
10505 4
            $length = (int) $str_length;
10506
        } else {
10507 2
            $length = (int) $length;
10508
        }
10509
10510
        if (
10511 4
            $encoding !== 'UTF-8'
10512
            &&
10513 4
            self::$SUPPORT['mbstring'] === false
10514
        ) {
10515 2
            \trigger_error('UTF8::substr() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10516
        }
10517
10518
        //
10519
        // fallback via intl
10520
        //
10521
10522
        if (
10523 4
            $encoding === 'UTF-8' // INFO: "grapheme_substr()" can't handle other encodings
10524
            &&
10525 4
            $offset >= 0 // grapheme_substr() can't handle negative offset
10526
            &&
10527 4
            self::$SUPPORT['intl'] === true
10528
        ) {
10529
            $return_tmp = \grapheme_substr($str, $offset, $length);
10530
            if ($return_tmp !== false) {
10531
                return $return_tmp;
10532
            }
10533
        }
10534
10535
        //
10536
        // fallback via iconv
10537
        //
10538
10539
        if (
10540 4
            $length >= 0 // "iconv_substr()" can't handle negative length
10541
            &&
10542 4
            self::$SUPPORT['iconv'] === true
10543
        ) {
10544
            $return_tmp = \iconv_substr($str, $offset, $length);
10545
            if ($return_tmp !== false) {
10546
                return $return_tmp;
10547
            }
10548
        }
10549
10550
        //
10551
        // fallback for ascii only
10552
        //
10553
10554 4
        if (ASCII::is_ascii($str)) {
10555
            return \substr($str, $offset, $length);
10556
        }
10557
10558
        //
10559
        // fallback via vanilla php
10560
        //
10561
10562
        // split to array, and remove invalid characters
10563 4
        $array = self::str_split($str);
10564
10565
        // extract relevant part, and join to make sting again
10566 4
        return \implode('', \array_slice($array, $offset, $length));
10567
    }
10568
10569
    /**
10570
     * Binary-safe comparison of two strings from an offset, up to a length of characters.
10571
     *
10572
     * @param string   $str1               <p>The main string being compared.</p>
10573
     * @param string   $str2               <p>The secondary string being compared.</p>
10574
     * @param int      $offset             [optional] <p>The start position for the comparison. If negative, it starts
10575
     *                                     counting from the end of the string.</p>
10576
     * @param int|null $length             [optional] <p>The length of the comparison. The default value is the largest
10577
     *                                     of the length of the str compared to the length of main_str less the
10578
     *                                     offset.</p>
10579
     * @param bool     $case_insensitivity [optional] <p>If case_insensitivity is TRUE, comparison is case
10580
     *                                     insensitive.</p>
10581
     * @param string   $encoding           [optional] <p>Set the charset for e.g. "mb_" function</p>
10582
     *
10583
     * @return int
10584
     *             <strong>&lt; 0</strong> if str1 is less than str2;<br>
10585
     *             <strong>&gt; 0</strong> if str1 is greater than str2,<br>
10586
     *             <strong>0</strong> if they are equal
10587
     */
10588 2
    public static function substr_compare(
10589
        string $str1,
10590
        string $str2,
10591
        int $offset = 0,
10592
        int $length = null,
10593
        bool $case_insensitivity = false,
10594
        string $encoding = 'UTF-8'
10595
    ): int {
10596
        if (
10597 2
            $offset !== 0
10598
            ||
10599 2
            $length !== null
10600
        ) {
10601 2
            if ($encoding === 'UTF-8') {
10602 2
                if ($length === null) {
10603 2
                    $str1 = (string) \mb_substr($str1, $offset);
10604
                } else {
10605 2
                    $str1 = (string) \mb_substr($str1, $offset, $length);
10606
                }
10607 2
                $str2 = (string) \mb_substr($str2, 0, (int) self::strlen($str1));
10608
            } else {
10609
                $encoding = self::normalize_encoding($encoding, 'UTF-8');
10610
10611
                $str1 = (string) self::substr($str1, $offset, $length, $encoding);
10612
                $str2 = (string) self::substr($str2, 0, (int) self::strlen($str1), $encoding);
10613
            }
10614
        }
10615
10616 2
        if ($case_insensitivity === true) {
10617 2
            return self::strcasecmp($str1, $str2, $encoding);
10618
        }
10619
10620 2
        return self::strcmp($str1, $str2);
10621
    }
10622
10623
    /**
10624
     * Count the number of substring occurrences.
10625
     *
10626
     * @see http://php.net/manual/en/function.substr-count.php
10627
     *
10628
     * @param string $haystack   <p>The string to search in.</p>
10629
     * @param string $needle     <p>The substring to search for.</p>
10630
     * @param int    $offset     [optional] <p>The offset where to start counting.</p>
10631
     * @param int    $length     [optional] <p>
10632
     *                           The maximum length after the specified offset to search for the
10633
     *                           substring. It outputs a warning if the offset plus the length is
10634
     *                           greater than the haystack length.
10635
     *                           </p>
10636
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
10637
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
10638
     *
10639
     * @return false|int this functions returns an integer or false if there isn't a string
10640
     */
10641 5
    public static function substr_count(
10642
        string $haystack,
10643
        string $needle,
10644
        int $offset = 0,
10645
        int $length = null,
10646
        string $encoding = 'UTF-8',
10647
        bool $clean_utf8 = false
10648
    ) {
10649 5
        if ($haystack === '' || $needle === '') {
10650 2
            return false;
10651
        }
10652
10653 5
        if ($length === 0) {
10654 2
            return 0;
10655
        }
10656
10657 5
        if ($encoding !== 'UTF-8' && $encoding !== 'CP850') {
10658 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
10659
        }
10660
10661 5
        if ($clean_utf8 === true) {
10662
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
10663
            // if invalid characters are found in $haystack before $needle
10664
            $needle = self::clean($needle);
10665
            $haystack = self::clean($haystack);
10666
        }
10667
10668 5
        if ($offset || $length > 0) {
10669 2
            if ($length === null) {
10670 2
                $length_tmp = self::strlen($haystack, $encoding);
10671 2
                if ($length_tmp === false) {
10672
                    return false;
10673
                }
10674 2
                $length = (int) $length_tmp;
10675
            }
10676
10677 2
            if ($encoding === 'UTF-8') {
10678 2
                $haystack = (string) \mb_substr($haystack, $offset, $length);
10679
            } else {
10680 2
                $haystack = (string) \mb_substr($haystack, $offset, $length, $encoding);
10681
            }
10682
        }
10683
10684
        if (
10685 5
            $encoding !== 'UTF-8'
10686
            &&
10687 5
            self::$SUPPORT['mbstring'] === false
10688
        ) {
10689
            \trigger_error('UTF8::substr_count() without mbstring cannot handle "' . $encoding . '" encoding', \E_USER_WARNING);
10690
        }
10691
10692 5
        if (self::$SUPPORT['mbstring'] === true) {
10693 5
            if ($encoding === 'UTF-8') {
10694 5
                return \mb_substr_count($haystack, $needle);
10695
            }
10696
10697 2
            return \mb_substr_count($haystack, $needle, $encoding);
10698
        }
10699
10700
        \preg_match_all('/' . \preg_quote($needle, '/') . '/us', $haystack, $matches, \PREG_SET_ORDER);
10701
10702
        return \count($matches);
10703
    }
10704
10705
    /**
10706
     * Count the number of substring occurrences.
10707
     *
10708
     * @param string $haystack <p>
10709
     *                         The string being checked.
10710
     *                         </p>
10711
     * @param string $needle   <p>
10712
     *                         The string being found.
10713
     *                         </p>
10714
     * @param int    $offset   [optional] <p>
10715
     *                         The offset where to start counting
10716
     *                         </p>
10717
     * @param int    $length   [optional] <p>
10718
     *                         The maximum length after the specified offset to search for the
10719
     *                         substring. It outputs a warning if the offset plus the length is
10720
     *                         greater than the haystack length.
10721
     *                         </p>
10722
     *
10723
     * @return false|int the number of times the
10724
     *                   needle substring occurs in the
10725
     *                   haystack string
10726
     */
10727
    public static function substr_count_in_byte(
10728
        string $haystack,
10729
        string $needle,
10730
        int $offset = 0,
10731
        int $length = null
10732
    ) {
10733
        if ($haystack === '' || $needle === '') {
10734
            return 0;
10735
        }
10736
10737
        if (
10738
            ($offset || $length !== null)
10739
            &&
10740
            self::$SUPPORT['mbstring_func_overload'] === true
10741
        ) {
10742
            if ($length === null) {
10743
                $length_tmp = self::strlen($haystack);
10744
                if ($length_tmp === false) {
10745
                    return false;
10746
                }
10747
                $length = (int) $length_tmp;
10748
            }
10749
10750
            if (
10751
                (
10752
                    $length !== 0
10753
                    &&
10754
                    $offset !== 0
10755
                )
10756
                &&
10757
                ($length + $offset) <= 0
10758
                &&
10759
                Bootup::is_php('7.1') === false // output from "substr_count()" have changed in PHP 7.1
10760
            ) {
10761
                return false;
10762
            }
10763
10764
            /** @var false|string $haystack_tmp - needed for PhpStan (stubs error) */
10765
            $haystack_tmp = \substr($haystack, $offset, $length);
10766
            if ($haystack_tmp === false) {
10767
                $haystack_tmp = '';
10768
            }
10769
            $haystack = (string) $haystack_tmp;
10770
        }
10771
10772
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10773
            // "mb_" is available if overload is used, so use it ...
10774
            return \mb_substr_count($haystack, $needle, 'CP850'); // 8-BIT
10775
        }
10776
10777
        if ($length === null) {
10778
            return \substr_count($haystack, $needle, $offset);
10779
        }
10780
10781
        return \substr_count($haystack, $needle, $offset, $length);
10782
    }
10783
10784
    /**
10785
     * Returns the number of occurrences of $substring in the given string.
10786
     * By default, the comparison is case-sensitive, but can be made insensitive
10787
     * by setting $case_sensitive to false.
10788
     *
10789
     * @param string $str            <p>The input string.</p>
10790
     * @param string $substring      <p>The substring to search for.</p>
10791
     * @param bool   $case_sensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
10792
     * @param string $encoding       [optional] <p>Set the charset for e.g. "mb_" function</p>
10793
     *
10794
     * @return int
10795
     */
10796 15
    public static function substr_count_simple(
10797
        string $str,
10798
        string $substring,
10799
        bool $case_sensitive = true,
10800
        string $encoding = 'UTF-8'
10801
    ): int {
10802 15
        if ($str === '' || $substring === '') {
10803 2
            return 0;
10804
        }
10805
10806 13
        if ($encoding === 'UTF-8') {
10807 7
            if ($case_sensitive) {
10808
                return (int) \mb_substr_count($str, $substring);
10809
            }
10810
10811 7
            return (int) \mb_substr_count(
10812 7
                \mb_strtoupper($str),
10813 7
                \mb_strtoupper($substring)
10814
            );
10815
        }
10816
10817 6
        $encoding = self::normalize_encoding($encoding, 'UTF-8');
10818
10819 6
        if ($case_sensitive) {
10820 3
            return (int) \mb_substr_count($str, $substring, $encoding);
10821
        }
10822
10823 3
        return (int) \mb_substr_count(
10824 3
            self::strtocasefold($str, true, false, $encoding, null, false),
10825 3
            self::strtocasefold($substring, true, false, $encoding, null, false),
10826 3
            $encoding
10827
        );
10828
    }
10829
10830
    /**
10831
     * Removes a prefix ($needle) from the beginning of the string ($haystack), case-insensitive.
10832
     *
10833
     * @param string $haystack <p>The string to search in.</p>
10834
     * @param string $needle   <p>The substring to search for.</p>
10835
     *
10836
     * @return string return the sub-string
10837
     */
10838 2
    public static function substr_ileft(string $haystack, string $needle): string
10839
    {
10840 2
        if ($haystack === '') {
10841 2
            return '';
10842
        }
10843
10844 2
        if ($needle === '') {
10845 2
            return $haystack;
10846
        }
10847
10848 2
        if (self::str_istarts_with($haystack, $needle) === true) {
10849 2
            $haystack = (string) \mb_substr($haystack, (int) self::strlen($needle));
10850
        }
10851
10852 2
        return $haystack;
10853
    }
10854
10855
    /**
10856
     * Get part of a string process in bytes.
10857
     *
10858
     * @param string $str    <p>The string being checked.</p>
10859
     * @param int    $offset <p>The first position used in str.</p>
10860
     * @param int    $length [optional] <p>The maximum length of the returned string.</p>
10861
     *
10862
     * @return false|string
10863
     *                      The portion of <i>str</i> specified by the <i>offset</i> and
10864
     *                      <i>length</i> parameters.</p><p>If <i>str</i> is shorter than <i>offset</i>
10865
     *                      characters long, <b>FALSE</b> will be returned.
10866
     */
10867
    public static function substr_in_byte(string $str, int $offset = 0, int $length = null)
10868
    {
10869
        // empty string
10870
        if ($str === '' || $length === 0) {
10871
            return '';
10872
        }
10873
10874
        // whole string
10875
        if (!$offset && $length === null) {
10876
            return $str;
10877
        }
10878
10879
        if (self::$SUPPORT['mbstring_func_overload'] === true) {
10880
            // "mb_" is available if overload is used, so use it ...
10881
            return \mb_substr($str, $offset, $length, 'CP850'); // 8-BIT
10882
        }
10883
10884
        return \substr($str, $offset, $length ?? 2147483647);
10885
    }
10886
10887
    /**
10888
     * Removes a suffix ($needle) from the end of the string ($haystack), case-insensitive.
10889
     *
10890
     * @param string $haystack <p>The string to search in.</p>
10891
     * @param string $needle   <p>The substring to search for.</p>
10892
     *
10893
     * @return string return the sub-string
10894
     */
10895 2
    public static function substr_iright(string $haystack, string $needle): string
10896
    {
10897 2
        if ($haystack === '') {
10898 2
            return '';
10899
        }
10900
10901 2
        if ($needle === '') {
10902 2
            return $haystack;
10903
        }
10904
10905 2
        if (self::str_iends_with($haystack, $needle) === true) {
10906 2
            $haystack = (string) \mb_substr($haystack, 0, (int) self::strlen($haystack) - (int) self::strlen($needle));
10907
        }
10908
10909 2
        return $haystack;
10910
    }
10911
10912
    /**
10913
     * Removes a prefix ($needle) from the beginning of the string ($haystack).
10914
     *
10915
     * @param string $haystack <p>The string to search in.</p>
10916
     * @param string $needle   <p>The substring to search for.</p>
10917
     *
10918
     * @return string return the sub-string
10919
     */
10920 2
    public static function substr_left(string $haystack, string $needle): string
10921
    {
10922 2
        if ($haystack === '') {
10923 2
            return '';
10924
        }
10925
10926 2
        if ($needle === '') {
10927 2
            return $haystack;
10928
        }
10929
10930 2
        if (self::str_starts_with($haystack, $needle) === true) {
10931 2
            $haystack = (string) \mb_substr($haystack, (int) self::strlen($needle));
10932
        }
10933
10934 2
        return $haystack;
10935
    }
10936
10937
    /**
10938
     * Replace text within a portion of a string.
10939
     *
10940
     * source: https://gist.github.com/stemar/8287074
10941
     *
10942
     * @param string|string[] $str         <p>The input string or an array of stings.</p>
10943
     * @param string|string[] $replacement <p>The replacement string or an array of stings.</p>
10944
     * @param int|int[]       $offset      <p>
10945
     *                                     If start is positive, the replacing will begin at the start'th offset
10946
     *                                     into string.
10947
     *                                     <br><br>
10948
     *                                     If start is negative, the replacing will begin at the start'th character
10949
     *                                     from the end of string.
10950
     *                                     </p>
10951
     * @param int|int[]|null  $length      [optional] <p>If given and is positive, it represents the length of the
10952
     *                                     portion of string which is to be replaced. If it is negative, it
10953
     *                                     represents the number of characters from the end of string at which to
10954
     *                                     stop replacing. If it is not given, then it will default to strlen(
10955
     *                                     string ); i.e. end the replacing at the end of string. Of course, if
10956
     *                                     length is zero then this function will have the effect of inserting
10957
     *                                     replacement into string at the given start offset.</p>
10958
     * @param string          $encoding    [optional] <p>Set the charset for e.g. "mb_" function</p>
10959
     *
10960
     * @return string|string[] The result string is returned. If string is an array then array is returned.
10961
     */
10962 10
    public static function substr_replace(
10963
        $str,
10964
        $replacement,
10965
        $offset,
10966
        $length = null,
10967
        string $encoding = 'UTF-8'
10968
    ) {
10969 10
        if (\is_array($str) === true) {
10970 1
            $num = \count($str);
10971
10972
            // the replacement
10973 1
            if (\is_array($replacement) === true) {
10974 1
                $replacement = \array_slice($replacement, 0, $num);
10975
            } else {
10976 1
                $replacement = \array_pad([$replacement], $num, $replacement);
10977
            }
10978
10979
            // the offset
10980 1
            if (\is_array($offset) === true) {
10981 1
                $offset = \array_slice($offset, 0, $num);
10982 1
                foreach ($offset as &$value_tmp) {
10983 1
                    $value_tmp = (int) $value_tmp === $value_tmp ? $value_tmp : 0;
10984
                }
10985 1
                unset($value_tmp);
10986
            } else {
10987 1
                $offset = \array_pad([$offset], $num, $offset);
10988
            }
10989
10990
            // the length
10991 1
            if ($length === null) {
10992 1
                $length = \array_fill(0, $num, 0);
10993 1
            } elseif (\is_array($length) === true) {
10994 1
                $length = \array_slice($length, 0, $num);
10995 1
                foreach ($length as &$value_tmp_V2) {
10996 1
                    $value_tmp_V2 = (int) $value_tmp_V2 === $value_tmp_V2 ? $value_tmp_V2 : $num;
10997
                }
10998 1
                unset($value_tmp_V2);
10999
            } else {
11000 1
                $length = \array_pad([$length], $num, $length);
11001
            }
11002
11003
            // recursive call
11004 1
            return \array_map([self::class, 'substr_replace'], $str, $replacement, $offset, $length);
11005
        }
11006
11007 10
        if (\is_array($replacement) === true) {
11008 1
            if ($replacement !== []) {
11009 1
                $replacement = $replacement[0];
11010
            } else {
11011 1
                $replacement = '';
11012
            }
11013
        }
11014
11015
        // init
11016 10
        $str = (string) $str;
11017 10
        $replacement = (string) $replacement;
11018
11019 10
        if (\is_array($length) === true) {
11020
            throw new \InvalidArgumentException('Parameter "$length" can only be an array, if "$str" is also an array.');
11021
        }
11022
11023 10
        if (\is_array($offset) === true) {
11024
            throw new \InvalidArgumentException('Parameter "$offset" can only be an array, if "$str" is also an array.');
11025
        }
11026
11027 10
        if ($str === '') {
11028 1
            return $replacement;
11029
        }
11030
11031 9
        if (self::$SUPPORT['mbstring'] === true) {
11032 9
            $string_length = (int) self::strlen($str, $encoding);
11033
11034 9
            if ($offset < 0) {
11035 1
                $offset = (int) \max(0, $string_length + $offset);
11036 9
            } elseif ($offset > $string_length) {
11037 1
                $offset = $string_length;
11038
            }
11039
11040 9
            if ($length !== null && $length < 0) {
11041 1
                $length = (int) \max(0, $string_length - $offset + $length);
11042 9
            } elseif ($length === null || $length > $string_length) {
11043 4
                $length = $string_length;
11044
            }
11045
11046
            /** @noinspection AdditionOperationOnArraysInspection */
11047 9
            if (($offset + $length) > $string_length) {
11048 4
                $length = $string_length - $offset;
11049
            }
11050
11051
            /** @noinspection AdditionOperationOnArraysInspection */
11052 9
            return ((string) \mb_substr($str, 0, $offset, $encoding)) .
11053 9
                   $replacement .
11054 9
                   ((string) \mb_substr($str, $offset + $length, $string_length - $offset - $length, $encoding));
11055
        }
11056
11057
        //
11058
        // fallback for ascii only
11059
        //
11060
11061
        if (ASCII::is_ascii($str)) {
11062
            return ($length === null) ?
11063
                \substr_replace($str, $replacement, $offset) :
11064
                \substr_replace($str, $replacement, $offset, $length);
11065
        }
11066
11067
        //
11068
        // fallback via vanilla php
11069
        //
11070
11071
        \preg_match_all('/./us', $str, $str_matches);
11072
        \preg_match_all('/./us', $replacement, $replacement_matches);
11073
11074
        if ($length === null) {
11075
            $length_tmp = self::strlen($str, $encoding);
11076
            if ($length_tmp === false) {
11077
                // e.g.: non mbstring support + invalid chars
11078
                return '';
11079
            }
11080
            $length = (int) $length_tmp;
11081
        }
11082
11083
        \array_splice($str_matches[0], $offset, $length, $replacement_matches[0]);
11084
11085
        return \implode('', $str_matches[0]);
11086
    }
11087
11088
    /**
11089
     * Removes a suffix ($needle) from the end of the string ($haystack).
11090
     *
11091
     * @param string $haystack <p>The string to search in.</p>
11092
     * @param string $needle   <p>The substring to search for.</p>
11093
     * @param string $encoding [optional] <p>Set the charset for e.g. "mb_" function</p>
11094
     *
11095
     * @return string return the sub-string
11096
     */
11097 2
    public static function substr_right(
11098
        string $haystack,
11099
        string $needle,
11100
        string $encoding = 'UTF-8'
11101
    ): string {
11102 2
        if ($haystack === '') {
11103 2
            return '';
11104
        }
11105
11106 2
        if ($needle === '') {
11107 2
            return $haystack;
11108
        }
11109
11110
        if (
11111 2
            $encoding === 'UTF-8'
11112
            &&
11113 2
            \substr($haystack, -\strlen($needle)) === $needle
11114
        ) {
11115 2
            return (string) \mb_substr($haystack, 0, (int) \mb_strlen($haystack) - (int) \mb_strlen($needle));
11116
        }
11117
11118 2
        if (\substr($haystack, -\strlen($needle)) === $needle) {
11119
            return (string) self::substr(
11120
                $haystack,
11121
                0,
11122
                (int) self::strlen($haystack, $encoding) - (int) self::strlen($needle, $encoding),
11123
                $encoding
11124
            );
11125
        }
11126
11127 2
        return $haystack;
11128
    }
11129
11130
    /**
11131
     * Returns a case swapped version of the string.
11132
     *
11133
     * @param string $str        <p>The input string.</p>
11134
     * @param string $encoding   [optional] <p>Set the charset for e.g. "mb_" function</p>
11135
     * @param bool   $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
11136
     *
11137
     * @return string each character's case swapped
11138
     */
11139 6
    public static function swapCase(string $str, string $encoding = 'UTF-8', bool $clean_utf8 = false): string
11140
    {
11141 6
        if ($str === '') {
11142 1
            return '';
11143
        }
11144
11145 6
        if ($clean_utf8 === true) {
11146
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11147
            // if invalid characters are found in $haystack before $needle
11148 2
            $str = self::clean($str);
11149
        }
11150
11151 6
        if ($encoding === 'UTF-8') {
11152 4
            return (string) (\mb_strtolower($str) ^ \mb_strtoupper($str) ^ $str);
11153
        }
11154
11155 4
        return (string) (self::strtolower($str, $encoding) ^ self::strtoupper($str, $encoding) ^ $str);
11156
    }
11157
11158
    /**
11159
     * Checks whether symfony-polyfills are used.
11160
     *
11161
     * @return bool
11162
     *              <strong>true</strong> if in use, <strong>false</strong> otherwise
11163
     */
11164
    public static function symfony_polyfill_used(): bool
11165
    {
11166
        // init
11167
        $return = false;
11168
11169
        $return_tmp = \extension_loaded('mbstring');
11170
        if ($return_tmp === false && \function_exists('mb_strlen')) {
11171
            $return = true;
11172
        }
11173
11174
        $return_tmp = \extension_loaded('iconv');
11175
        if ($return_tmp === false && \function_exists('iconv')) {
11176
            $return = true;
11177
        }
11178
11179
        return $return;
11180
    }
11181
11182
    /**
11183
     * @param string $str
11184
     * @param int    $tab_length
11185
     *
11186
     * @return string
11187
     */
11188 6
    public static function tabs_to_spaces(string $str, int $tab_length = 4): string
11189
    {
11190 6
        if ($tab_length === 4) {
11191 3
            $spaces = '    ';
11192 3
        } elseif ($tab_length === 2) {
11193 1
            $spaces = '  ';
11194
        } else {
11195 2
            $spaces = \str_repeat(' ', $tab_length);
11196
        }
11197
11198 6
        return \str_replace("\t", $spaces, $str);
11199
    }
11200
11201
    /**
11202
     * Converts the first character of each word in the string to uppercase
11203
     * and all other chars to lowercase.
11204
     *
11205
     * @param string      $str                           <p>The input string.</p>
11206
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
11207
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
11208
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
11209
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
11210
     *
11211
     * @return string
11212
     *                <p>A string with all characters of $str being title-cased.</p>
11213
     */
11214 5
    public static function titlecase(
11215
        string $str,
11216
        string $encoding = 'UTF-8',
11217
        bool $clean_utf8 = false,
11218
        string $lang = null,
11219
        bool $try_to_keep_the_string_length = false
11220
    ): string {
11221 5
        if ($clean_utf8 === true) {
11222
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11223
            // if invalid characters are found in $haystack before $needle
11224
            $str = self::clean($str);
11225
        }
11226
11227
        if (
11228 5
            $lang === null
11229
            &&
11230 5
            $try_to_keep_the_string_length === false
11231
        ) {
11232 5
            if ($encoding === 'UTF-8') {
11233 3
                return \mb_convert_case($str, \MB_CASE_TITLE);
11234
            }
11235
11236 2
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
11237
11238 2
            return \mb_convert_case($str, \MB_CASE_TITLE, $encoding);
11239
        }
11240
11241
        return self::str_titleize(
11242
            $str,
11243
            null,
11244
            $encoding,
11245
            false,
11246
            $lang,
11247
            $try_to_keep_the_string_length,
11248
            false
11249
        );
11250
    }
11251
11252
    /**
11253
     * alias for "UTF8::to_ascii()"
11254
     *
11255
     * @param string $str
11256
     * @param string $subst_chr
11257
     * @param bool   $strict
11258
     *
11259
     * @return string
11260
     *
11261
     * @see UTF8::to_ascii()
11262
     * @deprecated <p>please use "UTF8::to_ascii()"</p>
11263
     */
11264 7
    public static function toAscii(
11265
        string $str,
11266
        string $subst_chr = '?',
11267
        bool $strict = false
11268
    ): string {
11269 7
        return self::to_ascii($str, $subst_chr, $strict);
11270
    }
11271
11272
    /**
11273
     * alias for "UTF8::to_iso8859()"
11274
     *
11275
     * @param string|string[] $str
11276
     *
11277
     * @return string|string[]
11278
     *
11279
     * @see UTF8::to_iso8859()
11280
     * @deprecated <p>please use "UTF8::to_iso8859()"</p>
11281
     */
11282 2
    public static function toIso8859($str)
11283
    {
11284 2
        return self::to_iso8859($str);
11285
    }
11286
11287
    /**
11288
     * alias for "UTF8::to_latin1()"
11289
     *
11290
     * @param string|string[] $str
11291
     *
11292
     * @return string|string[]
11293
     *
11294
     * @see UTF8::to_iso8859()
11295
     * @deprecated <p>please use "UTF8::to_iso8859()"</p>
11296
     */
11297 2
    public static function toLatin1($str)
11298
    {
11299 2
        return self::to_iso8859($str);
11300
    }
11301
11302
    /**
11303
     * alias for "UTF8::to_utf8()"
11304
     *
11305
     * @param string|string[] $str
11306
     *
11307
     * @return string|string[]
11308
     *
11309
     * @see UTF8::to_utf8()
11310
     * @deprecated <p>please use "UTF8::to_utf8()"</p>
11311
     */
11312 2
    public static function toUTF8($str)
11313
    {
11314 2
        return self::to_utf8($str);
11315
    }
11316
11317
    /**
11318
     * Convert a string into ASCII.
11319
     *
11320
     * @param string $str     <p>The input string.</p>
11321
     * @param string $unknown [optional] <p>Character use if character unknown. (default is ?)</p>
11322
     * @param bool   $strict  [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad
11323
     *                        performance</p>
11324
     *
11325
     * @return string
11326
     */
11327 37
    public static function to_ascii(
11328
        string $str,
11329
        string $unknown = '?',
11330
        bool $strict = false
11331
    ): string {
11332 37
        return ASCII::to_transliterate($str, $unknown, $strict);
11333
    }
11334
11335
    /**
11336
     * @param mixed $str
11337
     *
11338
     * @return bool
11339
     */
11340 19
    public static function to_boolean($str): bool
11341
    {
11342
        // init
11343 19
        $str = (string) $str;
11344
11345 19
        if ($str === '') {
11346 2
            return false;
11347
        }
11348
11349
        // Info: http://php.net/manual/en/filter.filters.validate.php
11350
        $map = [
11351 17
            'true'  => true,
11352
            '1'     => true,
11353
            'on'    => true,
11354
            'yes'   => true,
11355
            'false' => false,
11356
            '0'     => false,
11357
            'off'   => false,
11358
            'no'    => false,
11359
        ];
11360
11361 17
        if (isset($map[$str])) {
11362 11
            return $map[$str];
11363
        }
11364
11365 6
        $key = \strtolower($str);
11366 6
        if (isset($map[$key])) {
11367 2
            return $map[$key];
11368
        }
11369
11370 4
        if (\is_numeric($str)) {
11371 2
            return ((float) $str + 0) > 0;
11372
        }
11373
11374 2
        return (bool) \trim($str);
11375
    }
11376
11377
    /**
11378
     * Convert given string to safe filename (and keep string case).
11379
     *
11380
     * @param string $str
11381
     * @param bool   $use_transliterate No transliteration, conversion etc. is done by default - unsafe characters are
11382
     *                                  simply replaced with hyphen.
11383
     * @param string $fallback_char
11384
     *
11385
     * @return string
11386
     */
11387 1
    public static function to_filename(
11388
        string $str,
11389
        bool $use_transliterate = false,
11390
        string $fallback_char = '-'
11391
    ): string {
11392 1
        return ASCII::to_filename(
11393 1
            $str,
11394 1
            $use_transliterate,
11395 1
            $fallback_char
11396
        );
11397
    }
11398
11399
    /**
11400
     * Convert a string into "ISO-8859"-encoding (Latin-1).
11401
     *
11402
     * @param string|string[] $str
11403
     *
11404
     * @return string|string[]
11405
     */
11406 8
    public static function to_iso8859($str)
11407
    {
11408 8
        if (\is_array($str) === true) {
11409 2
            foreach ($str as $k => &$v) {
11410 2
                $v = self::to_iso8859($v);
11411
            }
11412
11413 2
            return $str;
11414
        }
11415
11416 8
        $str = (string) $str;
11417 8
        if ($str === '') {
11418 2
            return '';
11419
        }
11420
11421 8
        return self::utf8_decode($str);
11422
    }
11423
11424
    /**
11425
     * alias for "UTF8::to_iso8859()"
11426
     *
11427
     * @param string|string[] $str
11428
     *
11429
     * @return string|string[]
11430
     *
11431
     * @see UTF8::to_iso8859()
11432
     * @deprecated <p>please use "UTF8::to_iso8859()"</p>
11433
     */
11434 2
    public static function to_latin1($str)
11435
    {
11436 2
        return self::to_iso8859($str);
11437
    }
11438
11439
    /**
11440
     * This function leaves UTF-8 characters alone, while converting almost all non-UTF8 to UTF8.
11441
     *
11442
     * <ul>
11443
     * <li>It decode UTF-8 codepoints and Unicode escape sequences.</li>
11444
     * <li>It assumes that the encoding of the original string is either WINDOWS-1252 or ISO-8859.</li>
11445
     * <li>WARNING: It does not remove invalid UTF-8 characters, so you maybe need to use "UTF8::clean()" for this
11446
     * case.</li>
11447
     * </ul>
11448
     *
11449
     * @param string|string[] $str                        <p>Any string or array.</p>
11450
     * @param bool            $decode_html_entity_to_utf8 <p>Set to true, if you need to decode html-entities.</p>
11451
     *
11452
     * @return string|string[] the UTF-8 encoded string
11453
     */
11454 43
    public static function to_utf8($str, bool $decode_html_entity_to_utf8 = false)
11455
    {
11456 43
        if (\is_array($str) === true) {
11457 4
            foreach ($str as $k => &$v) {
11458 4
                $v = self::to_utf8($v, $decode_html_entity_to_utf8);
11459
            }
11460
11461 4
            return $str;
11462
        }
11463
11464 43
        $str = (string) $str;
11465 43
        if ($str === '') {
11466 7
            return $str;
11467
        }
11468
11469 43
        $max = \strlen($str);
11470 43
        $buf = '';
11471
11472 43
        for ($i = 0; $i < $max; ++$i) {
11473 43
            $c1 = $str[$i];
11474
11475 43
            if ($c1 >= "\xC0") { // should be converted to UTF8, if it's not UTF8 already
11476
11477 39
                if ($c1 <= "\xDF") { // looks like 2 bytes UTF8
11478
11479 36
                    $c2 = $i + 1 >= $max ? "\x00" : $str[$i + 1];
11480
11481 36
                    if ($c2 >= "\x80" && $c2 <= "\xBF") { // yeah, almost sure it's UTF8 already
11482 22
                        $buf .= $c1 . $c2;
11483 22
                        ++$i;
11484
                    } else { // not valid UTF8 - convert it
11485 36
                        $buf .= self::to_utf8_convert_helper($c1);
11486
                    }
11487 36
                } elseif ($c1 >= "\xE0" && $c1 <= "\xEF") { // looks like 3 bytes UTF8
11488
11489 35
                    $c2 = $i + 1 >= $max ? "\x00" : $str[$i + 1];
11490 35
                    $c3 = $i + 2 >= $max ? "\x00" : $str[$i + 2];
11491
11492 35
                    if ($c2 >= "\x80" && $c2 <= "\xBF" && $c3 >= "\x80" && $c3 <= "\xBF") { // yeah, almost sure it's UTF8 already
11493 17
                        $buf .= $c1 . $c2 . $c3;
11494 17
                        $i += 2;
11495
                    } else { // not valid UTF8 - convert it
11496 35
                        $buf .= self::to_utf8_convert_helper($c1);
11497
                    }
11498 28
                } elseif ($c1 >= "\xF0" && $c1 <= "\xF7") { // looks like 4 bytes UTF8
11499
11500 28
                    $c2 = $i + 1 >= $max ? "\x00" : $str[$i + 1];
11501 28
                    $c3 = $i + 2 >= $max ? "\x00" : $str[$i + 2];
11502 28
                    $c4 = $i + 3 >= $max ? "\x00" : $str[$i + 3];
11503
11504 28
                    if ($c2 >= "\x80" && $c2 <= "\xBF" && $c3 >= "\x80" && $c3 <= "\xBF" && $c4 >= "\x80" && $c4 <= "\xBF") { // yeah, almost sure it's UTF8 already
11505 10
                        $buf .= $c1 . $c2 . $c3 . $c4;
11506 10
                        $i += 3;
11507
                    } else { // not valid UTF8 - convert it
11508 28
                        $buf .= self::to_utf8_convert_helper($c1);
11509
                    }
11510
                } else { // doesn't look like UTF8, but should be converted
11511
11512 39
                    $buf .= self::to_utf8_convert_helper($c1);
11513
                }
11514 40
            } elseif (($c1 & "\xC0") === "\x80") { // needs conversion
11515
11516 4
                $buf .= self::to_utf8_convert_helper($c1);
11517
            } else { // it doesn't need conversion
11518
11519 40
                $buf .= $c1;
11520
            }
11521
        }
11522
11523
        // decode unicode escape sequences + unicode surrogate pairs
11524 43
        $buf = \preg_replace_callback(
11525 43
            '/\\\\u([dD][89abAB][0-9a-fA-F]{2})\\\\u([dD][cdefCDEF][\da-fA-F]{2})|\\\\u([0-9a-fA-F]{4})/',
11526
            /**
11527
             * @param array $matches
11528
             *
11529
             * @return string
11530
             */
11531
            static function (array $matches): string {
11532 13
                if (isset($matches[3])) {
11533 13
                    $cp = (int) \hexdec($matches[3]);
11534
                } else {
11535
                    // http://unicode.org/faq/utf_bom.html#utf16-4
11536 1
                    $cp = ((int) \hexdec($matches[1]) << 10)
11537 1
                          + (int) \hexdec($matches[2])
11538 1
                          + 0x10000
11539 1
                          - (0xD800 << 10)
11540 1
                          - 0xDC00;
11541
                }
11542
11543
                // https://github.com/php/php-src/blob/php-7.3.2/ext/standard/html.c#L471
11544
                //
11545
                // php_utf32_utf8(unsigned char *buf, unsigned k)
11546
11547 13
                if ($cp < 0x80) {
11548 8
                    return (string) self::chr($cp);
11549
                }
11550
11551 10
                if ($cp < 0xA0) {
11552
                    /** @noinspection UnnecessaryCastingInspection */
11553
                    return (string) self::chr(0xC0 | $cp >> 6) . (string) self::chr(0x80 | $cp & 0x3F);
11554
                }
11555
11556 10
                return self::decimal_to_chr($cp);
11557 43
            },
11558 43
            $buf
11559
        );
11560
11561 43
        if ($buf === null) {
11562
            return '';
11563
        }
11564
11565
        // decode UTF-8 codepoints
11566 43
        if ($decode_html_entity_to_utf8 === true) {
11567 3
            $buf = self::html_entity_decode($buf);
11568
        }
11569
11570 43
        return $buf;
11571
    }
11572
11573
    /**
11574
     * Strip whitespace or other characters from the beginning and end of a UTF-8 string.
11575
     *
11576
     * INFO: This is slower then "trim()"
11577
     *
11578
     * We can only use the original-function, if we use <= 7-Bit in the string / chars
11579
     * but the check for ASCII (7-Bit) cost more time, then we can safe here.
11580
     *
11581
     * @param string      $str   <p>The string to be trimmed</p>
11582
     * @param string|null $chars [optional] <p>Optional characters to be stripped</p>
11583
     *
11584
     * @return string the trimmed string
11585
     */
11586 56
    public static function trim(string $str = '', string $chars = null): string
11587
    {
11588 56
        if ($str === '') {
11589 9
            return '';
11590
        }
11591
11592 49
        if (self::$SUPPORT['mbstring'] === true) {
11593 49
            if ($chars) {
11594
                /** @noinspection PregQuoteUsageInspection */
11595 27
                $chars = \preg_quote($chars);
11596 27
                $pattern = "^[${chars}]+|[${chars}]+\$";
11597
            } else {
11598 22
                $pattern = '^[\\s]+|[\\s]+$';
11599
            }
11600
11601
            /** @noinspection PhpComposerExtensionStubsInspection */
11602 49
            return (string) \mb_ereg_replace($pattern, '', $str);
11603
        }
11604
11605 8
        if ($chars) {
11606
            $chars = \preg_quote($chars, '/');
11607
            $pattern = "^[${chars}]+|[${chars}]+\$";
11608
        } else {
11609 8
            $pattern = '^[\\s]+|[\\s]+$';
11610
        }
11611
11612 8
        return self::regex_replace($str, $pattern, '', '', '/');
11613
    }
11614
11615
    /**
11616
     * Makes string's first char uppercase.
11617
     *
11618
     * @param string      $str                           <p>The input string.</p>
11619
     * @param string      $encoding                      [optional] <p>Set the charset for e.g. "mb_" function</p>
11620
     * @param bool        $clean_utf8                    [optional] <p>Remove non UTF-8 chars from the string.</p>
11621
     * @param string|null $lang                          [optional] <p>Set the language for special cases: az, el, lt, tr</p>
11622
     * @param bool        $try_to_keep_the_string_length [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
11623
     *
11624
     * @return string the resulting string
11625
     */
11626 69
    public static function ucfirst(
11627
        string $str,
11628
        string $encoding = 'UTF-8',
11629
        bool $clean_utf8 = false,
11630
        string $lang = null,
11631
        bool $try_to_keep_the_string_length = false
11632
    ): string {
11633 69
        if ($str === '') {
11634 3
            return '';
11635
        }
11636
11637 68
        if ($clean_utf8 === true) {
11638
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11639
            // if invalid characters are found in $haystack before $needle
11640 1
            $str = self::clean($str);
11641
        }
11642
11643 68
        $use_mb_functions = $lang === null && $try_to_keep_the_string_length === false;
11644
11645 68
        if ($encoding === 'UTF-8') {
11646 22
            $str_part_two = (string) \mb_substr($str, 1);
11647
11648 22
            if ($use_mb_functions === true) {
11649 22
                $str_part_one = \mb_strtoupper(
11650 22
                    (string) \mb_substr($str, 0, 1)
11651
                );
11652
            } else {
11653
                $str_part_one = self::strtoupper(
11654
                    (string) \mb_substr($str, 0, 1),
11655
                    $encoding,
11656
                    false,
11657
                    $lang,
11658 22
                    $try_to_keep_the_string_length
11659
                );
11660
            }
11661
        } else {
11662 47
            $encoding = self::normalize_encoding($encoding, 'UTF-8');
11663
11664 47
            $str_part_two = (string) self::substr($str, 1, null, $encoding);
11665
11666 47
            if ($use_mb_functions === true) {
11667 47
                $str_part_one = \mb_strtoupper(
11668 47
                    (string) \mb_substr($str, 0, 1, $encoding),
11669 47
                    $encoding
11670
                );
11671
            } else {
11672
                $str_part_one = self::strtoupper(
11673
                    (string) self::substr($str, 0, 1, $encoding),
11674
                    $encoding,
11675
                    false,
11676
                    $lang,
11677
                    $try_to_keep_the_string_length
11678
                );
11679
            }
11680
        }
11681
11682 68
        return $str_part_one . $str_part_two;
11683
    }
11684
11685
    /**
11686
     * alias for "UTF8::ucfirst()"
11687
     *
11688
     * @param string $str
11689
     * @param string $encoding
11690
     * @param bool   $clean_utf8
11691
     *
11692
     * @return string
11693
     *
11694
     * @see UTF8::ucfirst()
11695
     * @deprecated <p>please use "UTF8::ucfirst()"</p>
11696
     */
11697 1
    public static function ucword(
11698
        string $str,
11699
        string $encoding = 'UTF-8',
11700
        bool $clean_utf8 = false
11701
    ): string {
11702 1
        return self::ucfirst($str, $encoding, $clean_utf8);
11703
    }
11704
11705
    /**
11706
     * Uppercase for all words in the string.
11707
     *
11708
     * @param string   $str        <p>The input string.</p>
11709
     * @param string[] $exceptions [optional] <p>Exclusion for some words.</p>
11710
     * @param string   $char_list  [optional] <p>Additional chars that contains to words and do not start a new
11711
     *                             word.</p>
11712
     * @param string   $encoding   [optional] <p>Set the charset.</p>
11713
     * @param bool     $clean_utf8 [optional] <p>Remove non UTF-8 chars from the string.</p>
11714
     *
11715
     * @return string
11716
     */
11717 8
    public static function ucwords(
11718
        string $str,
11719
        array $exceptions = [],
11720
        string $char_list = '',
11721
        string $encoding = 'UTF-8',
11722
        bool $clean_utf8 = false
11723
    ): string {
11724 8
        if (!$str) {
11725 2
            return '';
11726
        }
11727
11728
        // INFO: mb_convert_case($str, MB_CASE_TITLE);
11729
        // -> MB_CASE_TITLE didn't only uppercase the first letter, it also lowercase all other letters
11730
11731 7
        if ($clean_utf8 === true) {
11732
            // "mb_strpos()" and "iconv_strpos()" returns wrong position,
11733
            // if invalid characters are found in $haystack before $needle
11734 1
            $str = self::clean($str);
11735
        }
11736
11737 7
        $use_php_default_functions = !(bool) ($char_list . \implode('', $exceptions));
11738
11739
        if (
11740 7
            $use_php_default_functions === true
11741
            &&
11742 7
            ASCII::is_ascii($str) === true
11743
        ) {
11744
            return \ucwords($str);
11745
        }
11746
11747 7
        $words = self::str_to_words($str, $char_list);
11748 7
        $use_exceptions = $exceptions !== [];
11749
11750 7
        $words_str = '';
11751 7
        foreach ($words as &$word) {
11752 7
            if (!$word) {
11753 7
                continue;
11754
            }
11755
11756
            if (
11757 7
                $use_exceptions === false
11758
                ||
11759 7
                !\in_array($word, $exceptions, true)
11760
            ) {
11761 7
                $words_str .= self::ucfirst($word, $encoding);
11762
            } else {
11763 7
                $words_str .= $word;
11764
            }
11765
        }
11766
11767 7
        return $words_str;
11768
    }
11769
11770
    /**
11771
     * Multi decode HTML entity + fix urlencoded-win1252-chars.
11772
     *
11773
     * e.g:
11774
     * 'test+test'                     => 'test test'
11775
     * 'D&#252;sseldorf'               => 'Düsseldorf'
11776
     * 'D%FCsseldorf'                  => 'Düsseldorf'
11777
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
11778
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
11779
     * 'Düsseldorf'                   => 'Düsseldorf'
11780
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
11781
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
11782
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
11783
     *
11784
     * @param string $str          <p>The input string.</p>
11785
     * @param bool   $multi_decode <p>Decode as often as possible.</p>
11786
     *
11787
     * @return string
11788
     */
11789 4
    public static function urldecode(string $str, bool $multi_decode = true): string
11790
    {
11791 4
        if ($str === '') {
11792 3
            return '';
11793
        }
11794
11795
        if (
11796 4
            \strpos($str, '&') === false
11797
            &&
11798 4
            \strpos($str, '%') === false
11799
            &&
11800 4
            \strpos($str, '+') === false
11801
            &&
11802 4
            \strpos($str, '\u') === false
11803
        ) {
11804 3
            return self::fix_simple_utf8($str);
11805
        }
11806
11807 4
        $str = self::urldecode_unicode_helper($str);
11808
11809 4
        if ($multi_decode === true) {
11810
            do {
11811 3
                $str_compare = $str;
11812
11813
                /**
11814
                 * @psalm-suppress PossiblyInvalidArgument
11815
                 */
11816 3
                $str = self::fix_simple_utf8(
11817 3
                    \urldecode(
11818 3
                        self::html_entity_decode(
11819 3
                            self::to_utf8($str),
11820 3
                            \ENT_QUOTES | \ENT_HTML5
11821
                        )
11822
                    )
11823
                );
11824 3
            } while ($str_compare !== $str);
11825
        } else {
11826
            /**
11827
             * @psalm-suppress PossiblyInvalidArgument
11828
             */
11829 1
            $str = self::fix_simple_utf8(
11830 1
                \urldecode(
11831 1
                    self::html_entity_decode(
11832 1
                        self::to_utf8($str),
11833 1
                        \ENT_QUOTES | \ENT_HTML5
11834
                    )
11835
                )
11836
            );
11837
        }
11838
11839 4
        return $str;
11840
    }
11841
11842
    /**
11843
     * Return a array with "urlencoded"-win1252 -> UTF-8
11844
     *
11845
     * @return string[]
11846
     *
11847
     * @deprecated <p>please use the "UTF8::urldecode()" function to decode a string</p>
11848
     */
11849 2
    public static function urldecode_fix_win1252_chars(): array
11850
    {
11851
        return [
11852 2
            '%20' => ' ',
11853
            '%21' => '!',
11854
            '%22' => '"',
11855
            '%23' => '#',
11856
            '%24' => '$',
11857
            '%25' => '%',
11858
            '%26' => '&',
11859
            '%27' => "'",
11860
            '%28' => '(',
11861
            '%29' => ')',
11862
            '%2A' => '*',
11863
            '%2B' => '+',
11864
            '%2C' => ',',
11865
            '%2D' => '-',
11866
            '%2E' => '.',
11867
            '%2F' => '/',
11868
            '%30' => '0',
11869
            '%31' => '1',
11870
            '%32' => '2',
11871
            '%33' => '3',
11872
            '%34' => '4',
11873
            '%35' => '5',
11874
            '%36' => '6',
11875
            '%37' => '7',
11876
            '%38' => '8',
11877
            '%39' => '9',
11878
            '%3A' => ':',
11879
            '%3B' => ';',
11880
            '%3C' => '<',
11881
            '%3D' => '=',
11882
            '%3E' => '>',
11883
            '%3F' => '?',
11884
            '%40' => '@',
11885
            '%41' => 'A',
11886
            '%42' => 'B',
11887
            '%43' => 'C',
11888
            '%44' => 'D',
11889
            '%45' => 'E',
11890
            '%46' => 'F',
11891
            '%47' => 'G',
11892
            '%48' => 'H',
11893
            '%49' => 'I',
11894
            '%4A' => 'J',
11895
            '%4B' => 'K',
11896
            '%4C' => 'L',
11897
            '%4D' => 'M',
11898
            '%4E' => 'N',
11899
            '%4F' => 'O',
11900
            '%50' => 'P',
11901
            '%51' => 'Q',
11902
            '%52' => 'R',
11903
            '%53' => 'S',
11904
            '%54' => 'T',
11905
            '%55' => 'U',
11906
            '%56' => 'V',
11907
            '%57' => 'W',
11908
            '%58' => 'X',
11909
            '%59' => 'Y',
11910
            '%5A' => 'Z',
11911
            '%5B' => '[',
11912
            '%5C' => '\\',
11913
            '%5D' => ']',
11914
            '%5E' => '^',
11915
            '%5F' => '_',
11916
            '%60' => '`',
11917
            '%61' => 'a',
11918
            '%62' => 'b',
11919
            '%63' => 'c',
11920
            '%64' => 'd',
11921
            '%65' => 'e',
11922
            '%66' => 'f',
11923
            '%67' => 'g',
11924
            '%68' => 'h',
11925
            '%69' => 'i',
11926
            '%6A' => 'j',
11927
            '%6B' => 'k',
11928
            '%6C' => 'l',
11929
            '%6D' => 'm',
11930
            '%6E' => 'n',
11931
            '%6F' => 'o',
11932
            '%70' => 'p',
11933
            '%71' => 'q',
11934
            '%72' => 'r',
11935
            '%73' => 's',
11936
            '%74' => 't',
11937
            '%75' => 'u',
11938
            '%76' => 'v',
11939
            '%77' => 'w',
11940
            '%78' => 'x',
11941
            '%79' => 'y',
11942
            '%7A' => 'z',
11943
            '%7B' => '{',
11944
            '%7C' => '|',
11945
            '%7D' => '}',
11946
            '%7E' => '~',
11947
            '%7F' => '',
11948
            '%80' => '`',
11949
            '%81' => '',
11950
            '%82' => '‚',
11951
            '%83' => 'ƒ',
11952
            '%84' => '„',
11953
            '%85' => '…',
11954
            '%86' => '†',
11955
            '%87' => '‡',
11956
            '%88' => 'ˆ',
11957
            '%89' => '‰',
11958
            '%8A' => 'Š',
11959
            '%8B' => '‹',
11960
            '%8C' => 'Œ',
11961
            '%8D' => '',
11962
            '%8E' => 'Ž',
11963
            '%8F' => '',
11964
            '%90' => '',
11965
            '%91' => '‘',
11966
            '%92' => '’',
11967
            '%93' => '“',
11968
            '%94' => '”',
11969
            '%95' => '•',
11970
            '%96' => '–',
11971
            '%97' => '—',
11972
            '%98' => '˜',
11973
            '%99' => '™',
11974
            '%9A' => 'š',
11975
            '%9B' => '›',
11976
            '%9C' => 'œ',
11977
            '%9D' => '',
11978
            '%9E' => 'ž',
11979
            '%9F' => 'Ÿ',
11980
            '%A0' => '',
11981
            '%A1' => '¡',
11982
            '%A2' => '¢',
11983
            '%A3' => '£',
11984
            '%A4' => '¤',
11985
            '%A5' => '¥',
11986
            '%A6' => '¦',
11987
            '%A7' => '§',
11988
            '%A8' => '¨',
11989
            '%A9' => '©',
11990
            '%AA' => 'ª',
11991
            '%AB' => '«',
11992
            '%AC' => '¬',
11993
            '%AD' => '',
11994
            '%AE' => '®',
11995
            '%AF' => '¯',
11996
            '%B0' => '°',
11997
            '%B1' => '±',
11998
            '%B2' => '²',
11999
            '%B3' => '³',
12000
            '%B4' => '´',
12001
            '%B5' => 'µ',
12002
            '%B6' => '¶',
12003
            '%B7' => '·',
12004
            '%B8' => '¸',
12005
            '%B9' => '¹',
12006
            '%BA' => 'º',
12007
            '%BB' => '»',
12008
            '%BC' => '¼',
12009
            '%BD' => '½',
12010
            '%BE' => '¾',
12011
            '%BF' => '¿',
12012
            '%C0' => 'À',
12013
            '%C1' => 'Á',
12014
            '%C2' => 'Â',
12015
            '%C3' => 'Ã',
12016
            '%C4' => 'Ä',
12017
            '%C5' => 'Å',
12018
            '%C6' => 'Æ',
12019
            '%C7' => 'Ç',
12020
            '%C8' => 'È',
12021
            '%C9' => 'É',
12022
            '%CA' => 'Ê',
12023
            '%CB' => 'Ë',
12024
            '%CC' => 'Ì',
12025
            '%CD' => 'Í',
12026
            '%CE' => 'Î',
12027
            '%CF' => 'Ï',
12028
            '%D0' => 'Ð',
12029
            '%D1' => 'Ñ',
12030
            '%D2' => 'Ò',
12031
            '%D3' => 'Ó',
12032
            '%D4' => 'Ô',
12033
            '%D5' => 'Õ',
12034
            '%D6' => 'Ö',
12035
            '%D7' => '×',
12036
            '%D8' => 'Ø',
12037
            '%D9' => 'Ù',
12038
            '%DA' => 'Ú',
12039
            '%DB' => 'Û',
12040
            '%DC' => 'Ü',
12041
            '%DD' => 'Ý',
12042
            '%DE' => 'Þ',
12043
            '%DF' => 'ß',
12044
            '%E0' => 'à',
12045
            '%E1' => 'á',
12046
            '%E2' => 'â',
12047
            '%E3' => 'ã',
12048
            '%E4' => 'ä',
12049
            '%E5' => 'å',
12050
            '%E6' => 'æ',
12051
            '%E7' => 'ç',
12052
            '%E8' => 'è',
12053
            '%E9' => 'é',
12054
            '%EA' => 'ê',
12055
            '%EB' => 'ë',
12056
            '%EC' => 'ì',
12057
            '%ED' => 'í',
12058
            '%EE' => 'î',
12059
            '%EF' => 'ï',
12060
            '%F0' => 'ð',
12061
            '%F1' => 'ñ',
12062
            '%F2' => 'ò',
12063
            '%F3' => 'ó',
12064
            '%F4' => 'ô',
12065
            '%F5' => 'õ',
12066
            '%F6' => 'ö',
12067
            '%F7' => '÷',
12068
            '%F8' => 'ø',
12069
            '%F9' => 'ù',
12070
            '%FA' => 'ú',
12071
            '%FB' => 'û',
12072
            '%FC' => 'ü',
12073
            '%FD' => 'ý',
12074
            '%FE' => 'þ',
12075
            '%FF' => 'ÿ',
12076
        ];
12077
    }
12078
12079
    /**
12080
     * Decodes a UTF-8 string to ISO-8859-1.
12081
     *
12082
     * @param string $str             <p>The input string.</p>
12083
     * @param bool   $keep_utf8_chars
12084
     *
12085
     * @return string
12086
     */
12087 14
    public static function utf8_decode(string $str, bool $keep_utf8_chars = false): string
12088
    {
12089 14
        if ($str === '') {
12090 6
            return '';
12091
        }
12092
12093
        // save for later comparision
12094 14
        $str_backup = $str;
12095 14
        $len = \strlen($str);
12096
12097 14
        if (self::$ORD === null) {
12098
            self::$ORD = self::getData('ord');
12099
        }
12100
12101 14
        if (self::$CHR === null) {
12102
            self::$CHR = self::getData('chr');
12103
        }
12104
12105 14
        $no_char_found = '?';
12106
        /** @noinspection ForeachInvariantsInspection */
12107 14
        for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) {
12108 14
            switch ($str[$i] & "\xF0") {
12109 14
                case "\xC0":
12110 13
                case "\xD0":
12111 13
                    $c = (self::$ORD[$str[$i] & "\x1F"] << 6) | self::$ORD[$str[++$i] & "\x3F"];
12112 13
                    $str[$j] = $c < 256 ? self::$CHR[$c] : $no_char_found;
12113
12114 13
                    break;
12115
12116
                /** @noinspection PhpMissingBreakStatementInspection */
12117 13
                case "\xF0":
12118
                    ++$i;
12119
12120
                // no break
12121
12122 13
                case "\xE0":
12123 11
                    $str[$j] = $no_char_found;
12124 11
                    $i += 2;
12125
12126 11
                    break;
12127
12128
                default:
12129 12
                    $str[$j] = $str[$i];
12130
            }
12131
        }
12132
12133
        /** @var false|string $return - needed for PhpStan (stubs error) */
12134 14
        $return = \substr($str, 0, $j);
12135 14
        if ($return === false) {
12136
            $return = '';
12137
        }
12138
12139
        if (
12140 14
            $keep_utf8_chars === true
12141
            &&
12142 14
            (int) self::strlen($return) >= (int) self::strlen($str_backup)
12143
        ) {
12144 2
            return $str_backup;
12145
        }
12146
12147 14
        return $return;
12148
    }
12149
12150
    /**
12151
     * Encodes an ISO-8859-1 string to UTF-8.
12152
     *
12153
     * @param string $str <p>The input string.</p>
12154
     *
12155
     * @return string
12156
     */
12157 14
    public static function utf8_encode(string $str): string
12158
    {
12159 14
        if ($str === '') {
12160 14
            return '';
12161
        }
12162
12163
        /** @var false|string $str - the polyfill maybe return false */
12164 14
        $str = \utf8_encode($str);
0 ignored issues
show
Bug introduced by
It seems like $str can also be of type false; however, parameter $data of utf8_encode() 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

12164
        $str = \utf8_encode(/** @scrutinizer ignore-type */ $str);
Loading history...
12165
12166
        /** @noinspection CallableParameterUseCaseInTypeContextInspection */
12167
        /** @psalm-suppress TypeDoesNotContainType */
12168 14
        if ($str === false) {
12169
            return '';
12170
        }
12171
12172 14
        return $str;
12173
    }
12174
12175
    /**
12176
     * fix -> utf8-win1252 chars
12177
     *
12178
     * @param string $str <p>The input string.</p>
12179
     *
12180
     * @return string
12181
     *
12182
     * @deprecated <p>please use "UTF8::fix_simple_utf8()"</p>
12183
     */
12184 2
    public static function utf8_fix_win1252_chars(string $str): string
12185
    {
12186 2
        return self::fix_simple_utf8($str);
12187
    }
12188
12189
    /**
12190
     * Returns an array with all utf8 whitespace characters.
12191
     *
12192
     * @see http://www.bogofilter.org/pipermail/bogofilter/2003-March/001889.html
12193
     *
12194
     * @return string[]
12195
     *                  An array with all known whitespace characters as values and the type of whitespace as keys
12196
     *                  as defined in above URL
12197
     */
12198 2
    public static function whitespace_table(): array
12199
    {
12200 2
        return self::$WHITESPACE_TABLE;
12201
    }
12202
12203
    /**
12204
     * Limit the number of words in a string.
12205
     *
12206
     * @param string $str        <p>The input string.</p>
12207
     * @param int    $limit      <p>The limit of words as integer.</p>
12208
     * @param string $str_add_on <p>Replacement for the striped string.</p>
12209
     *
12210
     * @return string
12211
     */
12212 2
    public static function words_limit(
12213
        string $str,
12214
        int $limit = 100,
12215
        string $str_add_on = '…'
12216
    ): string {
12217 2
        if ($str === '' || $limit < 1) {
12218 2
            return '';
12219
        }
12220
12221 2
        \preg_match('/^\\s*+(?:[^\\s]++\\s*+){1,' . $limit . '}/u', $str, $matches);
12222
12223
        if (
12224 2
            !isset($matches[0])
12225
            ||
12226 2
            \mb_strlen($str) === (int) \mb_strlen($matches[0])
12227
        ) {
12228 2
            return $str;
12229
        }
12230
12231 2
        return \rtrim($matches[0]) . $str_add_on;
12232
    }
12233
12234
    /**
12235
     * Wraps a string to a given number of characters
12236
     *
12237
     * @see http://php.net/manual/en/function.wordwrap.php
12238
     *
12239
     * @param string $str   <p>The input string.</p>
12240
     * @param int    $width [optional] <p>The column width.</p>
12241
     * @param string $break [optional] <p>The line is broken using the optional break parameter.</p>
12242
     * @param bool   $cut   [optional] <p>
12243
     *                      If the cut is set to true, the string is
12244
     *                      always wrapped at or before the specified width. So if you have
12245
     *                      a word that is larger than the given width, it is broken apart.
12246
     *                      </p>
12247
     *
12248
     * @return string
12249
     *                <p>The given string wrapped at the specified column.</p>
12250
     */
12251 12
    public static function wordwrap(
12252
        string $str,
12253
        int $width = 75,
12254
        string $break = "\n",
12255
        bool $cut = false
12256
    ): string {
12257 12
        if ($str === '' || $break === '') {
12258 4
            return '';
12259
        }
12260
12261 10
        $str_split = \explode($break, $str);
12262 10
        if ($str_split === false) {
12263
            return '';
12264
        }
12265
12266 10
        $chars = [];
12267 10
        $word_split = '';
12268 10
        foreach ($str_split as $i => $i_value) {
12269 10
            if ($i) {
12270 3
                $chars[] = $break;
12271 3
                $word_split .= '#';
12272
            }
12273
12274 10
            foreach (self::str_split($i_value) as $c) {
12275 10
                $chars[] = $c;
12276 10
                if ($c === ' ') {
12277 3
                    $word_split .= ' ';
12278
                } else {
12279 10
                    $word_split .= '?';
12280
                }
12281
            }
12282
        }
12283
12284 10
        $str_return = '';
12285 10
        $j = 0;
12286 10
        $b = -1;
12287 10
        $i = -1;
12288 10
        $word_split = \wordwrap($word_split, $width, '#', $cut);
12289
12290 10
        $max = \mb_strlen($word_split);
12291 10
        while (($b = \mb_strpos($word_split, '#', $b + 1)) !== false) {
12292 8
            for (++$i; $i < $b; ++$i) {
12293 8
                $str_return .= $chars[$j];
12294 8
                unset($chars[$j++]);
12295
12296
                // prevent endless loop, e.g. if there is a error in the "mb_*" polyfill
12297 8
                if ($i > $max) {
12298
                    break 2;
12299
                }
12300
            }
12301
12302
            if (
12303 8
                $break === $chars[$j]
12304
                ||
12305 8
                $chars[$j] === ' '
12306
            ) {
12307 5
                unset($chars[$j++]);
12308
            }
12309
12310 8
            $str_return .= $break;
12311
12312
            // prevent endless loop, e.g. if there is a error in the "mb_*" polyfill
12313 8
            if ($b > $max) {
12314
                break;
12315
            }
12316
        }
12317
12318 10
        return $str_return . \implode('', $chars);
12319
    }
12320
12321
    /**
12322
     * Line-Wrap the string after $limit, but split the string by "$delimiter" before ...
12323
     *    ... so that we wrap the per line.
12324
     *
12325
     * @param string      $str             <p>The input string.</p>
12326
     * @param int         $width           [optional] <p>The column width.</p>
12327
     * @param string      $break           [optional] <p>The line is broken using the optional break parameter.</p>
12328
     * @param bool        $cut             [optional] <p>
12329
     *                                     If the cut is set to true, the string is
12330
     *                                     always wrapped at or before the specified width. So if you have
12331
     *                                     a word that is larger than the given width, it is broken apart.
12332
     *                                     </p>
12333
     * @param bool        $add_final_break [optional] <p>
12334
     *                                     If this flag is true, then the method will add a $break at the end
12335
     *                                     of the result string.
12336
     *                                     </p>
12337
     * @param string|null $delimiter       [optional] <p>
12338
     *                                     You can change the default behavior, where we split the string by newline.
12339
     *                                     </p>
12340
     *
12341
     * @return string
12342
     */
12343 1
    public static function wordwrap_per_line(
12344
        string $str,
12345
        int $width = 75,
12346
        string $break = "\n",
12347
        bool $cut = false,
12348
        bool $add_final_break = true,
12349
        string $delimiter = null
12350
    ): string {
12351 1
        if ($delimiter === null) {
12352 1
            $strings = \preg_split('/\\r\\n|\\r|\\n/', $str);
12353
        } else {
12354 1
            $strings = \explode($delimiter, $str);
12355
        }
12356
12357 1
        $string_helper_array = [];
12358 1
        if ($strings !== false) {
12359 1
            foreach ($strings as $value) {
12360 1
                $string_helper_array[] = self::wordwrap($value, $width, $break, $cut);
12361
            }
12362
        }
12363
12364 1
        if ($add_final_break) {
12365 1
            $final_break = $break;
12366
        } else {
12367 1
            $final_break = '';
12368
        }
12369
12370 1
        return \implode($delimiter ?? "\n", $string_helper_array) . $final_break;
12371
    }
12372
12373
    /**
12374
     * Returns an array of Unicode White Space characters.
12375
     *
12376
     * @return string[] an array with numeric code point as key and White Space Character as value
12377
     */
12378 2
    public static function ws(): array
12379
    {
12380 2
        return self::$WHITESPACE;
12381
    }
12382
12383
    /**
12384
     * Checks whether the passed string contains only byte sequences that are valid UTF-8 characters.
12385
     *
12386
     * @see http://hsivonen.iki.fi/php-utf8/
12387
     *
12388
     * @param string $str    <p>The string to be checked.</p>
12389
     * @param bool   $strict <p>Check also if the string is not UTF-16 or UTF-32.</p>
12390
     *
12391
     * @return bool
12392
     *
12393
     * @noinspection ReturnTypeCanBeDeclaredInspection
12394
     */
12395 108
    private static function is_utf8_string(string $str, bool $strict = false)
12396
    {
12397 108
        if ($str === '') {
12398 14
            return true;
12399
        }
12400
12401 102
        if ($strict === true) {
12402 2
            $is_binary = self::is_binary($str, true);
12403
12404 2
            if ($is_binary && self::is_utf16($str, false) !== false) {
12405 2
                return false;
12406
            }
12407
12408
            if ($is_binary && self::is_utf32($str, false) !== false) {
12409
                return false;
12410
            }
12411
        }
12412
12413 102
        if (self::pcre_utf8_support() !== true) {
12414
            // If even just the first character can be matched, when the /u
12415
            // modifier is used, then it's valid UTF-8. If the UTF-8 is somehow
12416
            // invalid, nothing at all will match, even if the string contains
12417
            // some valid sequences
12418
            return \preg_match('/^./us', $str, $ar) === 1;
12419
        }
12420
12421 102
        $mState = 0; // cached expected number of octets after the current octet
12422
        // until the beginning of the next UTF8 character sequence
12423 102
        $mUcs4 = 0; // cached Unicode character
12424 102
        $mBytes = 1; // cached expected number of octets in the current sequence
12425
12426 102
        if (self::$ORD === null) {
12427
            self::$ORD = self::getData('ord');
12428
        }
12429
12430 102
        $len = \strlen($str);
12431
        /** @noinspection ForeachInvariantsInspection */
12432 102
        for ($i = 0; $i < $len; ++$i) {
12433 102
            $in = self::$ORD[$str[$i]];
12434
12435 102
            if ($mState === 0) {
12436
                // When mState is zero we expect either a US-ASCII character or a
12437
                // multi-octet sequence.
12438 102
                if ((0x80 & $in) === 0) {
12439
                    // US-ASCII, pass straight through.
12440 97
                    $mBytes = 1;
12441 83
                } elseif ((0xE0 & $in) === 0xC0) {
12442
                    // First octet of 2 octet sequence.
12443 73
                    $mUcs4 = $in;
12444 73
                    $mUcs4 = ($mUcs4 & 0x1F) << 6;
12445 73
                    $mState = 1;
12446 73
                    $mBytes = 2;
12447 58
                } elseif ((0xF0 & $in) === 0xE0) {
12448
                    // First octet of 3 octet sequence.
12449 42
                    $mUcs4 = $in;
12450 42
                    $mUcs4 = ($mUcs4 & 0x0F) << 12;
12451 42
                    $mState = 2;
12452 42
                    $mBytes = 3;
12453 29
                } elseif ((0xF8 & $in) === 0xF0) {
12454
                    // First octet of 4 octet sequence.
12455 18
                    $mUcs4 = $in;
12456 18
                    $mUcs4 = ($mUcs4 & 0x07) << 18;
12457 18
                    $mState = 3;
12458 18
                    $mBytes = 4;
12459 13
                } elseif ((0xFC & $in) === 0xF8) {
12460
                    /* First octet of 5 octet sequence.
12461
                     *
12462
                     * This is illegal because the encoded codepoint must be either
12463
                     * (a) not the shortest form or
12464
                     * (b) outside the Unicode range of 0-0x10FFFF.
12465
                     * Rather than trying to resynchronize, we will carry on until the end
12466
                     * of the sequence and let the later error handling code catch it.
12467
                     */
12468 5
                    $mUcs4 = $in;
12469 5
                    $mUcs4 = ($mUcs4 & 0x03) << 24;
12470 5
                    $mState = 4;
12471 5
                    $mBytes = 5;
12472 10
                } elseif ((0xFE & $in) === 0xFC) {
12473
                    // First octet of 6 octet sequence, see comments for 5 octet sequence.
12474 5
                    $mUcs4 = $in;
12475 5
                    $mUcs4 = ($mUcs4 & 1) << 30;
12476 5
                    $mState = 5;
12477 5
                    $mBytes = 6;
12478
                } else {
12479
                    // Current octet is neither in the US-ASCII range nor a legal first
12480
                    // octet of a multi-octet sequence.
12481 102
                    return false;
12482
                }
12483 83
            } elseif ((0xC0 & $in) === 0x80) {
12484
12485
                // When mState is non-zero, we expect a continuation of the multi-octet
12486
                // sequence
12487
12488
                // Legal continuation.
12489 75
                $shift = ($mState - 1) * 6;
12490 75
                $tmp = $in;
12491 75
                $tmp = ($tmp & 0x0000003F) << $shift;
12492 75
                $mUcs4 |= $tmp;
12493
                // Prefix: End of the multi-octet sequence. mUcs4 now contains the final
12494
                // Unicode code point to be output.
12495 75
                if (--$mState === 0) {
12496
                    // Check for illegal sequences and code points.
12497
                    //
12498
                    // From Unicode 3.1, non-shortest form is illegal
12499
                    if (
12500 75
                        ($mBytes === 2 && $mUcs4 < 0x0080)
12501
                        ||
12502 75
                        ($mBytes === 3 && $mUcs4 < 0x0800)
12503
                        ||
12504 75
                        ($mBytes === 4 && $mUcs4 < 0x10000)
12505
                        ||
12506 75
                        ($mBytes > 4)
12507
                        ||
12508
                        // From Unicode 3.2, surrogate characters are illegal.
12509 75
                        (($mUcs4 & 0xFFFFF800) === 0xD800)
12510
                        ||
12511
                        // Code points outside the Unicode range are illegal.
12512 75
                        ($mUcs4 > 0x10FFFF)
12513
                    ) {
12514 9
                        return false;
12515
                    }
12516
                    // initialize UTF8 cache
12517 75
                    $mState = 0;
12518 75
                    $mUcs4 = 0;
12519 75
                    $mBytes = 1;
12520
                }
12521
            } else {
12522
                // ((0xC0 & (*in) != 0x80) && (mState != 0))
12523
                // Incomplete multi-octet sequence.
12524 35
                return false;
12525
            }
12526
        }
12527
12528 67
        return true;
12529
    }
12530
12531
    /**
12532
     * @param string $str
12533
     * @param bool   $use_lowercase      <p>Use uppercase by default, otherwise use lowercase.</p>
12534
     * @param bool   $use_full_case_fold <p>Convert not only common cases.</p>
12535
     *
12536
     * @return string
12537
     *
12538
     * @noinspection ReturnTypeCanBeDeclaredInspection
12539
     */
12540 33
    private static function fixStrCaseHelper(
12541
        string $str,
12542
        $use_lowercase = false,
12543
        $use_full_case_fold = false
12544
    ) {
12545 33
        $upper = self::$COMMON_CASE_FOLD['upper'];
12546 33
        $lower = self::$COMMON_CASE_FOLD['lower'];
12547
12548 33
        if ($use_lowercase === true) {
12549 2
            $str = \str_replace(
12550 2
                $upper,
12551 2
                $lower,
12552 2
                $str
12553
            );
12554
        } else {
12555 31
            $str = \str_replace(
12556 31
                $lower,
12557 31
                $upper,
12558 31
                $str
12559
            );
12560
        }
12561
12562 33
        if ($use_full_case_fold) {
12563 31
            static $FULL_CASE_FOLD = null;
12564 31
            if ($FULL_CASE_FOLD === null) {
12565 1
                $FULL_CASE_FOLD = self::getData('caseFolding_full');
12566
            }
12567
12568 31
            if ($use_lowercase === true) {
12569 2
                $str = \str_replace($FULL_CASE_FOLD[0], $FULL_CASE_FOLD[1], $str);
12570
            } else {
12571 29
                $str = \str_replace($FULL_CASE_FOLD[1], $FULL_CASE_FOLD[0], $str);
12572
            }
12573
        }
12574
12575 33
        return $str;
12576
    }
12577
12578
    /**
12579
     * get data from "/data/*.php"
12580
     *
12581
     * @param string $file
12582
     *
12583
     * @return array
12584
     *
12585
     * @noinspection ReturnTypeCanBeDeclaredInspection
12586
     */
12587 6
    private static function getData(string $file)
12588
    {
12589
        /** @noinspection PhpIncludeInspection */
12590
        /** @noinspection UsingInclusionReturnValueInspection */
12591
        /** @psalm-suppress UnresolvableInclude */
12592 6
        return include __DIR__ . '/data/' . $file . '.php';
12593
    }
12594
12595
    /**
12596
     * @return true|null
12597
     */
12598 12
    private static function initEmojiData()
12599
    {
12600 12
        if (self::$EMOJI_KEYS_CACHE === null) {
12601 1
            if (self::$EMOJI === null) {
12602 1
                self::$EMOJI = self::getData('emoji');
12603
            }
12604
12605 1
            \uksort(
12606 1
                self::$EMOJI,
12607
                static function (string $a, string $b): int {
12608 1
                    return \strlen($b) <=> \strlen($a);
12609 1
                }
12610
            );
12611
12612 1
            self::$EMOJI_KEYS_CACHE = \array_keys(self::$EMOJI);
12613 1
            self::$EMOJI_VALUES_CACHE = \array_values(self::$EMOJI);
12614
12615 1
            foreach (self::$EMOJI_KEYS_CACHE as $key) {
12616 1
                $tmp_key = \crc32($key);
12617 1
                self::$EMOJI_KEYS_REVERSIBLE_CACHE[] = '_-_PORTABLE_UTF8_-_' . $tmp_key . '_-_' . \strrev((string) $tmp_key) . '_-_8FTU_ELBATROP_-_';
12618
            }
12619
12620 1
            return true;
12621
        }
12622
12623 12
        return null;
12624
    }
12625
12626
    /**
12627
     * Checks whether mbstring "overloaded" is active on the server.
12628
     *
12629
     * @return bool
12630
     *
12631
     * @noinspection ReturnTypeCanBeDeclaredInspection
12632
     */
12633
    private static function mbstring_overloaded()
12634
    {
12635
        /**
12636
         * INI directive 'mbstring.func_overload' is deprecated since PHP 7.2
12637
         */
12638
12639
        /** @noinspection PhpComposerExtensionStubsInspection */
12640
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
12641
        return \defined('MB_OVERLOAD_STRING')
12642
               &&
12643
               ((int) @\ini_get('mbstring.func_overload') & \MB_OVERLOAD_STRING);
12644
    }
12645
12646
    /**
12647
     * @param array    $strings
12648
     * @param bool     $remove_empty_values
12649
     * @param int|null $remove_short_values
12650
     *
12651
     * @return array
12652
     *
12653
     * @noinspection ReturnTypeCanBeDeclaredInspection
12654
     */
12655 2
    private static function reduce_string_array(
12656
        array $strings,
12657
        bool $remove_empty_values,
12658
        int $remove_short_values = null
12659
    ) {
12660
        // init
12661 2
        $return = [];
12662
12663 2
        foreach ($strings as &$str) {
12664
            if (
12665 2
                $remove_short_values !== null
12666
                &&
12667 2
                \mb_strlen($str) <= $remove_short_values
12668
            ) {
12669 2
                continue;
12670
            }
12671
12672
            if (
12673 2
                $remove_empty_values === true
12674
                &&
12675 2
                \trim($str) === ''
12676
            ) {
12677 2
                continue;
12678
            }
12679
12680 2
            $return[] = $str;
12681
        }
12682
12683 2
        return $return;
12684
    }
12685
12686
    /**
12687
     * rxClass
12688
     *
12689
     * @param string $s
12690
     * @param string $class
12691
     *
12692
     * @return string
12693
     *
12694
     * @noinspection ReturnTypeCanBeDeclaredInspection
12695
     */
12696 33
    private static function rxClass(string $s, string $class = '')
12697
    {
12698 33
        static $RX_CLASS_CACHE = [];
12699
12700 33
        $cache_key = $s . $class;
12701
12702 33
        if (isset($RX_CLASS_CACHE[$cache_key])) {
12703 21
            return $RX_CLASS_CACHE[$cache_key];
12704
        }
12705
12706 16
        $class_array = [$class];
12707
12708
        /** @noinspection SuspiciousLoopInspection */
12709
        /** @noinspection AlterInForeachInspection */
12710 16
        foreach (self::str_split($s) as &$s) {
0 ignored issues
show
Bug introduced by
The expression self::str_split($s) cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
12711 15
            if ($s === '-') {
12712
                $class_array[0] = '-' . $class_array[0];
12713 15
            } elseif (!isset($s[2])) {
12714 15
                $class_array[0] .= \preg_quote($s, '/');
12715 1
            } elseif (self::strlen($s) === 1) {
12716 1
                $class_array[0] .= $s;
12717
            } else {
12718 15
                $class_array[] = $s;
12719
            }
12720
        }
12721
12722 16
        if ($class_array[0]) {
12723 16
            $class_array[0] = '[' . $class_array[0] . ']';
12724
        }
12725
12726 16
        if (\count($class_array) === 1) {
12727 16
            $return = $class_array[0];
12728
        } else {
12729
            $return = '(?:' . \implode('|', $class_array) . ')';
12730
        }
12731
12732 16
        $RX_CLASS_CACHE[$cache_key] = $return;
12733
12734 16
        return $return;
12735
    }
12736
12737
    /**
12738
     * Personal names such as "Marcus Aurelius" are sometimes typed incorrectly using lowercase ("marcus aurelius").
12739
     *
12740
     * @param string $names
12741
     * @param string $delimiter
12742
     * @param string $encoding
12743
     *
12744
     * @return string
12745
     *
12746
     * @noinspection ReturnTypeCanBeDeclaredInspection
12747
     */
12748 1
    private static function str_capitalize_name_helper(
12749
        string $names,
12750
        string $delimiter,
12751
        string $encoding = 'UTF-8'
12752
    ) {
12753
        // init
12754 1
        $name_helper_array = \explode($delimiter, $names);
12755 1
        if ($name_helper_array === false) {
12756
            return '';
12757
        }
12758
12759
        $special_cases = [
12760 1
            'names' => [
12761
                'ab',
12762
                'af',
12763
                'al',
12764
                'and',
12765
                'ap',
12766
                'bint',
12767
                'binte',
12768
                'da',
12769
                'de',
12770
                'del',
12771
                'den',
12772
                'der',
12773
                'di',
12774
                'dit',
12775
                'ibn',
12776
                'la',
12777
                'mac',
12778
                'nic',
12779
                'of',
12780
                'ter',
12781
                'the',
12782
                'und',
12783
                'van',
12784
                'von',
12785
                'y',
12786
                'zu',
12787
            ],
12788
            'prefixes' => [
12789
                'al-',
12790
                "d'",
12791
                'ff',
12792
                "l'",
12793
                'mac',
12794
                'mc',
12795
                'nic',
12796
            ],
12797
        ];
12798
12799 1
        foreach ($name_helper_array as &$name) {
12800 1
            if (\in_array($name, $special_cases['names'], true)) {
12801 1
                continue;
12802
            }
12803
12804 1
            $continue = false;
12805
12806 1
            if ($delimiter === '-') {
12807
                /** @noinspection AlterInForeachInspection */
12808 1
                foreach ((array) $special_cases['names'] as &$beginning) {
12809 1
                    if (self::strpos($name, $beginning, 0, $encoding) === 0) {
12810 1
                        $continue = true;
12811
                    }
12812
                }
12813
            }
12814
12815
            /** @noinspection AlterInForeachInspection */
12816 1
            foreach ((array) $special_cases['prefixes'] as &$beginning) {
12817 1
                if (self::strpos($name, $beginning, 0, $encoding) === 0) {
12818 1
                    $continue = true;
12819
                }
12820
            }
12821
12822 1
            if ($continue === true) {
12823 1
                continue;
12824
            }
12825
12826 1
            $name = self::ucfirst($name);
12827
        }
12828
12829 1
        return \implode($delimiter, $name_helper_array);
12830
    }
12831
12832
    /**
12833
     * Generic case-sensitive transformation for collation matching.
12834
     *
12835
     * @param string $str <p>The input string</p>
12836
     *
12837
     * @return string|null
12838
     */
12839 6
    private static function strtonatfold(string $str)
12840
    {
12841
        /** @noinspection PhpUndefinedClassInspection */
12842 6
        return \preg_replace(
12843 6
            '/\p{Mn}+/u',
12844 6
            '',
12845 6
            \Normalizer::normalize($str, \Normalizer::NFD)
12846
        );
12847
    }
12848
12849
    /**
12850
     * @param int|string $input
12851
     *
12852
     * @return string
12853
     *
12854
     * @noinspection ReturnTypeCanBeDeclaredInspection
12855
     */
12856 31
    private static function to_utf8_convert_helper($input)
12857
    {
12858
        // init
12859 31
        $buf = '';
12860
12861 31
        if (self::$ORD === null) {
12862 1
            self::$ORD = self::getData('ord');
12863
        }
12864
12865 31
        if (self::$CHR === null) {
12866 1
            self::$CHR = self::getData('chr');
12867
        }
12868
12869 31
        if (self::$WIN1252_TO_UTF8 === null) {
12870 1
            self::$WIN1252_TO_UTF8 = self::getData('win1252_to_utf8');
12871
        }
12872
12873 31
        $ordC1 = self::$ORD[$input];
12874 31
        if (isset(self::$WIN1252_TO_UTF8[$ordC1])) { // found in Windows-1252 special cases
12875 31
            $buf .= self::$WIN1252_TO_UTF8[$ordC1];
12876
        } else {
12877
            /** @noinspection OffsetOperationsInspection */
12878 1
            $cc1 = self::$CHR[$ordC1 / 64] | "\xC0";
12879 1
            $cc2 = ((string) $input & "\x3F") | "\x80";
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise & or did you mean &&?
Loading history...
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
12880 1
            $buf .= $cc1 . $cc2;
12881
        }
12882
12883 31
        return $buf;
12884
    }
12885
12886
    /**
12887
     * @param string $str
12888
     *
12889
     * @return string
12890
     *
12891
     * @noinspection ReturnTypeCanBeDeclaredInspection
12892
     */
12893 10
    private static function urldecode_unicode_helper(string $str)
12894
    {
12895 10
        $pattern = '/%u([0-9a-fA-F]{3,4})/';
12896 10
        if (\preg_match($pattern, $str)) {
12897 7
            $str = (string) \preg_replace($pattern, '&#x\\1;', $str);
12898
        }
12899
12900 10
        return $str;
12901
    }
12902
}
12903